【数据结构】队列——超详解!!!(包含队列的实现)

【数据结构】队列——超详解!!!(包含队列的实现)

【数据结构】队列——超详解!!!(包含队列的实现)

前言

往期我们的学习中讲到了顺序表、链表以及栈
它们可以帮我们解决很多问题,而类似的数据结构还有很多
今天,我们就来聊聊——队列

一、队列是什么?

1. 队列的定义

队列:
特殊的线性表只允许在一端进行插入数据操作,在另一端进行删除数据操作的一种数据结构
队列具有先进先出FIFO(First In First Out) 的原则

2. 队尾&&队头

进行插入操作的一端称为队尾
进行删除操作的一端称为队头
在这里插入图片描述

3.类比

就像我们在餐厅吃饭时,若饭店满人了,也许就会开始排队摇号,有1号、2号、3号…等等
当有座位空出来时,就让序号前的人先去;当又有人来时,就在队尾加一个序列
而队列也是如此,只允许在一端进行插入数据操作,在另一端进行删除数据操作的一种数据结构
当要删除数据时,就让队头的数据(先来的数据)先删除;当要插入数据时,就在队尾插入数据

二、队列的实现

那么,我们该如何来实现队列呢?

1. 用什么来实现?

想要实现队列,首先要想清楚要用什么实现队列
之前,我们依次实现了顺序表、链表以及栈,他们都是用数组或链表来实现的

队列也可以数组和链表的结构实现
但是之前我们讲到了在数组上头删(出队列)很麻烦,后续的每一个元素都要往前挪一位
如果使用数组的结构来实现队列,出队列就会在数组头上出数据,效率会很低
综上所诉,使用链表的结构来实现队列更优一些

2. 实现思路

由于队列与单链表类似,这里可以拿单链表的实现来做参考

  • 第1步
与单链表同理,队列中的每一个节点有以下两成员:
1
. 指向下一个节点的指针
2
. 节点中存储的数据
  • 第2步

但是,我们需要记录下队列的总元素个数、头节点、尾节点这三个重要信息
于是乎,我们可以单独创建一个结构体来包含这些信息,将队列的总元素个数、头节点、尾节点包含在内

  • 第3步

最后,就是一一实现我们队列的增、删、查、改等操作了

3. 代码实现

本文以创建一个 int 类型的队列为例

(1)创建头文件&源文件

之前在讲解扫雷游戏中我就提到:
在写复杂程序时要养成写多个头文件&源文件的好习惯,这样条理就很清晰也不会乱

详见【C语言】扫雷游戏详解(包含递归,变色,记录时间等)

在这里插入图片描述

如图:
创建了一个 “ Queue.h "头文件
用于存放用来放函数的声明和一些库函数的头文件

创建了一个 “ Queue.c "源文件
用于用来放函数的定义(队列的主体)

还有一个 ” Test.c "源文件
用于测试实现的队列的运行效果

(2)定义队列(定义)

首先我们要定义一个队列
这里之前讲过,要先创建一个结构体来表示每个节点
再单独创建一个结构体来包含这些信息,将队列的总元素个数、头节点、尾节点包含在内

这样方便我们对队列的实现

