编程小白真香现场!20张图带你揭开「队列」的迷雾,让你一目了然

队列的概念

首先咱们联想一下链表,在单链表中,咱们只能对他的链表表尾进行插入,对链表的表头进行结点的删除,这样强限制性的链表,就是咱们所说的队列。编程

也就是说,队列(queue)是限定在表的一端进行插入,表的另外一端进行删除的数据结构。微信

以下图所示,假如你去买票排队,每一列队伍都有一个队尾和对头,先来的先买票,后来的后买,买好的就从对头出去,新来买票的就须要从队尾继续排队。数据结构

一般,称进数据的一端为 队尾,出数据的一端为 队头,数据元素进队列的过程称为 入队,出队列的过程称为 出队函数

ID:技术让梦想更伟大学习

做者:李肖遥优化

咱们能够总结以下:设计

队列是一个线性的数据结构,而且这个数据结构只容许在一端进行插入,另外一端进行删除,禁止直接访问除这两端之外的一切数据,且队列是一个先进先出的数据结构。3d

如上图,队列就像一个两端相通的水管,只容许一端插入,另外一端取出,取出的球就不在水管里面了,而先放入管中的球就会先从管中拿出。指针

队列存储结构的实现有如下两种方式:调试

(1)顺序队列:在顺序表的基础上实现的队列结构

(2)链队列:在链表的基础上实现的队列结构

二者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。

队列的结点设计与初始化

队列只有链式的设计方法,其自己分为多种队列,如顺序队列循环队列,还有衍生的优先队列等等,咱们以顺序队列的设计为例。

首先是队列的结点设计,咱们能够设计出两个结构体,一个结构体Node表示结点,其中包含有一个data域和next指针,如图所示:

其中data表示数据,其能够是简单的类型,也能够是复杂的结构体。

next指针表示,下一个的指针,其指向下一个结点,经过next指针将各个结点连接。

而后咱们再添加一个结构体,其包括了两个分别永远指向队列的队尾和队头的指针,看到这里是否是以为和栈很像?

咱们主要的操做只对这两个指针进行操做,如图所示:

其结构体设计的代码能够表示为:

对于初始化须要初始化两个类型,一个是初始化结点,一个是初始化队列。

咱们看到代码中的描述,初始化队列有些不一样,当初始化队列的时候,须要将头尾两个结点指向的内容通通置为空,表示是一个空队列,两个建立的函数代码能够表示为:

判断队列是否为空

这是一个既简单也很要紧的操做,判断队列是否为空直接就是判断队列头指针是不是空值便可,判断队列是否为空是比较经常使用的操做,切勿忘记。

其代码能够表示为:

或者直接利用返回值进行更简单的判断也能够,代码以下:

入队操做

入队操做变化以下图:

进行入队(push)操做的时候,一样的,咱们首先须要特判队列是否为空,若是队列为空的话,须要将头指针和尾指针一同指向第一个结点,代码以下

如图所示:

当若是队列不为空的时候,这时咱们只须要将尾结点向后移动,经过不断移动next指针指向新的结点构成队列便可。如图所示:

其代码能够表示为:

出队操做

出队操做变化以下图:

出队(pop)操做,是指在队列不为空的状况下进行的一个判断,固然咱们在此也必定要进行队列判空的操做,你懂的。

如图,若是队列只有一个元素了,也就是说头尾指针均指向了同一个结点,那么咱们直接将头尾两指针制空NULL,并释放这一个结点便可,以下图所示:

当队列含有以上个元素时,咱们须要将队列的头指针指向头指针当前指向的下一个元素,并释放掉当前元素便可,以下图所示

其代码能够表示为:

打印队列元素(遍历)

打印队列的所有元素能够帮助咱们调试,看到队列中具体的数据,在队列不为空的状况下,经过结点的next指向依次遍历并输出元素既可。

其代码能够表示为

遍历操做还有不少别的表示方法,好比说进行计算队列中含有多少元素,代码以下:

顺序队列的假溢出

什么是假溢出?咱们可能会有疑问,溢出还有假的!

这里咱们也须要考虑到顺序队列有什么缺点,对于顺序队列而言,其存在已经足够解决大多时候的设计问题了,可是其依旧存在一些缺陷和不足。

从上面的解析中咱们看到,入队和出队操做均是直接在其后面进行结点的连接和删除,这种操做会形成其使用空间不断向出队的那一边偏移,产生假溢出。

