栈和队列——队列
栈和队列——队列
- 1. 队列的基本定义
- 2. 队列的实现(顺序存储结构)
- 2.1 队列的结构定义
- 2.2 队列的初始化
- 2.3 插入数据(入队列)
- 2.4 删除数据(出队列)
- 2.5 获取队头元素
- 2.6 获取队尾元素
- 2.7 获取有效元素个数
- 2.8 检测队列是否已满
- 2.9 检测队列是否为空
- 2.10 队列的销毁
- 2.11 顺序队列功能综合
- 3. 队列的实现(链式存储结构)
- 3.1 队列的结构定义
- 3.2 队列的初始化
- 3.3 插入数据(入队列)
- 3.4 删除数据(出队列)
- 3.5 获取队头元素
- 3.6 获取队尾元素
- 3.7 获取有效元素个数
- 3.8 判断队列是否为空
- 3.9 队列的销毁
- 3.10 链队列功能综合
- 4. 综合对比
1. 队列的基本定义
我们把只允许在一端插入数据,另一端删除数据的线性表称之为 队列。队列是一种**先进先出(First In First Out)**的线性数据结构,简称FIFO。它和栈一样,都是特殊的线性表。
队头:允许删除数据的一端,又称队首。
队尾:允许插入数据的一端。
入队:向队尾插入数据。
出队:删除队头数据。
2. 队列的实现(顺序存储结构)
在栈的部分我们提到了两种实现方法,由于队列也是一种特殊的线性表,那么实现方法也有两种。首先使用顺序存储结构进行实现。
在实现之前,我们要想到顺序表的缺点,除了尾部插入数据效率高,头部和中间插入删除数据时可能会需要对表中元素进行大量的移动,效率比较低,当然这样也可以实现。这里我们使用顺序结构实现队列就换一种方式—— 循环队列。
循环队列是一种使用 固定大小数组 实现的队列,通过取模运算使得队列在 逻辑上首尾相连,从而更高效地利用存储空间。是不是容易联想到之前提到的双向循环链表,但在循环队列的定义里可没有指针域。
注意:循环队列使用的是数组,我们只能将它想象成首尾相连,为了方便理解,这就类似于一个圆环,而它的实际结构可不是圆环。
2.1 队列的结构定义
思考一下,如果队列只有一个元素时,front 和 back 位置重合,就会产生歧义,两者重合可能是空、满、或者有一个元素。所以为了避免这种情况,我们就把 back 定义成队尾元素的下一个位置。
这里一定要把 front 和 back 理解清楚。
//循环队列(顺序存储结构)
typedef int QDataType;
typedef struct QueueSNode {QDataType* a; //数组int front; //队头元素下标int back; //队尾元素下一个位置的下标int size; //数组长度
}QueueSNode;
图解:
2.2 队列的初始化
由于 back 是队尾元素的下一个位置,如过数组长度还是 k ,当队列满的时候,front 和 back 重合,还是会产生歧义,所以为了避免这种情况,我们就会开辟 k + 1 个空间,多分配一个空间就会避免这种情况。尽管已经对空间分配好了,在其他功能判断队列空和满的部分还是得细心。
QueueSNode* QueueSInit(int k) { //数组长度KQueueSNode* qs = (QueueSNode*)malloc(sizeof(QueueSNode));QDataType* tmp = (QDataType*)malloc(sizeof(QDataType) * (k + 1)); //这里其实是为数组开辟空间,注意是 k+1 if (qs == NULL) {perror("malloc fail!");return;}qs->a = tmp;qs->front = 0;qs->back = 0;qs->size = k;return qs;
}
图解:
2.3 插入数据(入队列)
在循环队列中,有一种现象叫假溢出,在使用普通顺序队列(基于数组实现的非循环队列)时,队列实际上还有可用空间,但由于队列的实现方式导致无法继续插入新元素。
简单来说就是当数组的最后一个位置被占用时,back 会走到下一个位置,但是数组的长度固定,back 就会在一个溢出的位置。而实际上前面还有位置插入,这就是假溢出。如下图:
这时循环的重要性就体现出来了,通过取模的方式实现循环。如下图:
void QueueSPush(QueueSNode* qs, QDataType x) {if (QueueSFull(qs)) { //判断队列是否为满的函数,下面会提到return;}qs->a[qs->back] = x; //直接插入qs->back = (qs->back + 1) % (qs->size + 1); //循环的体现,防止假溢出}
还要注意的一点是 (qs->back + 1) 模的是数组的总长度,就是上面初始化为数组开辟空间时的(k + 1),也就是 size + 1 。
2.4 删除数据(出队列)
void QueueSPop(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return;}++qs->front;qs->front = (qs->front) % (qs->size + 1); //避免假溢出}
2.5 获取队头元素
QDataType QueueSFront(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return ;}return qs->a[qs->front]; //直接返回 front 位置的值
}
2.6 获取队尾元素
由于 back 位置为队尾的下一个,在取队尾元素时就要退一格。当 back 在两个极端位置时,要分开判断。
QDataType QueueSTail(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return;}if (qs->back == 0) {return qs->a[qs->size]; //back 在下标为0处,队尾就在元素就在最后 }else {return qs->a[qs->back - 1]; //其他情况 back-1 即可}
}
图解:
2.7 获取有效元素个数
我们可以使用 front 和 back 计算元素个数,经过反复论证,最终得出:(back - front + (size + 1)) % (size + 1) 。
int QueueSCount(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return;}return (qs->back - qs->front + (qs->size + 1)) % (qs->size + 1);
}
2.8 检测队列是否已满
back+1 与 front 重合时则为满。
bool QueueSFull(QueueSNode* qs) {return (qs->back + 1) % (qs->size + 1) == qs->front;
}
2.9 检测队列是否为空
front 与 back 重合时则为空。
bool QueueSEmpty(QueueSNode* qs) {return qs->front == qs->back;
}
2.10 队列的销毁
void QueueSDestroy(QueueSNode* qs) {free(qs->a);free(qs);
}
2.11 顺序队列功能综合
QueueS.h:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>//循环队列(顺序存储结构)
typedef int QDataType;typedef struct QueueSNode {QDataType* a; //数组int front; //队头元素下标int back; //队尾元素下一个位置的下标int size; //数组长度
}QueueSNode;//初始化长度为k的顺序队列
QueueSNode* QueueSInit(int k);
//入队列
void QueueSPush(QueueSNode* qs,QDataType x);
//出队列
void QueueSPop(QueueSNode* qs);
//获取队头元素
QDataType QueueSFront(QueueSNode* qs);
//获取队尾元素
QDataType QueueSTail(QueueSNode* qs);
//获取有效元素个数
int QueueSCount(QueueSNode* qs);
//检测队列是否已满,已满返回true,否则返回fals
bool QueueSFull(QueueSNode* qs);
//检测队列是否为空,为空返回true,否则返回false
bool QueueSEmpty(QueueSNode* qs);
//销毁队列
void QueueSDestroy(QueueSNode* qs);
QueueS.c:
#include"QueueS.h"//初始化队列
QueueSNode* QueueSInit(int k) { //数组长度KQueueSNode* qs = (QueueSNode*)malloc(sizeof(QueueSNode));QDataType* tmp = (QDataType*)malloc(sizeof(QDataType) * (k + 1)); //这里其实是为数组开辟空间,注意是 k+1 if (qs == NULL) {perror("malloc fail!");return;}qs->a = tmp;qs->front = 0;qs->back = 0;qs->size = k;return qs;
}//入队列
void QueueSPush(QueueSNode* qs, QDataType x) {if (QueueSFull(qs)) { //判断队列是否为满的函数,下面会提到return;}qs->a[qs->back] = x; //直接插入qs->back = (qs->back + 1) % (qs->size + 1); //循环的体现,防止假溢出}//出队列
void QueueSPop(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return;}++qs->front;qs->front = (qs->front) % (qs->size + 1); //避免假溢出}//获取队头元素
QDataType QueueSFront(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return ;}return qs->a[qs->front]; //直接返回 front 位置的值
}//获取队尾元素
QDataType QueueSTail(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return;}if (qs->back == 0) {return qs->a[qs->size]; //back 在下标为0处,队尾就在元素就在最后 }else {return qs->a[qs->back - 1]; //其他情况 back-1 即可}
}//获取有效元素个数
int QueueSCount(QueueSNode* qs) {if (QueueSEmpty(qs)) { //判断队列是否为空的函数,下面会提到return;}return (qs->back - qs->front + (qs->size + 1)) % (qs->size + 1);
}//判断队列是否已满,已满返回true,否则返回false
bool QueueSFull(QueueSNode* qs) {return (qs->back + 1) % (qs->size + 1) == qs->front;
}//判断队列是否为空,为空返回true,否则返回false
bool QueueSEmpty(QueueSNode* qs) {return qs->front == qs->back;
}//队列的销毁
void QueueSDestroy(QueueSNode* qs) {free(qs->a);free(qs);
}
test.c:
#include"QueueS.h"int main(){QueueSNode* qs= QueueSInit(5);QueueSPush(qs, 1);QueueSPush(qs, 2);printf("%d \n", QueueSCount(qs));QueueSPush(qs, 3);QueueSPop(qs);QueueSPush(qs, 4);QueueSPush(qs, 5);QueueSPush(qs, 6);printf("%d \n", QueueSCount(qs));while (!(QueueSEmpty(qs))) {printf("%d ", QueueSFront(qs));QueueSPop(qs);}QueueSDestroy(qs);return 0;
}
3. 队列的实现(链式存储结构)
接下来就用链式结构实现队列,其实就是单链表,只不过在插入和删除操作上有固定的方向。
3.1 队列的结构定义
为了方便观察元素个数,直接在结构体中加入 size 计算元素个数。
typedef int QDataType;typedef struct QueueLNode
{QDataType val; //数据域struct QueueNode* next; //指针域
}QNode;typedef struct Queue
{QNode* head; //队列的头指针QNode* tail; //队列的尾指针int size; //元素个数
}Queue;
3.2 队列的初始化
void QueueInit(Queue* q) {assert(q);q->head = q->tail = NULL;q->size = 0;
}
3.3 插入数据(入队列)
void QueuePush(Queue* q, QDataType data) {assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {printf("malloc fail!");return;}newnode->val = data;newnode->next = NULL;if (q->tail == NULL) {q->tail = q->head = newnode;}else {q->tail->next = newnode;q->tail = newnode;}q->size++; //插入数据后,元素个数 +1
}
图解:
3.4 删除数据(出队列)
void QueuePop(Queue* q) {assert(q);assert(q->head);QNode* newhead = q->head->next;free(q->head);q->head = newhead;if (q->head == NULL) {q->tail = NULL;}q->size--; //删除数据后,元素个数 -1
}
图解:
3.5 获取队头元素
由于结构中定义了头指针,取队头元素会很方便。
QDataType QueueFront(Queue* q) {assert(q);assert(q->head);return q->head->val; //取队头指针的数据域的值
}
3.6 获取队尾元素
同样,可以直接返回队尾指针对应的值。
QDataType QueueBack(Queue* q) {assert(q);assert(q->tail);return q->tail->val;
}
3.7 获取有效元素个数
可以直接返回 szie。
int QueueSize(Queue* q) {assert(q);return q->size;
}
3.8 判断队列是否为空
bool QueueEmpty(Queue* q) {assert(q);return q->head == NULL;
}
3.9 队列的销毁
// 销毁队列
void QueueDestroy(Queue* q) {assert(q);QNode* cur = q->head;while (cur) {QNode* Next = cur->next;free(cur);cur = Next;}q->head = q->tail = NULL;q->size = 0;
}
3.10 链队列功能综合
QueueL.h:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int QDataType;
// 链式结构队列
typedef struct QueueLNode
{struct QueueNode* next;QDataType val;
}QNode;// 队列的结构
typedef struct Queue
{QNode* head;QNode* tail;int size;
}Queue;// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
QueueL.c:
#include"QueueL.h"//初始化队列
void QueueInit(Queue* q) {assert(q);q->head = q->tail = NULL;q->size = 0;
}//入队列
void QueuePush(Queue* q, QDataType data) {assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {printf("malloc fail!");return;}newnode->val = data;newnode->next = NULL;if (q->tail == NULL) {q->tail = q->head = newnode;}else {q->tail->next = newnode;q->tail = newnode;}q->size++; //插入数据后,元素个数 +1
}//出队列
void QueuePop(Queue* q) {assert(q);assert(q->head);QNode* newhead = q->head->next;free(q->head);q->head = newhead;if (q->head == NULL) {q->tail = NULL;}q->size--; //删除数据后,元素个数 -1
}//获取队头元素
QDataType QueueFront(Queue* q) {assert(q);assert(q->head);return q->head->val; //取队头指针的数据域的值
}//获取队尾元素
QDataType QueueBack(Queue* q) {assert(q);assert(q->tail);return q->tail->val;
}//获取有效元素个数
int QueueSize(Queue* q) {assert(q);return q->size;
}//检测链队列是否为空,为空返回true,否则返回false
bool QueueEmpty(Queue* q) {assert(q);return q->head == NULL;
}//销毁队列
void QueueDestroy(Queue* q) {assert(q);QNode* cur = q->head;while (cur) {QNode* Next = cur->next;free(cur);cur = Next;}q->head = q->tail = NULL;q->size = 0;
}
test.c:
#include"QueueL.h"int main(){Queue q;QueueInit(&q);// 队尾入队列 QueuePush(&q, 1);QueuePush(&q, 2);printf("%d \n", QueueFront(&q));QueuePop(&q);printf("%d \n", QueueFront(&q));printf("个数:%d \n", QueueSize(&q));printf("队尾:%d \n", QueueBack(&q));QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);while (!QueueEmpty(&q)) {printf("%d ", QueueFront(&q));QueuePop(&q);}QueueDestroy(&q);return 0;
}
4. 综合对比
链队列 | 顺序队列 | |
---|---|---|
存储结构 | 链式存储 | 顺序存储 |
内存分配 | 动态(按需动态开辟) | 静态(固定大小) |
空间利用率 | 高 (无浪费) | 需要多预留一个位置 |
内存开销 | 每个结点有额外指针 | 无额外指针,可能会有空间浪费 |
队列的同样需要根据实际情况来选择。简单来说,循环队列的 访问速度更快,链队列操作起来更加 灵活。