【面向小白】你见过这样讲解队列的吗?(阅此文可学会用纯C手撕一个队列)
目录
0.前言
1.什么是队列
2.选择什么结构实现队列
3.用C语言实现队列
3.1用什么可以封装代表一个队列
3.2队列接口的设计
3.3 队列的初始化
3.4 队列的销毁
3.5* 队列的状态分析
3.6 队列的插入
3.7 队列的删除
3.8 队列的大小(有效元素的数目)
3.9判断队列是否为空
3.10 获取队头数据
3.11 获取队尾数据
4.测试实现的队列
0.前言
4队列的实现 · onlookerzy123456qwq/data_structure_practice_primer - 码云 - 开源中国 (gitee.com)
https://gitee.com/onlookerzy123456qwq/data_structure_practice_primer/tree/master/4%E9%98%9F%E5%88%97%E7%9A%84%E5%AE%9E%E7%8E%B0本文所有代码都已上传至gitee,可以自取。
1.什么是队列
我们想象一个管道,在这个管道运行的时候,比如我们现在要输送石油从新疆到上海,那么这个管道当中,石油是从新疆端 入的,石油是从上海端 出的,并且石油只允许从新疆输入,石油只允许从上海端输出。同时在管道里的石油,肯定是先输入的石油更快到达上海,后面输入的石油是在先输入的石油到达之后,才到达上海(因为先输入的石油是堵在管道前面的,必须先进的先出之后,后进的才能出)。
这个管道就是一个队列。先后输入的石油就是队列里面的元素。

栈是只允许在一端进行插入/删除,而我们的队列与之相反,队列是只允许在一端进行插入,只能在相反的另一端进行删除。
同时队列也是只能先进先出的,后进后出的,先入队列的元素肯定是堵在后入队列的元素的前面,必须先入队列的数据先出队列之后,相对后入的队列才能出队列。
负责插入(入队列)的一端称为队尾,负责删除(出队列)的一端称之为队头。

2.选择什么结构实现队列
实现队列我们可供选择的有两种结构,一种是顺序表结构,一种是链式结构,在这个效率至上的时代,我们肯定是选择链式结构实现队列!因为队列是只允许在一端插入,另一端删除的,所以我们就需要 头插配尾删(此时顺序表/链表的头部是队尾,尾部是队头) 或者是 头删配尾插(此时此时顺序表/链表的头部是队头,尾部是队尾),而我们的顺序表天然在头部进行插入删除的效率是极低的,效率为O(N),因为需要挪动数据。所以链表结构便成为了不二选择。
我们以单链表的头head 作为队列的队头(出数据),以单链表的尾tail 作为队列的队尾(入数据):
单链表头删的效率是O(1),但是单链表尾插由于需要找尾,效率会降为O(N),这是这并没有太大的关系,因为我们在用链式结构实现队列的时候,可以一直记录tail尾的位置,即我们在封装Queue的时候,除了记录头节点head的位置,也记录住尾节点tail的位置,每次删除/插入操作的时候,适当更新tail,就不用找尾,此时我们对tail尾部的插入的效率就也是O(1)了。
3.用C语言实现队列
3.1用什么可以封装代表一个队列
我们选择的是一个链式结构作为基础实现队列,所以我们要保存一个单链表,如何代表一个单链表,其实我们只需要记录一个单链表的头head即可。同时为了方便我们进行尾插,我们还需要维护一个单链表的tail指针。
所以队列通过一个链表节点head和tail指针就可代表了。
所以我们定义一个存储队列数据的节点struct Node类,然后对封装的Queue类当中,封装两个节点指针head,tail即可。
//范式类型
typedef int QDataType;//节点
typedef struct QueueNode
{QDataType _data;struct QueueNode* _next;
}QueueNode;//队列
typedef struct Queue
{QueueNode* _head;//队头QueueNode* _tail;//队尾
}Queue;
3.2队列接口的设计
我们要修改的是一个队列Queue q对象,我们就不能做对Queue q的直接传值传参,因为这样的话,就是在调用的函数栈帧当中,拷贝一个相同的Queue tmp_q,并不是这个我们的q。

