0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

队列的概念

GReq_mcu168 来源:玩转单片机 作者:玩转单片机 2020-10-30 11:39 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

队列的概念

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

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

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

通常,称进数据的一端为队尾,出数据的一端为队头,数据元素进队列的过程称为入队,出队列的过程称为出队。

我们可以总结如下

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

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

队列存储结构的实现有以下两种方式

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

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

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

队列的结点设计与初始化

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

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

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

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

然后我们再添加一个结构体,其包括了两个分别永远指向队列的队尾和队头的指针,看到这里是不是觉得和栈很像?

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

其结构体设计的代码可以表示为:

//结点定义 typedefstructnode{ intdata; structnode*next; }node; //队列定义,队首指针和队尾指针 typedefstructqueue{ node*front;//头指针 node*rear;//尾指针 }queue;

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

我们看到代码中的描述,初始化队列有些不同,当初始化队列的时候,需要将头尾两个结点指向的内容统统置为空,表示是一个空队列,两个创建的函数代码可以表示为:

//初始化结点 node*init_node(){ node*n=(node*)malloc(sizeof(node)); if(n==NULL){//建立失败,退出 exit(0); } returnn; }//初始化队列 queue*init_queue(){ queue*q=(queue*)malloc(sizeof(queue)); if(q==NULL){//建立失败,退出 exit(0); } //头尾结点均赋值NULL q->front=NULL; q->rear=NULL; returnq; }

判断队列是否为空

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

其代码可以表示为:

//队列判空 intempty(queue*q){ if(q->front==NULL){ return1;//1--表示真,说明队列非空 }else{ return0;//0--表示假,说明队列为空 } }

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

intempty(queue*q){ returnq->front==NULL; }

入队操作

入队操作变化如下图:

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

front=n; rear=n;

如图所示:

唯一结点n

当如果队列不为空的时候,这时我们只需要将尾结点向后移动,通过不断移动next指针指向新的结点构成队列即可。如图所示:

其代码可以表示为:

//入队操作 voidpush(queue*q,intdata){ node*n=init_node(); n->data=data; n->next=NULL;//采用尾插入法 //if(q->rear==NULL){//使用此方法也可以 if(empty(q)){ q->front=n; q->rear=n; }else{ q->rear->next=n;//n成为当前尾结点的下一结点 q->rear=n;//让尾指针指向n } }

出队操作

出队操作变化如下图:

出队(pop)操作,是指在队列不为空的情况下进行的一个判断,当然我们在此也一定要进行队列判空的操作,你懂的。

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

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

其代码可以表示为:

//出队操作 voidpop(queue*q){ node*n=q->front; if(empty(q)){ return;//此时队列为空,直接返回函数结束 } if(q->front==q->rear){ q->front=NULL;//只有一个元素时直接将两端指向置为空即可 q->rear=NULL; free(n);//记得归还内存空间 }else{ q->front=q->front->next; free(n); } }

打印队列元素(遍历)

打印队列的全部元素可以帮助我们调试,看到队列中具体的数据,在队列不为空的情况下,通过结点的next指向依次遍历并输出元素既可。

其代码可以表示为

//打印队列元素 voidprint_queue(queue*q){ node*n=init_node(); n=q->front; if(empty(q)){ return;//此时队列为空,直接返回函数结束 } while(n!=NULL){ printf("%d ",n->data); n=n->next; } printf(" ");//记得换行 }

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

