当前位置: 首页 > news >正文

栈和队列——队列

栈和队列——队列

  • 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. 综合对比

链队列顺序队列
存储结构链式存储顺序存储
内存分配动态(按需动态开辟)静态(固定大小)
空间利用率高 (无浪费)需要多预留一个位置
内存开销每个结点有额外指针无额外指针,可能会有空间浪费

  队列的同样需要根据实际情况来选择。简单来说,循环队列的 访问速度更快,链队列操作起来更加 灵活

http://www.dtcms.com/a/275099.html

相关文章:

  • 计算机基础:内存模型
  • mysql 散记:innodb引擎和memory引擎对比 sql语句少用函数 事务与长事务
  • 光伏反内卷,股价大涨
  • 电子电气架构 --- 电动汽车的主旋律(48V区域架构)
  • 【2025/07/11】GitHub 今日热门项目
  • Python 第三方库的安装与卸载全指南
  • vue2和vue3响应式原理浅析--应付面试本人是懒得记
  • PyTorch中的torch.argmax()和torch.max()区别
  • 视觉SLAM学习笔记:g2o位姿优化实战
  • doker和网站部署
  • Matplotlib-多图布局与网格显示
  • [Reverse1] Tales of the Arrow
  • P1886 滑动窗口 /【模板】单调队列
  • 代码随想录|图论|10水流问题
  • Word表格默认格式修改成三线表,一劳永逸,提高生产力!
  • Sigma-Aldrich细胞培养实验方案 | 悬浮细胞系的传代培养
  • 【真实案例】CATCOM-100实战:铁路积水监测与智能预警
  • Wend看源码-DeerFlow(基于LangGraph 的DeepResearch框架)
  • [SL] Brutus Linux登入紀錄分析+MITRE ATTCK+linper.sh本地权限提升工具
  • 面向构件的编程(COP)深度解析:构建模块化系统的工程范式
  • Debian:从GNOME切换到Xfce
  • 二叉树的层次遍历(BFS)
  • ## SQLITE:多表(子母表)联合查询【DA_Project笔记】
  • 032_super关键字与父类访问
  • CSP-J/S 参赛选手注册报名流程
  • 如何应对风险和不确定性
  • 还在靠防火墙硬抗?网络安全需要从“单点防御“转向“系统化防护“!
  • AGV穿梭不“迷路”CCLinkIE转Modbus TCP的衔接技巧
  • 【AI大模型】超越RAG的搜索革命!分层框架让AI像专家团队一样深度思考
  • 三轴云台之三维重建算法篇