所以我们应该传入的是main函数当中Queue q对象的指针,即Queue* pq,你传入的是q对象的指针,这样pq指向的就是我main函数中的q实体了,在Func函数当中指针找到的也是main函数当中的q实体。
所以接口我们这样设计:
//队列相关接口
/*传入一级指针即可:传入(指向[Queue两个指针实体]的指针,可以修改Queue *head *tail实体)*/
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
3.3 队列的初始化
我们定义出来一个队列类的对象,那这个对象里面的_head和_tail一定是随机值野指针,如果不对之进行初始化,那后续我们对这个队列进行插入删除等操作的时候,灾难性的野指针问题就会暴露出来(非法访问)。
所以每次定义一个队列,我们都要对这个队列对象进行初始化:
void QueueInit(Queue* pq)
{//须传入队列实体assert(pq);pq->_head = NULL;pq->_tail = NULL;
}
3.4 队列的销毁
每次使用完队列,我们不能就不管了,因为堆区申请的空间,需要我们主动释放,在这个队列的实现当中,我们是在堆区里申请了一个一个的节点组成了一个链表,如果不释放的话,那么就会造成内存泄漏,这些堆区空间就会一直被“霸占”着,无法被再使用。
所以每次使用完一个队列,我们都要对之释放清理资源:
void QueueDestroy(Queue* pq)
{//须传入队列实体assert(pq);//依次释放所有的节点QueueNode* cur = pq->_head;while(cur){QueueNode* next = cur->_next;free(cur);cur = next;}//置空,防止野指针pq->_head = NULL;pq->_tail = NULL;
}
3.5* 队列的状态分析
我们分析一下队列在各个状态下,_head _tail成员的实际情况:
队列空的时候,head和tail都是NULL。
在通常情况下,队列_head指向队头的数据节点 ,负责删除更新(头删),_tail指向队尾的数据节点,负责插入更新(尾插)。
再紧接着分析一下队列的插入删除,对_head_tail成员的影响:
对队列插入就是在_tail的后面进行链接新创建的节点,并记录更新_tail为新节点。对队列的删除就是对_head所在位置的节点释放删除,并记录更新_head为原来_head的下一个节点的位置。
但是这样真的就行了吗?入队列,即尾插,真的只更新_tail就行了吗?出队列,即头删,真的只更新_head就行了吗?当然不行!!!
万一现在是空队列,_head和_tail都是NULL空,那你入队列,插入一个数据,此时_head和_tail都需要更新为新节点,不能只顾_tail。万一现在队列只有一个节点,那你删除之后,此时_head和_tail也都想需更新为NULL,不能只顾_head。
3.6 队列的插入
大致思路就是,创建新节点,然后链接到tail后面,并更新tail。
检查队列是合法的(用户传入的是一个有效队列指针);提前判断一开始队列就是空的特殊情况,此时除了_tail需要更新为新节点,_head也需要更新。
void QueuePush(Queue* pq,QDataType x)
{//须传入队列实体assert(pq);//创建新节点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));newnode->_next = NULL;newnode->_data = x;/*插入分两种情况情况一:直接在tail后面链接新节点然后更新tail即可情况二:队列为空,head==tail==NULL,此时需要更新head+tail为新节点指针*/if (pq->_head == NULL)//QueueEmpty(pq){pq->_head = newnode;pq->_tail = newnode;}else {pq->_tail->_next = newnode;pq->_tail = newnode;}
}
3.7 队列的删除
大致思路就是,把_head指向的队头的节点进行释放删除,然后更新_head为下一个节点的位置。
检查队列是合法的(用户传入的是一个有效队列指针);检查此时队列不为空,为空,即没有有效数据不能进行删除,需要报错;提前判断一开始队列就是只有一个节点的特殊情况,此时除了_tail需要更新为NULL,_head也需要更新,且更新为NULL。
void QueuePop(Queue* pq)
{//传入有效队列assert(pq);//有有效数据,即队列非空才可以删assert(!QueueEmpty(pq));//pq->_head != NULL/*分两种情况情况一:直接删除head节点,然后更新head到next第二个节点的指针情况二:现在只有一个节点,删除该节点后head变成next空节点NULL,此时tail也需更新为NULL,因为现在tail还指向着刚刚删除的节点的空间,出现野指针问题*/QueueNode* next = pq->_head->_next;free(pq->_head);pq->_head = next;if (pq->_head == NULL)//删除到空{pq->_tail = NULL;}
}
3.8 队列的大小(有效元素的数目)
获取当前队列里面一共有多少个有效数据,其实就是看从_head到_tail,这两个节点之间有多少个有效节点数据。思路就是依次从_head往后直至遍历到NULL,统计数量。
int QueueSize(Queue* pq)
{//须传入队列实体assert(pq);//链式结构通常不存节点数目//所以通常进行遍历统计int size = 0;QueueNode* cur = pq->_head;while (cur){cur = cur->_next;++size;}return size;
}
3.9判断队列是否为空
队列为空的时候_head为NULL。
bool QueueEmpty(Queue* pq)
{ //须传入队列实体assert(pq);//队头是空即可return pq->_head == NULL;
}
3.10 获取队头数据
获取队头的数据,即下一个要出队列的队头QueueFront,但是需要判断队列不为空,如果队列为空即没有有效数据的话,那就是需要报错的。
QDataType QueueFront(Queue* pq)
{//传入有效队列实体assert(pq);//必须有数据(队列非空)才能取assert(!QueueEmpty(pq));//Front就是队头head的元素数据return pq->_head->_data;
}
3.11 获取队尾数据
获取队尾的数据,即最近一次插入的队列的队尾QueueBack,但是需要判断队列不为空,如果队列为空即没有有效数据的话,那就是需要报错的。
QDataType QueueBack(Queue* pq)
{//传入有效队列实体assert(pq);//必须有数据(队列非空)才能取assert(!QueueEmpty(pq));//Back就是队尾tail的元素数据return pq->_tail->_data;
}
4.测试实现的队列
任何实现都离不开测试,这里笔者建议大家,不要写一堆之后再测试,应该最好是写一点测一点,写一个接口测试一下,这样可以帮助我们快速的定位问题bug的所在处。这并不是在浪费时间,而是在节省时间。
void QueueTest1()
{Queue q;QueueInit(&q);printf("Queue Size:%d\n", QueueSize(&q));printf("Queue Empty:%d\n", QueueEmpty(&q));QueueDestroy(&q);
}
void QueueTest2()
{Queue q;QueueInit(&q);QueuePush(&q,5);QueuePush(&q, 9);QueuePush(&q, 7);printf("Queue Size:%d\n", QueueSize(&q));printf("Queue Empty:%d\n", QueueEmpty(&q));QueueDestroy(&q);
}
void QueueTest3()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 0);QueuePush(&q, 0);QueuePush(&q, 8);QueuePush(&q, 6);printf("Queue Size:%d\n", QueueSize(&q));printf("Queue Empty:%d\n", QueueEmpty(&q));QueuePop(&q);printf("Queue Size:%d\n", QueueSize(&q));QueueDestroy(&q);
}
void QueueTest4()
{Queue q;QueueInit(&q);QueuePush(&q, 1);printf("Queue Back:%d ", QueueBack(&q));QueuePush(&q, 0);printf("Queue Back:%d ", QueueBack(&q));QueuePush(&q, 0);printf("Queue Back:%d ", QueueBack(&q));QueuePush(&q, 8);printf("Queue Back:%d ", QueueBack(&q));QueuePush(&q, 6);printf("Queue Back:%d ", QueueBack(&q));printf("\n");printf("Queue Size:%d\n", QueueSize(&q));printf("Queue Empty:%d\n", QueueEmpty(&q));//测试具体的遍历栈的过程//数据是先进先出while (!QueueEmpty(&q)){printf("Queue Front:%d Pop\n", QueueFront(&q));QueuePop(&q);}
}
int main()
{//QueueTest1();//QueueTest2();//QueueTest3();QueueTest4();return 0;
}相关文章:
【面向小白】你见过这样讲解队列的吗?(阅此文可学会用纯C手撕一个队列)
目录 0.前言 1.什么是队列 2.选择什么结构实现队列 3.用C语言实现队列 3.1用什么可以封装代表一个队列 3.2队列接口的设计 3.3 队列的初始化 3.4 队列的销毁 3.5* 队列的状态分析 3.6 队列的插入 3.7 队列的删除 3.8 队列的大小(有效元素的数目ÿ…...
[element plus] 对话框组件再封装使用 - vue
学习关键语句: 饿了么组件dialog组件使用 dialog组件二次封装 vue3中封住的组件使用update触发更新 vue3中封装组件使用v-model:属性值来传值 写在前面 这是我遇到的一个页面需求 , 其中一个对话框的内容是很常用的 , 所以我将它封装出来才写的一篇文章 现在给出如下需求: 封…...
Markdown基本语法简介
前言:当你在git平台创建一个仓库时,平台会自动创建一个README.md文件,并将它的内容展现在web端页面,方面其他读者查阅。README.md实则是一个适用Markdown语法的文本文件,从他的后缀md即可看出它是Markdown的缩写。在gi…...
分布式服务的接口幂等性如何设计
1.1 概述 所谓幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。 基于RESTful API的角度对部分常见类型请求的幂等性特点进行分析 举个例子: 假如你有个某多多 有个服务 服务提供一个接口,结果这个服务部署在…...
视频流截取保存到本地路径(打包jar包CMD运行)
需求:现在有一批https的监控视频流URL,需要对视频流进行每三秒截屏一次,并保存到本地路径,png格式,以当前时间命名。代码:import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.Fra…...
mysql索引失效的几种情况
失效的几种情况 1、select * from xxx 2、索引列上有计算 3、索引列上有函数 4、like左边包含‘%’ 5、使用or关键字 6、not in和not exists 7、order by 8、不满足最左匹配原则 给code、age和name这3个字段建好联合索引:idx_code_age_name。 该索引字段的顺…...
Windows下载安装Redis的详细步骤
目录 一、概述 1.redis的版本维护介绍 2.msi安装包和压缩包的优点和缺点 二、操作步骤 三、测试是否安装成功(查看版本) 四、获取资源 一、概述 1.redis的版本维护介绍 Redis的官网只提供Linux系统的下载。但是微软的技术团队长期开发和维护着这…...
【蓝桥杯每日一题】差分算法
🍎 博客主页:🌙披星戴月的贾维斯 🍎 欢迎关注:👍点赞🍃收藏🔥留言 🍇系列专栏:🌙 蓝桥杯 🌙我与杀戮之中绽放,亦如黎明的花…...
MyBatis Plus 数据库字段加密处理
目录1.场景介绍2.Maven依赖2.AESUtil.java 加解密工具类3.字段处理类4.修改 MyBatis Plus 查询4.1 修改表对应实体类4.2 修改加密字段对应属性4.3 修改 xml 使用 ResultMap4.4 修改 xml 中 el 表达式5.测试结果6.MyBatis Plus 缺陷补充:测试实例1 查询测试1.1 查询信…...
openpose在win下环境配置
1.下载OpenPose库 以下二选一进行下载源码 (1)git进行下载 打开GitHub Desktop或者Powershell git clone https://github.com/CMU-Perceptual-Computing-Lab/openpose cd openpose/ git submodule update --init --recursive --remote(2)在github上手动下载 由于下载环境问…...
【剑指offer-C++】JZ16:数值的整数次方
【剑指offer】JZ16:数值的整数次方题目描述解题思路题目描述 描述:实现函数 double Power(double base, int exponent),求base的exponent次方。 注意: 1.保证base和exponent不同时为0。 2.不得使用库函数,同时不需要…...
了解Axios及其运用方式
Axios简介 axios框架全称(ajax – I/O – system): 基于promise用于浏览器和node.js的http客户端,因此可以使用Promise API 一、axios是干啥的 说到axios我们就不得不说下Ajax。在旧浏览器页面在向服务器请求数据时,…...
【LeetCode】剑指 Offer(7)
目录 写在前面: 题目剑指 Offer 17. 打印从1到最大的n位数 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 题目:剑指 Offer 18. 删除链表的节…...
Python:try except 异常处理整理
目录 一、try except异常处理的语句格式 二、获取相关异常信息 (1)sys.exec_info() 三、traceback模块的常用方式 (1)traceback.print_tb(tb, limitNone, fileNone) 打印指定堆栈异常信息 (2)tracebac…...
Redis Lua脚本的详细介绍以及使用入门
Redis Lua脚本的详细介绍以及使用入门。 文章目录Redis Lua脚本的引入开源软件的可扩展性Redis的扩展性脚本Redis Lua脚本的基本使用通过EVAL命令执行Lua脚本通过脚本与Redis交互Java中调用Redis Lua脚本Java调用Lua脚本的方式Redis Lua脚本的使用建议脚本缓存脚本缓存稳定性脚…...
synchronized和ReentrantLock有什么区别呢?
第15讲 | synchronized和ReentrantLock有什么区别呢? 从今天开始,我们将进入 Java 并发学习阶段。软件并发已经成为现代软件开发的基础能力,而 Java 精心设计的高效并发机制,正是构建大规模应用的基础之一,所以考察并发…...
SVHN数据集下载及使用方法
街景门牌号数据集(SVHN),这是一个现实世界数据集,用于开发目标检测算法。它需要最少的数据预处理过程。它与 MNIST 数据集有些类似,但是有着更多的标注数据(超过 600,000 张图像)。这些数据是从…...
产业安全公开课:2023年DDoS攻击趋势研判与企业防护新思路
2023年,全球数字化正在加速发展,网络安全是数字化发展的重要保障。与此同时,网络威胁日益加剧。其中,DDoS攻击作为网络安全的主要威胁之一,呈现出连年增长的态势,给企业业务稳定带来巨大挑战。2月21日&…...
Docker 容器命令 和安装各种镜像环境
CentOS安装Docker 1.1.卸载(可选) 如果之前安装过旧版本的Docker,可以使用下面命令卸载: yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotat…...
【数据结构】顺序表的深度剖析
🌇个人主页:平凡的小苏 📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情 🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html 🚀数据结构专栏ÿ…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
