本文首发于 个人博客
之前分享了一篇关于栈这种数据结构的逻辑和实现,这篇文章我们看看队列这种数据结构是一种什么样的结构以及如何从顺序存储和链式存储去实现这么一个结构。
队列也是一种线性数据结构,跟栈的结构差不多,唯一不同的的就是 栈是先进后出 而 队列是先进先出 也就是通常所说的 FIFO : first in first out !
顺序存储队列
以上是一个顺序存储队列的示意图,标红的字母表示目前不在队列中,但是这些数据依旧在开辟的存储空间(数组)中(俗称脏数据) ,这点很重要,因为我们只是挪动头尾代表的索引,并不会删除具体的数据,从图中可以看出:
①图:空队列的时候队头和队尾都指向默认的一个节点
②图:入队的时候只需要挪动队尾的位置即可
③图:出队同入队只需要挪动队头的位置即可
-
③④图:当队尾达到存储空间尾部即代表队列已满
综上我们会发现当 Q.rear 到达尾部存储空间的时候代表队列已满,但是 Q.front 可能已经随着出队列已经空出了一些空间,这样就导致了整个存储空间的浪费。第二在我们 ④图中如果 f 数据也出队列的话 Q.rear == Q.front 跟我们判断队列是否为空是一样的,所以就导致了整个判断的多意性,所以我们需要另外的一种形式来表示我们的线性队列
循环队列
为了避免存储空间的浪费以及这种结构的重复利用性,出现 循环队列 的存储结构,看起来是这样:
但是这个结构仍然有个问题就是依旧无法判断队满还是队空,计算机科学领域的任务问题都可以通过增加一个中间层来解决 ,这也一样,牺牲掉一个存储单元来对他们进行分隔就很容易判断了,其实说是牺牲一个存储单元不如说是额外增加一个存储单元,讲道理不是一个意思嘛!
循环队列的线性结构
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 进行操作
- 循环队列的循环入队以及出队的过程中涉及到 rear 和 front 位置的变迁,这也涉及到一个环状计算的问题,这里一般使用 %(模) 运算进行处理
链式存储队列
相比线性循环队列而言,对列的链式存储方式就简单太多,其实说到底就是一个 链表,具体怎么设计就看自己了,这里我以 带头节点的单向链表作为例子:
如图我们首先要定义节点的结构和队列的结构,队列的结构又依托于节点,所以它们的结构应该是这样:
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;
}
关于单链表前面我们已经有文章做过概述,这里我们就不对其进行一一验证,具体的代码有需要请前往下载代码