队列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为 rear)进行插入操作,在前端(称为 front)进行删除操作。队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。
循环队列
和顺序栈相类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,还设置了两个整形变量 front 和 rear 分别指示队列头元素和队列尾元素的位置(分别称为头指针和尾指针)。
为了防止因为数组越界而导致的程序非法操作错误(我们称为“假溢出”)。因为此时队列的实际空间并未占满,所以这种现象称为“假溢出”。造成这种情况的原因是由于“队尾入队,队头出队”这种受限制的操作造成的。
关于“假溢出”,可以参考下面这两篇 csdn 博客。
队列---顺序队列的存储结构不足(假溢出)
队列---循环队列
上述“假溢出”的问题该怎么解决么?这里采用一个巧妙的办法,将顺序队列变成一个环状空间,称之为循环队列。
#include <stdio.h>
#include <stdlib.h>
#define MAX_QUEUE_SIZE 100
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define INFEASIBLE 0
typedef int Status;
typedef int QElemType;
typedef struct {
QElemType *base; //存储空间基地址
int front; //头指针
int rear; //尾指针
int QueueSize; //队列容量
}SqQueue;
Status InitQueue(SqQueue *);
Status DestroyQueue(SqQueue *);
Status ClearQueue(SqQueue *);
Status QueueEmpty(SqQueue );
int QueueLength(SqQueue);
Status GetHead(SqQueue, QElemType *);
Status EnQueue(SqQueue *, QElemType);
Status DeQueue(SqQueue *, QElemType *);
void QueueTraverse(SqQueue , void(*vi)(QElemType));
/**
* 操作结果:构造一个空队列 Q
* @param e
* @return
*/
Status InitQueue(SqQueue *Q) {
Q->base = (QElemType *)malloc(MAX_QUEUE_SIZE * sizeof(SqQueue));
if (!Q->base) {
exit(OVERFLOW);
}
Q->front = Q->rear = 0; //头指针尾指针置为零,队列为空
return OK;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:队列 Q 被销毁
* @param Q
* @return
*/
Status DestroyQueue(SqQueue *Q) {
if (Q->base) {
free(Q->base);
}
Q->base = NULL;
Q->front = Q->rear = 0;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:清空队列 Q
* @param Q
* @return
*/
Status ClearQueue(SqQueue *Q) {
Q->front = Q->rear = 0;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:若 Q 为空队列,则返回 true,否则返回 false
* @param Q
* @return
*/
Status QueueEmpty(SqQueue Q) {
if (Q.front == Q.rear) {
return TRUE;
} else {
return FALSE;
}
}
/**
* 初始条件:队列 Q 存在
* 操作结果:返回 Q 中元素个数,即队列长度
* @param Q
* @return
*/
int QueueLength(SqQueue Q) {
return (Q.rear - Q.front + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE;
}
/**
* 初始条件:队列 Q 存在且非空
* 操作结果:返回 Q 中队头元素
* @param Q
* @param e
* @return
*/
Status GetHead(SqQueue Q, QElemType *e) {
if (Q.front == Q.rear) {
return ERROR;
}
*e = Q.base[Q.front];
return OK;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:插入入元素 e 为 Q 的新队尾元素
* @param Q
* @param e
* @return
*/
Status EnQueue(SqQueue *Q, QElemType e) {
if ((Q->rear + 1) % MAX_QUEUE_SIZE == Q->front) {
return ERROR; //队尾指针在循环意义上加 1 后等于头指针,表示队满
}
Q->base[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAX_QUEUE_SIZE; //队尾指针加 1
return OK;
}
/**
* 初始条件:队列 Q 存在且非空
* 操作结果:删除 Q 的队头元素,且用 e 返回
* @param Q
* @param e
* @return
*/
Status DeQueue(SqQueue *Q, QElemType *e) {
if (Q->front == Q->rear) {
return ERROR;
}
*e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAX_QUEUE_SIZE; //队头指针加 1
return OK;
}
/**
* 初始条件:队列 Q 存在且非空
* 操作结果:从队头到队尾,依次对遍历队列中每个元素
* @param Q
* @param vi
*/
void QueueTraverse(SqQueue Q, void(*vi)(QElemType)) {
int i = Q.front;
while (i != Q.rear) {
vi(Q.base[i]); //遍历
i = (i + 1) % MAX_QUEUE_SIZE;
}
printf("\n");
}
/**
* 遍历函数
* @param e
*/
void vi(QElemType e) {
printf("%d ",e);
}
/**
* 主函数,测试程序
* @return
*/
int main() {
SqQueue q;
QElemType e;
InitQueue(&q);
printf("队列的长度:%d\n", QueueLength(q));
printf("队列是否为空:%d\n", QueueEmpty(q));
EnQueue(&q, 3);
EnQueue(&q, 4);
EnQueue(&q, 5);
EnQueue(&q, 6);
QueueTraverse(q, vi);
printf("队列的长度:%d\n", QueueLength(q));
printf("队列是否为空:%d\n", QueueEmpty(q));
GetHead(q, &e);
printf("队列的头元素:%d\n", e);
DeQueue(&q, &e);
QueueTraverse(q, vi);
ClearQueue(&q);
printf("队列的长度:%d\n", QueueLength(q));
printf("队列是否为空:%d\n", QueueEmpty(q));
DestroyQueue(&q);
}
上面测试代码在 Clion 下的执行结果如下图所示。
链式队列
链队是指采用链式存储结构实现的对列,队列通常用链队来实现,它有效的解决了队列存储空间不足的问题。
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define INFEASIBLE 0
typedef int Status;
typedef int QElemType;
typedef struct QNode {
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct {
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
Status InitQueue(LinkQueue *);
Status DestroyQueue(LinkQueue *);
Status ClearQueue(LinkQueue *);
Status QueueEmpty(LinkQueue );
int QueueLength(LinkQueue);
Status GetHead(LinkQueue, QElemType *);
Status EnQueue(LinkQueue *, QElemType);
Status DeQueue(LinkQueue *, QElemType *);
void QueueTraverse(LinkQueue , void(*vi)(QElemType));
/**
* 操作结果:构造一个空队列 Q
* @param Q
* @return
*/
Status InitQueue(LinkQueue *Q) {
if (!(Q->front = Q->rear = (QueuePtr)malloc(sizeof(struct QNode)))) {
exit(OVERFLOW);
}
Q->front->next = NULL;
return OK;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:队列 Q 被销毁
* @param Q
* @return
*/
Status DestroyQueue(LinkQueue *Q) {
while (Q->front) { //指向队尾时结束循环
Q->rear = Q->front->next; // 队尾指针指向队头指针的下一个结点
free(Q->front); //释放队头结点
Q->front = Q->rear; //修改队头指针
}
return OK;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:清空队列 Q
* @param Q
* @return
*/
Status ClearQueue(LinkQueue *Q) {
QueuePtr p, q;
Q->rear = Q->front; //队尾指针指向头结点
p = Q->front->next; //p 指向队列第一个元素
Q->front->next = NULL; //队头指针 next 域置空
while (p) {
q = p->next;
free(p);
p = q;
}
return OK;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:若 Q 为空队列,则返回 true,否则返回 false
* @param Q
* @return
*/
Status QueueEmpty(LinkQueue Q) {
if (Q.front->next == NULL) {
return TRUE;
} else {
return FALSE;
}
}
/**
* 初始条件:队列 Q 存在
* 操作结果:返回 Q 中元素个数,即队列长度
* @param Q
* @return
*/
int QueueLength(LinkQueue Q) {
int i = 0; //计数器
QueuePtr p = Q.front->next; //p 指向队头元素
while (p) {
i++;
p = p->next;
}
return i;
}
/**
* 初始条件:队列 Q 存在且非空
* 操作结果:返回 Q 中队头元素
* @param Q
* @param e
* @return
*/
Status GetHead(LinkQueue Q, QElemType *e) {
if (Q.front != Q.rear) {
*e = Q.front->next->data;
return OK;
}
return INFEASIBLE;
}
/**
* 初始条件:队列 Q 存在
* 操作结果:插入入元素 e 为 Q 的新队尾元素
* @param Q
* @param e
* @return
*/
Status EnQueue(LinkQueue *Q, QElemType e) {
QueuePtr p;
p = (QueuePtr)malloc(sizeof(struct QNode)); //开辟新结点
p->data = e;
p->next = NULL;
Q->rear->next = p; //将新结点插入到队尾
Q->rear = p; //修改队尾指针
return OK;
}
/**
* 初始条件:队列 Q 存在且非空
* 操作结果:删除 Q 的队头元素,且用 e 返回
* @param Q
* @param e
* @return
*/
Status DeQueue(LinkQueue *Q, QElemType *e) {
QueuePtr p;
if (Q->front == Q->rear) { //判断是否为空队列
return ERROR;
}
p = Q->front->next; //p 指向队头元素
*e = p->data;
Q->front->next = p->next; //修改头指针
if (Q->rear == p) { //如果删除的是队列最后一个元素
Q->rear = Q->front; //队尾指针指向头结点
}
free(p);
return OK;
}
/**
* 初始条件:队列 Q 存在且非空
* 操作结果:从队头到队尾,依次对遍历队列中每个元素
* @param Q
* @param vi
*/
void QueueTraverse(LinkQueue Q, void(*vi)(QElemType)) {
QueuePtr p = Q.front->next; //p 指向队头元素
while (p) {
vi(p->data); //遍历
p = p->next;
}
printf("\n");
}
/**
* 遍历函数
* @param e
*/
void vi(QElemType e) {
printf("%d ",e);
}
/**
* 主函数,测试程序
* @return
*/
int main() {
LinkQueue q;
QElemType e;
InitQueue(&q);
printf("队列的长度:%d\n", QueueLength(q));
printf("队列是否为空:%d\n", QueueEmpty(q));
EnQueue(&q, 3);
EnQueue(&q, 4);
EnQueue(&q, 5);
EnQueue(&q, 6);
QueueTraverse(q, vi);
printf("队列的长度:%d\n", QueueLength(q));
printf("队列是否为空:%d\n", QueueEmpty(q));
GetHead(q, &e);
printf("队列的头元素:%d\n", e);
DeQueue(&q, &e);
QueueTraverse(q, vi);
ClearQueue(&q);
printf("队列的长度:%d\n", QueueLength(q));
printf("队列是否为空:%d\n", QueueEmpty(q));
DestroyQueue(&q);
}
具体运行效果和顺序栈运行效果一样。
想要查看源码可以访问下面 github 链接,如果觉得不错,请给个 Star。
本文属数据结构系列持续更新中。