代码演示:(内有注释)
(在头文件“ Queue.h "中写)

//重定义,方便修改类型typedefint QDataType;//表示每个节点的类型typedefstructQListNode{ QDataType data;//节点中存储的数据structQListNode* next;//指向下一个节点的指针}QNode;// 队列的结构 typedefstructQueue{ QNode* front;//头指针 QNode* tail;//尾指针int size;//总元素个数}Queue;
在定义队列的代码中,有两个需要注意的点:本文是以 int 类型为例,但如果以后要将队列中的元素类型修改成 char 类型或是其他类型一个一个修改就很麻烦
所以我们重定义int类型为QDataType,并将接下来代码中的int类型全部写成QDataType
这是为了方便我们以后对类型进行修改,仅需将int 改为其他类型即可
在定义队列的同时重定义队列的变量名为 QNode方便以后使用

(3)队列的初始化(初始化)

定义完队列后,肯定要对队列进行初始化,内容全部置 0 / NULL

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 初始化队列 voidQueueInit(Queue* q);

“ Queue.c "源文件中写到:

// 初始化队列 voidQueueInit(Queue* q){assert(q);//断言空指针 q->front =NULL; q->tail =NULL; q->size =0;//全部初始化置 0 / NULL}
在写实现代码中,有一个很重要的点:
当我们函数在进行传参时,可能会传入空指针,而我们知道空指针是不能进行解引用的
故为了我们的代码更加健壮,可以加入assert 断言来判断是否符合条件,在之后的代码中也都有

关于更加详细的assert 断言介绍可参见下文:
【C语言】带你层层深入指针——指针详解3(野指针、assert等)

(4)队列的销毁(销毁)

在我们的程序运行完毕后,当然要对队列进行销毁,以免占用内存
这里我们直接遍历链表,一一释放空间销毁就行啦

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 销毁队列 voidQueueDestroy(Queue* q);

“ Queue.c "源文件中写到:

// 销毁队列 voidQueueDestroy(Queue* q){assert(q);//断言空指针 QNode* cur = q->front;//遍历链表,一一释放空间销毁while(cur){ QNode* next = cur->next;free(cur); cur = next;} q->front = q->tail =NULL; q->size =0;//全部初始化置 0 / NULL}

(5)队尾入队列(尾插)

  • 怎么插入?
在入队列时,由于队列只允许在一端进行插入数据操作,在另一端进行删除数据
所以我们在插入时进行尾插数据
而在删除时进行头删数据
  • 怎么开辟空间?
由于这里是链表的结构,故每一次插入时开辟一个节点的空间就行了
  • 注意!!!
注意:当队列中没有节点时
此时插入的节点既是头节点,又是尾节点
所以头尾节点都要赋值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 队尾入队列 voidQueuePush(Queue* q, QDataType data);

“ Queue.c "源文件中写到:

// 队尾入队列 voidQueuePush(Queue* q, QDataType data){assert(q);//断言空指针 QNode* tmp =(QNode*)malloc(sizeof(QNode));//直接开辟一个节点的空间if(tmp ==NULL)//加一个 if语句 防止增容失败{perror("malloc");return;}//没有问题后就赋值 tmp->data = data; tmp->next =NULL;//注意:当队列中没有节点时//此时插入的节点既是头节点,又是尾节点if(q->size ==0){ q->front = tmp; q->tail = tmp;}else{ q->tail->next = tmp; q->tail = tmp;} q->size++;}

(6)队头出队列 (头删)

  • 怎么删除?
在出队列时,由于队列只允许在一端进行插入数据操作,在另一端进行删除数据
刚刚我们在队尾进行插入数据
而现在删除数据时就是用头删数据
  • 注意!!!
注意:当队列中只有一个节点时
此时头尾节点相等,都要进行删除
所以将头节点和尾节点都释放

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 队头出队列 voidQueuePop(Queue* q);

“ Queue.c "源文件中写到:

// 队头出队列 voidQueuePop(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空//注意:当队列中只有一个节点时,头尾节点相等//此时将头节点和为尾节点都释放if(q->size ==1){free(q->front); q->front =NULL; q->tail =NULL;}else{ QNode* del = q->front; q->front = q->front->next;free(del);} q->size--;}

(7)获取队列头部元素

这个很简单,直接用下标进行访问数据
再返回所对应的值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 获取队列头部元素  QDataType QueueFront(Queue* q);

“ Queue.c "源文件中写到:

// 获取队列头部元素  QDataType QueueFront(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空return q->front->data;}

(8)获取队列尾部元素

这个很简单,直接用下标进行访问数据
再返回所对应的值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 获取队列队尾元素  QDataType QueueBack(Queue* q);

“ Queue.c "源文件中写到:

// 获取队列队尾元素  QDataType QueueBack(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空return q->tail->data;}

(9)获取队列中有效元素个数

这个很简单
直接返回所对应的值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 获取队列中有效元素个数 intQueueSize(Queue* q);

“ Queue.c "源文件中写到:

// 获取队列中有效元素个数 intQueueSize(Queue* q){assert(q);//断言空指针return q->size;}

(10)检测队列是否为空

这个很简单
如果队列为空返回非零结果
如果不为空返回0

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

“ Queue.h "头文件中写到:

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 intStackEmpty(Stack* ps);

“ Queue.c "源文件中写到:

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 intQueueEmpty(Queue* q){assert(q);//断言空指针return q->size ==0;}

三、完整代码实现

1. Queue.h

用于存放用来放函数的声明和一些库函数的头文件

#pragmaonce#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<stdlib.h>#include<stdbool.h>#include<assert.h>#include<sperror.h>//重定义,方便修改类型typedefint QDataType;//表示每个节点的类型typedefstructQListNode{ QDataType data;//节点中存储的数据structQListNode* next;//指向下一个节点的指针}QNode;// 队列的结构 typedefstructQueue{ QNode* front;//头指针 QNode* tail;//尾指针int size;//总元素个数}Queue;// 初始化队列 voidQueueInit(Queue* q);// 队尾入队列 voidQueuePush(Queue* q, QDataType data);// 队头出队列 voidQueuePop(Queue* q);// 获取队列头部元素  QDataType QueueFront(Queue* q);// 获取队列队尾元素  QDataType QueueBack(Queue* q);// 获取队列中有效元素个数 intQueueSize(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 intQueueEmpty(Queue* q);// 销毁队列 voidQueueDestroy(Queue* q);

2. Queue.c

用于用来放函数的定义(队列的主体)

#include"Queue .h"// 初始化队列 voidQueueInit(Queue* q){assert(q);//断言空指针 q->front =NULL; q->tail =NULL; q->size =0;//全部初始化置 0 / NULL}// 队尾入队列 voidQueuePush(Queue* q, QDataType data){assert(q);//断言空指针 QNode* tmp =(QNode*)malloc(sizeof(QNode));//直接开辟一个节点的空间if(tmp ==NULL)//加一个 if语句 防止增容失败{perror("malloc");return;}//没有问题后就赋值 tmp->data = data; tmp->next =NULL;//注意:当队列中没有节点时//此时插入的节点既是头节点,又是尾节点if(q->size ==0){ q->front = tmp; q->tail = tmp;}else{ q->tail->next = tmp; q->tail = tmp;} q->size++;}// 队头出队列 voidQueuePop(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空//注意:当队列中只有一个节点时,头尾节点相等//此时将头节点和尾节点都释放if(q->size ==1){free(q->front); q->front =NULL; q->tail =NULL;}else{ QNode* del = q->front; q->front = q->front->next;free(del);} q->size--;}// 获取队列头部元素  QDataType QueueFront(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空return q->front->data;}// 获取队列队尾元素  QDataType QueueBack(Queue* q){assert(q);assert(q->size >0);//断言空指针//断言顺序表不能为空return q->tail->data;}// 获取队列中有效元素个数 intQueueSize(Queue* q){assert(q);//断言空指针return q->size;}// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 intQueueEmpty(Queue* q){assert(q);//断言空指针return q->size ==0;}// 销毁队列 voidQueueDestroy(Queue* q){assert(q);//断言空指针 QNode* cur = q->front;//遍历链表,一一释放空间销毁while(cur){ QNode* next = cur->next;free(cur); cur = next;} q->front = q->tail =NULL; q->size =0;//全部初始化置 0 / NULL}

3. Test.c

用于测试实现的队列的运行效果
(这里是小编在写代码时写的测试用例)
(大家在写的时候也要多多测试哦)

#include"Queue .h"intmain(){ Queue Q;QueueInit(&Q);QueuePush(&Q,1);QueuePush(&Q,2);QueuePush(&Q,3);QueuePush(&Q,4);QueuePush(&Q,5);QueuePush(&Q,6);/*QueuePop(&Q); int ret1 = QueueFront(&Q); QueuePop(&Q); int ret2 = QueueFront(&Q); QueuePop(&Q); int ret3 = QueueFront(&Q); printf("%d %d %d\n\n", ret1, ret2, ret3);*/while(QueueEmpty(&Q)){printf("%d ",QueueFront(&Q));QueuePop(&Q);}printf("\n\n");QueueDestroy(&Q);return0;}

结语

本期资料来自于:

在这里插入图片描述

https://legacy.cplusplus.com/

OK,本期的队列详解到这里就结束了

若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持

本文有若有不足之处,希望各位兄弟们能给出宝贵的意见。谢谢大家!!!

新人,本期制作不易希望各位兄弟们能动动小手,三连走一走!!!

支持一下(三连必回QwQ)

Read more

世界职业院校技能大赛-人工智能

世界职业院校技能大赛-人工智能

人工智能赛道:技术创新 + 产业落地双驱动 赛道定位 2025 年大赛新增核心赛道,首次实现 “普通高中 - 中职 - 高职 - 本科” 全学段覆盖,以 “真问题、真场景、真技能” 为考核核心,重点检验选手 “AI 算法开发、智能系统集成、跨学科协作、产业落地” 四大能力,旨在推动 AI 技术从 “实验室” 走向 “实体经济”,赛道参赛队伍较上一届增长 120%,竞争尤为激烈。 核心技术方向 * 算法开发与模型优化:基于 TensorFlow、PyTorch 框架设计深度学习模型,覆盖图像分类、目标检测、时序预测;某合作院校团队针对工业设备故障问题,开发 “轴承故障预测模型”,通过优化卷积神经网络结构,预测准确率达

By Ne0inhk
AI入门系列:AI新手必看:人工智能发展历程与现状分析

AI入门系列:AI新手必看:人工智能发展历程与现状分析

写在前面:为什么AI发展历史很重要? 记得刚开始学习AI的时候,我总觉得历史这种东西很枯燥,不如直接学习最新的技术来得实在。但后来我发现,了解AI的发展历程,就像了解一个人的成长经历一样,能帮助我们更好地理解现在的AI是如何走到今天的,也能帮助我们预测未来可能的发展方向。 有一次,我和一位从事AI研究多年的教授聊天,他告诉我:"现在的学生总想直接学习深度学习,但如果不了解符号主义AI的兴衰,就无法理解为什么深度学习会成功,也无法预见它可能面临的挑战。"这句话让我深受启发。 所以,在这篇文章中,我想和大家一起回顾一下AI的发展历程,不是为了考试背诵那些枯燥的年代和事件,而是为了让我们能够站在历史的高度,更好地理解现在的AI技术,以及它在我们生活中的应用。 人工智能的诞生:一个充满想象力的开始 说起AI的诞生,我们不得不提到1956年的达特茅斯会议。这次会议被公认为人工智能学科的诞生标志。 想象一下那个场景:一群来自不同领域的顶尖科学家,包括约翰·麦卡锡、马文·明斯基、克劳德·香农等,聚集在一起,讨论着一个看似疯狂的问题:"机器能思考吗?"他们相信,只要给机器输入足够多的规则

By Ne0inhk
人工智能:自然语言处理在金融领域的应用与实战

人工智能:自然语言处理在金融领域的应用与实战

人工智能:自然语言处理在金融领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在金融领域的应用场景和重要性 💡 掌握金融领域NLP应用的核心技术(如文本分类、情感分析、风险评估) 💡 学会使用前沿模型(如BERT、GPT-3)进行金融文本分析 💡 理解金融领域的特殊挑战(如金融术语、数据噪声、实时性要求高) 💡 通过实战项目,开发一个金融风险评估应用 重点内容 * 金融领域NLP应用的主要场景 * 核心技术(文本分类、情感分析、风险评估) * 前沿模型(BERT、GPT-3)在金融领域的使用 * 金融领域的特殊挑战 * 实战项目:金融风险评估应用开发 一、金融领域NLP应用的主要场景 1.1 文本分类 1.1.1 文本分类的基本概念 文本分类是对金融文本进行分类的过程。在金融领域,文本分类的主要应用场景包括: * 新闻分类:对金融新闻进行分类(如“股票新闻”、“债券新闻”

By Ne0inhk
【保姆级】无需公网 IP!Windows 本地一键部署 OpenClaw,10 分钟打造你的飞书 AI 数字员工

【保姆级】无需公网 IP!Windows 本地一键部署 OpenClaw,10 分钟打造你的飞书 AI 数字员工

目录 写在前面 OpenClaw 是什么? 蓝耘平台是什么?与 OpenClaw 的关系 步骤一:极速安装,一行命令搞定环境 步骤二:启动向导,初始化配置参数 步骤 三:注入灵魂,获取蓝耘MaaS API Key 步骤四:打通渠道,搭建飞书长连接桥梁 步骤五:引擎点火,启动核心网关服务 步骤六:仪表盘检阅,后台状态可视化 步骤七:实战演练,验证智能交互效果 快速排错提示 写在末尾 写在前面 本文面向:想在 Windows 本地(PowerShell)一键部署 OpenClaw,使用蓝耘MaaS作为大模型,并通过飞书长连接模式实现 AI 机器人的用户。 内容涵盖:从零开始安装配置、对接飞书机器人、验证与排错的完整流程,

By Ne0inhk