intcalac(queue*q){ node*n=init_node(); n=q->front; intcnt=0;//计数器设计 if(empty(q)){ return0;//此时队列为空,直接返回函数结束 } while(n!=NULL) { n=n->next; cnt++; } returncnt; }

顺序队列的假溢出

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

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

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

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

示例顺序队列

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

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

从图上看到似乎没有什么问题,但是当我们接着再进行入队操作,比如我们入队2个元素,分别是data5和data6。

此时我们已经发现问题了,尾指针移动到我们可以进行队列操作的范围之外去了,有没有发现?

这种现象我们称呼作为队列用的存储区还没有满,但队列却发生了溢出,我们把这种现象称为假溢出。如下图所示:

出队产生假溢出

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

循环队列的概念

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

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

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

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

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

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

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

由于循环队列的设计思维更像一个环,因此常使用一个环图来表示,但我们需要注意,实际上循环队列不是一个真正的环,它依旧是单线性的。

循环队列的结构设计

由于循环对列给定了数据范围的大小,所以不需要使用链式的动态创建方法了。

因为如果使用链式存储,会无法确定何时再回到队头进行插入操作,所以我们采用模拟的方法,如图所示:

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

maxsize表示循环队列的最大容纳量,其表示队列的全部可操作空间。

rear代表尾指针,入队时移动。

front代表头指针,出队时移动。

其代码可以表示为:

#definemaxsize10//表示循环队列的最大容量 //循环队列的结构设计 typedefstructcir_queue{ intdata[maxsize]; intrear; intfront; }cir_queue;

循环队列的初始化

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

其代码可以表示为:

//初始化 cir_queue*init(){ cir_queue*q=(cir_queue*)malloc(sizeof(cir_queue)); if(q==NULL){ exit(0);//申请内存失败,退出程序 } q->front=0; q->rear=0; returnq; }

入队操作

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

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

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

入队操作

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

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

如图:

队列已满

其代码可以表示为:

//入队操作push voidpush(cir_queue*q,intdata){ if((q->rear+1)%maxsize==q->front){ printf("溢出,无法入队 "); return; }else{ q->rear=(q->rear+1)%maxsize; q->data[q->rear]=data; } }

出队操作

如果顺序队列的出队操作,直接将front进行后移一位即可。

这里上面很多地方都提过了,有一个需要留意的地方,即队列是否为空,当队列为空的时候是无法进行出队操作的。

出队操作

其代码可以表示为:

//出队操作pop voidpop(cir_queue*q){ if(q->rear==q->front){ printf("队列为空,无法出队 "); return; }else{ q->data[q->front]=0; q->front=(q->front+1)%maxsize; } }

遍历操作

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

//遍历队列 voidprint(cir_queue*q){ inti=q->front; while(i!=q->rear){ i=(i+1)%maxsize; printf("%d ",q->data[i]); } printf(" ");//记得换行 }

关于队列的总结

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

责任编辑:lq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据结构
    +关注

    关注

    3

    文章

    573

    浏览量

    41359
  • 队列
    +关注

    关注

    1

    文章

    46

    浏览量

    11192
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

    10999

原文标题:真香!20张图揭开「队列」的迷雾,一目了然

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    优先级队列介绍

    队列(Queue)的知识点:「概念」:队列是一种先进先出(FIFO)的数据结构,类似于排队的概念。「基本操作」:enqueue(item): 将元素添加到
    发表于 11-26 07:56

    RTOS 必学概念:任务、信号量、队列一次搞懂

    如果你刚接触RTOS(实时操作系统),很可能会有这样的困惑:“RTOS和裸机程序到底有什么区别?”“任务是线程吗?为什么要分任务?”“信号量和互斥锁有什么区别,不都是同步手段吗?”“队列是不是就是一
    的头像 发表于 11-17 10:53 173次阅读
    RTOS 必学<b class='flag-5'>概念</b>:任务、信号量、<b class='flag-5'>队列</b>一次搞懂

    基于环形队列的UART收发回显实验

    在实际项目开发中,由于有些串口不具备FIFO(如SCI1和SCI2)或FIFO的buffer比较小,这可能会在数据处理速度小于数据接收速度的时候,导致数据的丢失。因此我们可以设计一个队列来避免这一
    的头像 发表于 10-27 13:51 1685次阅读
    基于环形<b class='flag-5'>队列</b>的UART收发回显实验

    NVMe高速传输之摆脱XDMA设计41:队列管理功能验证与分析5

    本文主要交流NVMe设计思路,在本博客已给出相关博文约四十篇,希望对初学者有用。注意这里只是抛砖引玉,切莫认为参考这就可以完成商用IP设计。 4) 删除队列边界测试结果 测试步骤 7 关键信号波形
    发表于 10-23 16:24

    NVMe高速传输之摆脱XDMA设计40:队列管理功能验证与分析4

    本文主要交流NVMe设计思路,在本博客已给出相关博文几十篇,希望对初学者有用。注意这里只是抛砖引玉,切莫认为参考这就可以完成商用IP设计。有需求者请先看下面B站联系后再讨论。 (3) 删除队列功能
    发表于 10-22 10:14

    NVMe高速传输之摆脱XDMA设计38:队列管理功能验证与分析2

    波形如图1 所示。 创建深度为 1024 的 I/O 提交队列, 由于支持的最大队列深度为 1023, 所以创建返回状态 cr_status 值为 4, 表示创建深度错误, 仿真行为符合设计预期
    发表于 10-15 08:14

    NVMe高速传输之摆脱XDMA设计37:队列管理功能验证与分析1

    本文主要交流设计思路,在本博客已给出相关博文九十多篇,希望对初学者有用。注意这里只是抛砖引玉,切莫认为参考这就可以完成商用IP设计。若有产品或项目需求,请看B站视频后联系 队列管理功能主要包含创建
    发表于 10-13 11:17

    NVMe IP高速传输却不依赖XDMA设计之九:队列管理模块(上)

    这是采用PCIe设计NVMe,并非调用XDMA方式,后者在PCIe4.0时不大方便,故团队直接采用PCIe设计,结合UVM验证加快设计速度。 队列管理模块采用队列的存储与控制分离的设计结构。
    的头像 发表于 08-04 09:53 585次阅读
    NVMe IP高速传输却不依赖XDMA设计之九:<b class='flag-5'>队列</b>管理模块(上)

    NVMe高速传输之摆脱XDMA设计十:队列管理模块设计(下)

    ​续上,队列管理模块采用队列的存储与控制分离的设计结构,如图1所示为队列管理模块的结构框图。 ​编辑 图1 队列管理结构 由于提交队列管理单
    发表于 07-30 16:27

    NVMe高速传输之摆脱XDMA设计九:队列管理模块设计(上)

    设计,结合UVM验证加快设计速度。队列管理模块采用队列的存储与控制分离的设计结构,如图1所示为队列管理模块的结构框图。 图1队列管理模块结构图对于提交
    发表于 07-27 17:41

    RabbitMQ消息队列解决方案

    在现代分布式系统架构中,消息队列作为核心组件,承担着系统解耦、异步处理、流量削峰等重要职责。RabbitMQ作为一款成熟的消息队列中间件,以其高可用性、高可靠性和丰富的特性,成为众多企业的首选方案。本文将从运维工程师的角度,详细阐述RabbitMQ从单机部署到集群搭建的完
    的头像 发表于 07-08 15:55 427次阅读

    RDMA简介5之RoCE V2队列分析

    在RoCE v2协议中,RoCE v2队列是数据传输的最底层控制机制,其由工作队列(WQ)和完成队列(CQ)共同组成。其中工作队列采用双向通道设计,包含用于存储即将发送数据的发送
    发表于 06-05 17:28

    QDMA Subsystem for PCI Express v5.0产品指南

    AMD QDMA Subsystem for PCI Express( PCIe )旨在利用多队列概念实现高性能 DMA,以搭配 PCI Express Integrated Block 一起
    的头像 发表于 05-13 09:21 700次阅读
    QDMA Subsystem for PCI Express v5.0产品指南

    NVME控制器之队列管理模块

    队列管理模块是整个NVMe Host控制器的核心模块,该模块实现了提交队列与完成队列的管理,多队列请求的仲裁判决等功能。队列管理模块中含有数
    发表于 05-03 20:19

    NVME控制器之队列管理模块

    队列管理模块是整个NVMe Host控制器的核心模块,该模块实现了提交队列与完成队列的管理,多队列请求的仲裁判决等功能。队列管理模块中含有数
    的头像 发表于 05-03 15:32 420次阅读
    NVME控制器之<b class='flag-5'>队列</b>管理模块