咱们来打打一个比方,先看看下面的图:

示例顺序队列

上图所示,有一个顺序队列,这个队列的大小为5,其已经包含了四个元素data1,data2,data3,data4。

接着,咱们对这个队列进行出队操做,出队2个元素,队列就变成了这个样子,以下图所示:

从图上看到彷佛没有什么问题,可是当咱们接着再进行入队操做,好比咱们入队2个元素,分别是data5和data6。

此时咱们已经发现问题了,尾指针移动到咱们能够进行队列操做的范围以外去了,有没有发现?

这种现象咱们称呼做为队列用的存储区尚未满,但队列却发生了溢出,咱们把这种现象称为假溢出。以下图所示:

出队产生假溢出

那么咱们有什么办法解决这个问题呢?这就要涉及到循环队列的性质了!

循环队列的概念

可能这个时候会产生一个疑问,咱们学习的队列不是使用链表实现的动态队列么?

没有空间的时候会开辟空间,这难道还会产生假溢出么?

的确,当进行动态建立队列的时候,也只不过是向后继续不断的申请内存空间;

即便前面出队操做释放掉了前面的空间,可是指针依旧会向后进行移动,直到达到系统预留给程序的内存上界被强行终止;

这对于极为频繁的队列操做和程序而言是致命的,这时候,就须要对咱们的队列进行优化,使用更为优秀的结构——循环队列

循环队列就是将队列存储空间的最后一个位置转而绕到第一个位置,造成逻辑上的环状空间,以此来供队列循环使用,以下图。

循环队列就是给定咱们队列的大小范围,在原有队列的基础上,只要队列的后方满了,就从这个队列的前面开始进行插入,以达到重复利用空间的效果;

因为循环队列的设计思惟更像一个环,所以常使用一个环图来表示,但咱们须要注意,实际上循环队列不是一个真正的环,它依旧是单线性的。

循环队列的结构设计

因为循环对列给定了数据范围的大小,因此不须要使用链式的动态建立方法了。

由于若是使用链式存储,会没法肯定什么时候再回到队头进行插入操做,因此咱们采用模拟的方法,如图所示:

其中,data表示一个数据域,int为类型,其能够修改成任意自定义的类型,好比说简单的char,float类型等等,也能够是复杂的结构体类型。

(1)maxsize表示循环队列的最大容纳量,其表示队列的所有可操做空间。

(2)rear表明尾指针,入队时移动。

(3)front表明头指针,出队时移动。

其代码能够表示为:

循环队列的初始化

循环队列的初始化核心就在于申请空间,而且将front指针和rear指针内容赋值为0,即指向第0个元素便可,这里要注意第 0个元素内容为空,以下图所示:

其代码能够表示为:

入队操做

入队操做同顺序队列的方法,直接将rear向后移动便可。

可是要注意判断,若是rear达到了队列的空间上线,将要从头继续开始移动。

这里推荐使用余数法,即不管如何求余都是在这片空间内进行操做,防止一次错误执行就直接总体崩溃,并且也相对而言更为简洁,不推荐使用if语句,这样显得比较累赘。

入队操做

注意进行加一移动位置操做的时候,不能直接q->rear++这样的操做,这样计算机判断优先级会产生让本身意想不到的后果。

此外这里还须要进行一次是否队列已满的判断,当咱们rear指针的下一个位置就是front的位置的时候,即改循环队列已满。

如图:

出队操做

若是顺序队列的出队操做,直接将front进行后移一位便可。

这里上面不少地方都提过了,有一个须要留意的地方,即队列是否为空,当队列为空的时候是没法进行出队操做的。

其代码能够表示为:

遍历操做

遍历操做须要借助一个临时变量储存位置front的位置信息,利用i逐步向后移动,直到i到达了rear的位置便可宣告遍历的结束。

关于队列的总结

请牢记这句话:队列是一个先进先出的数据结构。

今天队列基础就讲到这里,下一期,咱们再见!

另外若是你想更好的提高你的编程能力,好好学习C/C++编程知识的话!那么你很幸运~

C语言C++编程学习交流圈子,QQ群757874045点击进入】微信公众号:C语言编程学习基地

分享(源码、项目实战视频、项目笔记,基础入门教程)

欢迎转行和学习编程的伙伴,利用更多的资料学习成长比本身琢磨更快哦!

编程学习书籍

编程学习视频