数据结构与算法 - 队列

本文首发于 我的博客node

以前分享了一篇关于栈这种数据结构的逻辑和实现,这篇文章咱们看看队列这种数据结构是一种什么样的结构以及如何从顺序存储和链式存储去实现这么一个结构。git

队列也是一种线性数据结构,跟栈的结构差很少,惟一不一样的的就是 栈是先进后出队列是先进先出 也就是一般所说的 FIFO : first in first out !github

顺序存储队列

以上是一个顺序存储队列的示意图,标红的字母表示目前不在队列中,可是这些数据依旧在开辟的存储空间(数组)中(俗称脏数据) ,这点很重要,由于咱们只是挪动头尾表明的索引,并不会删除具体的数据,从图中能够看出:数组

  • ①图:空队列的时候队头和队尾都指向默认的一个节点markdown

  • ②图:入队的时候只须要挪动队尾的位置便可数据结构

  • ③图:出队同入队只须要挪动队头的位置便可app

  • ③④图:当队尾达到存储空间尾部即表明队列已满oop

    综上咱们会发现当 Q.rear 到达尾部存储空间的时候表明队列已满,可是 Q.front 可能已经随着出队列已经空出了一些空间,这样就致使了整个存储空间的浪费。第二在咱们 图中若是 f 数据也出队列的话 Q.rear == Q.front 跟咱们判断队列是否为空是同样的,因此就致使了整个判断的多意性,因此咱们须要另外的一种形式来表示咱们的线性队列spa

循环队列

为了不存储空间的浪费以及这种结构的重复利用性,出现 循环队列 的存储结构,看起来是这样:设计

可是这个结构仍然有个问题就是依旧没法判断队满仍是队空,计算机科学领域的任务问题均可以经过增长一个中间层来解决 ,这也同样,牺牲掉一个存储单元来对他们进行分隔就很容易判断了,其实说是牺牲一个存储单元不如说是额外增长一个存储单元,讲道理不是一个意思嘛!

循环队列的线性结构

typedef struct Node {
    Data data[MAXSIZE];
    int rear;
    int front;
} Queue;
复制代码

基于此种结构,咱们对它的一些方法进行构造:

// 初始化一个空队列
Status InitQueue(Queue *Q) {
    Q->rear = 0;
    Q->rear = 0;
    return SUCCESS;
}

// 清空一个队列
Status ClearQueue(Queue *Q) {
    Q->rear = 0;
    Q->front = 0;
    return SUCCESS;
}

// 是否为空队列
Status IsEmpty(Queue Q) {
    return Q.rear == Q.front ? TRUE:FALSE;
}

// 队列长度
int Length(Queue Q) {
    return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

// 获取头节点
Status GetHead(Queue Q,Data *data) {
    if (IsEmpty(Q)) return ERROR;
    *data = Q.data[Q.front];
    return SUCCESS;
}

// 入丢列
Status QueueEnter(Queue *Q,Data data) {
    if ((Q->rear+1)%MAXSIZE == Q->front) return ERROR;
    Q->data[Q->rear] = data;
    // rear 指针向后移一位,若是到达最后则转到数组头部
    Q->rear = (Q->rear+1)%MAXSIZE;
    return SUCCESS;
}

// 出队列
Status QueuePop(Queue *Q, Data *data) {
    if (IsEmpty(*Q)) return ERROR;
    *data = Q->data[Q->front];
    // front 同上面出队列的rear同样
    Q->front = (Q->front+1)%MAXSIZE;
    return SUCCESS;
}

// 遍历队列
Status QueueTraverse(Queue Q) {
    if (IsEmpty(Q)) return ERROR;
    int i = Q.front;
    while ((i+Q.front) != Q.rear) {
        printf("%d   ",Q.data[i]);
        i = (i+1)%MAXSIZE;
    }
    printf("\n");
    return SUCCESS;
}
复制代码

注意循环队列的关键点在于

  • 咱们牺牲了一个节点,因此在于队列是否满的状态判断中要针对 +1 进行操做
  • 循环队列的循环入队以及出队的过程当中涉及到 rearfront 位置的变迁,这也涉及到一个环状计算的问题,这里通常使用 %(模) 运算进行处理

链式存储队列

相比线性循环队列而言,对列的链式存储方式就简单太多,其实说到底就是一个 链表,具体怎么设计就看本身了,这里我以 带头节点的单向链表做为例子:

如图咱们首先要定义节点的结构和队列的结构,队列的结构又依托于节点,因此它们的结构应该是这样:

typedef int Status;
typedef int Data;

typedef struct Node {
    Data data;
    struct Node *next;
} Node;

typedef struct {
    Node *front;
    Node *rear;
} Queue;
复制代码

其实说到底仍是队单链表的处理:

// 初始化队列
Status InitQueue(Queue *Q) {
    Q->front = Q->rear = (Node *)malloc(sizeof(Node));
    if (!Q->front) return ERROR;
    Q->front->next = NULL;
    return SUCCESS;
}

// 销毁队列
Status DestroyQueue(Queue *Q) {
    // 遍历链表进行销毁
    while (Q->front) {
        Node *temp = = Q->front;
        Q->front = Q->front->next;
        free(temp);
    }
    return SUCCESS;
}

// 将队列置空
Status ClearQueue(Queue *Q) {
    Node *target = Q->front->next;
    while (target) {
        Node *temp = target;
        target = target->next;
        free(temp);
    }
    Q->front->next = Q->rear->next = NULL;
    return SUCCESS;
}

// 判断队列是否为空
Status IsEmpty(Queue Q) {
    return (Q.rear == Q.front)?TRUE:FALSE;
}

// 获取队列的长度
int GetLength(Queue Q) {
    Node *target = Q.front->next;
    int i=0;
    while (target) {
        i++;
        target = target->next;
    }
    return i;
}

// 入队
Status QueueEnter(Queue *Q,Data data) {
    Node *node = (Node *)malloc(sizeof(node));
    if (!node) return ERROR;
    node->data = data;
    node->next = NULL;
    Q->rear->next = node;
    // 修改队尾指针
    Q->rear = node;
    return SUCCESS;
}

// 出队
Status QueuePop(Queue *Q,Data *data) {
    if (Q->front == Q->rear) return ERROR;
    Node *temp = Q->front->next;
    *data = temp->data;
    Q->front->next = temp->next;
    // 若是只有一个节点,就要移动尾指针
    if (temp == Q->rear) {
        Q->rear = Q->front;
    }
    free(temp);
    return SUCCESS;
}

// 获取队头元素
Status GetHead(Queue Q,Data *data) {
    if (Q.front == Q.rear) return ERROR;
    *data = Q.front->next->data;
    return SUCCESS;
}

// 遍历队列
Status QueueTraverse(Queue Q) {
    Node *target = Q.front->next;
    printf("打印队列是:")
    while (target) {
        printf("%d ",target->data);
        target = target->next;
    }
    printf("\n");
    return SUCCESS;
}
复制代码

关于单链表前面咱们已经有文章作过概述,这里咱们就不对其进行一一验证,具体的代码有须要请前往下载代码

这篇文章主要是讲述了队列的结构原理以及队列的两种实现方式,但愿可以讲清楚,有问题还请随时留言,谢谢!