【数据结构】【线性表】一文讲完队列(附C语言源码)

队列

        • 队列的基本概念
          • 基本术语
          • 基本操作
        • 队列的顺序实现
          • 顺序队列结构体的创建
          • 顺序队列的初始化
          • 顺序队列入队
          • 顺序队列出队
          • 顺序队列存在的问题分析
          • 循环队列
          • 代码汇总
        • 队列的链式实现
          • 链式队列的创建
          • 链式队列初始化-不带头结点
          • 链式队列入队-不带头节点
          • 链式队列出队-不带头结点
          • 带头结点的链式队列各个基本操作源码

队列的基本概念

今天介绍一下线性表的另一个类型,队列。队列和栈类似,都是在操作规则上有一定要求的线性表:

  • 栈是一个只允许在一端进行插入或者删除操作的线性表;
  • 队列是一个只允许在一端插入,另一端删除的线性表。

和栈不同的是,栈的插入或删除规定在同一端进行,但队列将插入和删除分开在了两端进行。我们可以将其理解成排队打饭,先去排队的人先打到饭,自然也是先离开,因此队列遵循的是先进先出的原则。队列逻辑结构示意图如图:
![[Pasted image 20241122133855.png]]

基本术语
  • 队头:等效于排队的队头,是第一个出去的即删除端;
  • 队尾:等效于排队的队尾,是加入队伍的一端即插入端;
  • 队头元素:队伍的第一个元素;
  • 队尾元素:队伍的最后一个元素;
  • 空队列:没有一个元素的队列;
基本操作
  • 初始化队列:创建一个空队列Q
  • 销毁队列:销毁队列,释放队列Q所占用的空间
  • 入队:队列Q没有满的情况下,加入新的元素到队尾
  • 出队:队列Q没有空的情况下,删除队头元素
  • 读队头元素:队列Q没有空的情况下,获取第一个元素的数据
队列的顺序实现

顺序队列是以类似顺序表的方式实现队列,即队列各个元素的存储空间连续且顺序,其结构体的创建也与顺序表类似。

顺序队列结构体的创建

创建顺序队列有两个主要的点:一个是队列空间的创建;另一个是队列的队头与队尾指针的构建。
![[顺序队列示意图.png]]

其相关程序如下:

#define MaxSize 10//队列最大长度
typedef struct{ElemType data[MaxSize];//数组存放队列元素int front,rear;//队头指针和队尾指针
}SqQueue;//顺序队列
顺序队列的初始化

初始化主要是清除空间的残余数据,并将front和rear指针分别指向队头和队尾。具体程序如下:

void InitQueue(SqQueue &Q){//初始化队内各个元素数据for(int i=0;i<MaxSize;i++)Q.data[i]=0;Q.front=Q.rear=0;//初始化队头和队尾指针
}
顺序队列入队

入队的逻辑是在队尾插入一个新元素,然后将指针+1即可,示意图:
![[顺序队列入队示意图.png]]

我们可以看到,这里的rear一般指向空元素内存,具体程序如下:

bool EnQueue(SqQueue &Q,ElemType e){if(队列判满)return false;//队列已满,入队失败Q.datd[Q.rear]=e;//队列未满,元素入队//rear指针加一return true;//入队成功
}

在这里有些同学会有一些疑惑:

  • 队列判满为什么没有写
  • 入队完队尾指针为什么没有具体代码
    这里先不急,后续我们再讨论这个问题
顺序队列出队

出队的逻辑是将队头元素输出,然后指针加一即可,顺序队列出列示意图
![[顺序队列出列示意图.png]]

这里的front指向的都是有元素的空间,具体程序如下:

bool DeQueue(SqQueue &Q,ElemType &e){if(队列判空)return false;//队列以空,出队失败e=Q.datd[Q.front];//队列未空,元素出队//front指针加一return true;//出队成功
}

在这里,我们也出现两个问题:

  • 队列判空为什么不写
  • 出队完队头指针为什么不写具体代码
    接下来我们就来讨论一下上述问题
顺序队列存在的问题分析

在讨论上述顺序队列判空、判满以及指针加一的问题之前,我先抛出一个问题:

  • 队列的队头和队尾指针是固定不变的嘛?
    答案显示不是,入队队尾指针需要加一,同样的出队队头指针也需要加一。不知道你们发现问题没有,入队和出队的指针增长方向是一致的,对于已经分配好的静态空间来说,那经过一番出队入队的操作,其内存会形成以下现象:
    ![[顺序队列指针的增长方向.png]]

如图所示fornt和rear的增长方向一致,那么front之前的内存如何处理呢?如果任期发展下去,可能会出现front和rear都会在队尾,空队列也会成为满队列,front之前的空间变成一次性空间了:
![[顺序队列的指针窘境.png]]

显然这样的队列肯定不是一个好队列,因此我们需要如何解决这个问题,其实有人到这时候会想到顺序表或者排队,前面的每走一个,后面的就向前一步不就可以了嘛。确实也是,顺序表就是这样做的。但这无疑会给队列的基本运算带来更大的工作量。
我们需要一个方法,使其在队头和队尾指针增长方向一致的情况下,也利用到front前面的空间,这样做势必要让front回到前面的空间,将到这里,答案也呼之欲出了,那就是循环!
接下来我们就用循环队列来讲述以下如何判空和判满以及front和rear指针的变化

循环队列

将队列的头尾相接,构成循环队列,如此构建,哪怕队头和队尾指针都向一个方向增长,我们都可以让队列的每一个空间都可以利用到。
循环队列示意图:
![[循环队列示意图.png]]

解决问题的思路是构建循环队列,但我们还是要落实的具体的东西来,回归我们要解决的两个问题:

  • 队列判空和判满
  • front和rear指针增长
    我们通过循环队列先去解决队列的判空和判满问题
    伪满判断法
    观察示意图中红色表示有元素,深青色表示无元素,当rear的下一个是front时说明满队列。但这个时候其实也有人发现了,10并没有元素,这也是一个伪满队列。如果我们让10也插入元素,那么就会有rear == front。但在这里我们将rear == front作为判断空栈的条件了,为了做出区分满队列和空队列,我们牺牲一个结点空间让front == rear+1作为判断满栈的条件。
    判满条件:
  • front == rear+1
    判空条件:
  • rear == front
    长度判断法
    那有没有办法不牺牲空间的情况下去做这件事呢?那么首先我们要搞清楚其特点,从指针上看,循环队列的两个指针在队列空和队列满的时候都是一样的。那么我们应该从其他角度上去做改进,第一个想到的就是顺序表的length,当前表长。显然这个就很合适,但这个需要该队列的结构体:
#define MaxSize 10
typedef struct{ElemType data[MaxSize];int front,rear;int length;
}SqQueue;

判满条件:

  • length == MaxSize
    判空条件:
  • length == 0
    过程判断法
    我们在将视野放长一点,看一下满队列和空队列的具体情况,一个队列要满,肯定是需要通过入队这个操作;而一个队列要空则有两种情况,一个是新创建的队列,一个是通过出队使得队列变空。在了解了这一段动态区别之后,即便满队和空队的front指针和rear指针指向同一位置,也可以辨别其状态。类似的我们也需要改变其结构体用于记录其操作过程。
#define MaxSize 10
typedef struct{ElemType data[MaxSize];int front,rear;int flag;
}SqQueue;
  • flag为1时表示操作为入队
  • flag为0时表示操作为出队
  • 队列初始化时需要将flag置为0;
    判满条件:
  • front == rear && flag == 1
    判空条件:
  • front == rear && flag == 0
    上述解决了判空和判满的问题,但我们还有一个问题没解决:front和rear指针的增长问题。以往我们认为无论是出队还是入队操作我们只需要将rear或front加一即可,但一直的+必定在某个时刻指针会超限,即超过MaxSize。我们在引入循环队列之后,指针也需要做出改变,即不能超过MaxSize同时要在增长过程中不断循环。我们可以采用取余的方式实现,每次指针超限通过取余又回到范围:
front指针增长:
front=(front+1)%MaxSize;
rear指针增长:
rear=(rear+1)%MaxSize;
代码汇总

循环队列伪满队列方法:

/*顺序队列结构体创建*/
#define MaxSize 10//队列最大长度
typedef struct{int data[MaxSize];//数组存放队列元素int front,rear;//队头指针和队尾指针
}SqQueue;//顺序队列/*顺序队列初始化*/
void InitQueue(SqQueue &Q){//初始化队内各个元素数据for(int i=0;i<MaxSize;i++)Q.data[i]=0;Q.front=Q.rear=0;//初始化队头和队尾指针
}/*顺序队列入队*/
bool EnQueue(SqQueue &Q,int e){if(front==rear)//队列判满return false;//队列已满,入队失败Q.datd[Q.rear]=e;//队列未满,元素入队rear=(rear+1)%MaxSize;//rear指针加一return true;//入队成功
}/*顺序队列出队*/
bool DeQueue(SqQueue &Q,int &e){if(front==rear)//队列判空return false;//队列以空,出队失败e=Q.datd[Q.front];//队列未空,元素出队front=(front+1)%MaxSize;//front指针加一return true;//出队成功
}

循环队列队列长度方法:

/*顺序队列结构体创建*/
#define MaxSize 10//队列最大长度
typedef struct{int data[MaxSize];//数组存放队列元素int front,rear;//队头指针和队尾指针int length;
}SqQueue;//顺序队列/*顺序队列初始化*/
void InitQueue(SqQueue &Q){//初始化队内各个元素数据for(int i=0;i<MaxSize;i++)Q.data[i]=0;Q.front=Q.rear=0;//初始化队头和队尾指针Q.length=0;//初始化队列长度
}/*顺序队列入队*/
bool EnQueue(SqQueue &Q,int e){if(Q.length==MaxSize)//队列判满return false;//队列已满,入队失败Q.datd[Q.rear]=e;//队列未满,元素入队rear=(rear+1)%MaxSize;//rear指针加一Q.length=Q.length+1;//队列长度加一return true;//入队成功
}/*顺序队列出队*/
bool DeQueue(SqQueue &Q,int &e){if(Q.length==0)//队列判空return false;//队列以空,出队失败e=Q.datd[Q.front];//队列未空,元素出队front=(front+1)%MaxSize;//front指针加一Q.length=Q.length-1;return true;//出队成功
}

循环队列操作过程法:

/*顺序队列结构体创建*/
#define MaxSize 10//队列最大长度
typedef struct{int data[MaxSize];//数组存放队列元素int front,rear;//队头指针和队尾指针int flag;
}SqQueue;//顺序队列/*顺序队列初始化*/
void InitQueue(SqQueue &Q){//初始化队内各个元素数据for(int i=0;i<MaxSize;i++)Q.data[i]=0;Q.front=Q.rear=0;//初始化队头和队尾指针Q.flag=0;//初始化队列长度
}/*顺序队列入队*/
bool EnQueue(SqQueue &Q,int e){if(front==rear && Q.flag==1 )//队列判满return false;//队列已满,入队失败Q.datd[Q.rear]=e;//队列未满,元素入队rear=(rear+1)%MaxSize;//rear指针加一Q.flag=1;//队列长度加一return true;//入队成功
}/*顺序队列出队*/
bool DeQueue(SqQueue &Q,int &e){if(front==rear && Q.flag==0)//队列判空return false;//队列以空,出队失败e=Q.datd[Q.front];//队列未空,元素出队front=(front+1)%MaxSize;//front指针加一Q.flag=0;return true;//出队成功
}
队列的链式实现

类似的,队列可以仿照顺序表的物理结构存储方式实现顺序队列,我们也可以仿照链表的存储方式实现链式队列.同样的,链式队列也有带头结点和不带头结点的区分
![[链式队列.png]]

和链式栈类似,因为队列是对两端操作,带不带头结点其实差别不大,接下来我们先以不带头结点为例子进行讲解

链式队列的创建

队列对于元素本身只需要存储数据和next指针,但对于整个队列,非常重要的是队列的队头和队尾指针,因此在这里我们需要创建两个结构体,一个是元素结点的结构体,记录元素的数据和next指针,另一个是队列的结构体,记录队列的队头指针和队尾指针。

/*队列结点结构体*/
typedef struct LinkNode{ElemType datd;struct LinkNode *next;
}LinkNode;/*队列结构体*/
typedef struct {LinkNode *front,rear;
}LinkQueue;
链式队列初始化-不带头结点

因为没有头结点,同时初始化时的队列不存在元素,因此链式队列的front和rear指针总是指向NULL

bool InitQueue(LinkQueue &Q){Q.front=NULL;Q.rear=NULL;
}
链式队列入队-不带头节点

链式队列的入队主要就是指针的改变,唯一需要注意的是因为没有头结点的存在,第一个元素入队时和其他元素入队会有一点不一样:

void EnQueue(LinkQueue &Q,ElemType e){LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));//创建插入结点空间s->dada=e;//结点数据s->next=NULL;//新结点next为空//第一个元素入队需要特殊处理,头尾指针均指向第一个结点if(Q.front==NULL){Q.front=s;Q.rear=s;}else{Q.rear->next=s;//原来的尾结点指向新结点Q.rear=s;//更新队尾指针指向新结点}
}

在这里我们没有判断队满,因为链式队列的空间是动态的,除非内存空间不足,这是几乎不可能出现的。

链式队列出队-不带头结点

链式队列出队主要是修改front指针和释放结点空间,需要注意的是最后一个结点出队时的不同

bool DeQueue(LinkQueue &Q,ElemType &e){if(Q.front==NULL)//判空return false;//空队列,出队失败LinkNode *s=Q.front;//暂存出队结点e=s->data;//返回出队元素数据Q.front=s->next;//更新队头指针//最后一个结点出队特殊处理if(Q.rear==s){Q.front=NULL;Q.rear=NULL;}free(s);//释放空间return true;//出队成功
}

通过上述的基本操作我们可以看出来,没有头结点的链式队列在空间上可以节省一个结点的内存,但在出队入队的操作上,需要对第一个结点特殊处理,如果是有头结点则不需要.

带头结点的链式队列各个基本操作源码
/*队列结点结构体*/
typedef struct LinkNode{ElemType datd;struct LinkNode *next;
}LinkNode;/*队列结构体*/
typedef struct {LinkNode *front,rear;
}LinkQueue;/*有头结点链式队列初始化*/
bool InitQueue(LinkQueue &Q){//头 尾指针都指向头结点Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));//创建头结点并初始化头尾指针Q.front->next=NULL;//初始化头结点的next指向空
}/*有头结点链式队列入队*/
void EnQueue(LinkQueue &Q,ElemType e){LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));//创建插入结点空间s->dada=e;//结点数据s->next=NULL;//新结点next为空Q.rear->next=s;//原来的尾结点指向新结点Q.rear=s;//更新队尾指针指向新结点
}/*有头结点链式队列出队*/
bool DeQueue(LinkQueue &Q,ElemType &e){if(Q.front==NULL)//判空return false;//空队列,出队失败LinkNode *s=Q.front;//暂存出队结点e=s->data;//返回出队元素数据Q.front->next=s->next;//更新头结点指针//最后一个结点出队特殊处理if(Q.rear==s)Q.rear=Q.front;free(s);//释放空间return true;//出队成功
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/61874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机文件可以打印出来吗

在数字化时代&#xff0c;手机已成为我们日常生活和工作中不可或缺的一部分。很多时候&#xff0c;我们需要将手机上的文件打印出来&#xff0c;无论是学习资料、工作报告还是生活文档。那么&#xff0c;手机上的文件真的可以打印出来吗&#xff1f;答案是肯定的。 直接前往打…

《Spring Boot:快速构建应用的利器》

一、Spring Boot 的崛起与优势 &#xff08;四&#xff09;丰富的生态支持 Spring Boot 拥有强大的生态系统&#xff0c;这是它在 Java 开发领域中占据重要地位的关键因素之一。 Spring Cloud 是 Spring Boot 生态中的重要组成部分&#xff0c;它为构建分布式系统的微服务架构…

爬虫实战:采集知乎XXX话题数据

目录 反爬虫的本意和其带来的挑战目标实战开发准备代码开发发现问题1. 发现问题[01]2. 发现问题[02] 解决问题1. 解决问题[01]2. 解决问题[02] 最终结果 结语 反爬虫的本意和其带来的挑战 在这个数字化时代社交媒体已经成为人们表达观点的重要渠道&#xff0c;对企业来说&…

微信小程序2-地图显示和地图标记

一、index修改页面&#xff0c;让页面能够显示地图和一个添加标记的按钮。 index.wxml <scroll-view class"scrollarea" scroll-y type"list"><view class"index_container"><map id"map" style"width: 100%; h…

Qt入门1——认识Qt的几个常用头文件和常用函数

1.头文件 ① #include <QPushButton>——“按钮”头文件&#xff1b; ② #include <QLabel>——“标签”头文件&#xff1b; ③ #include <QFont>——“字体”头文件&#xff1b; ④#include <QDebug>——输出相关信息&#xff1b; 2. 常用函数/类的基…

社交电商专业赋能高校教育与产业协同发展:定制开发AI智能名片及2+1链动商城小程序的创新驱动

摘要&#xff1a;本文围绕社交电商有望成为高校常态专业这一趋势展开深入探讨&#xff0c;剖析国家政策认可下其学科发展前景&#xff0c;着重阐述在专业建设进程中面临的师资短缺及实践教学难题。通过引入定制开发AI智能名片与21链动商城小程序&#xff0c;探究如何借助这些新…

CPU命名那些事

一、Intel CPU命名 1. 命名结构 Intel CPU 的命名通常包含以下几个部分&#xff1a; 品牌 产品线 系列 代数 具体型号 后缀 例如&#xff1a;Intel Core i7-13700K 2. 各部分含义 品牌 Intel&#xff1a;表示厂商&#xff08;几乎所有命名中都有&#xff09;。不同品…

AR智能眼镜|AR眼镜定制开发|工业AR眼镜方案

AR眼镜的设计与制造成本主要受到芯片、显示屏和光学方案的影响&#xff0c;因此选择合适的芯片至关重要。一款优秀的芯片平台能够有效提升设备性能&#xff0c;并解决多种技术挑战。例如&#xff0c;采用联发科八核2.0GHz处理器&#xff0c;结合12nm制程工艺&#xff0c;这种低…

第二十一周机器学习笔记:动手深度学习之——数据操作、数据预处理

第二十周周报 摘要Abstract一、动手深度学习1. 数据操作1.1 数据基本操作1.2 数据运算1.2.1 广播机制 1.3 索引和切片 2. 数据预处理 二、复习RNN与LSTM1. Recurrent Neural Network&#xff08;RNN&#xff0c;循环神经网络&#xff09;1.1 词汇vector的编码方式1.2 RNN的变形…

ThinkPad t61p 作SMB服务器,打印服务器,pc ,android ,ipad利用此服务器互传文件

1.在t61p上安装win7 2,配置好smb 服务 3.再安装好打印驱动程序 4.pc与win7利用系统的网络互相发现,映射为硬盘使用。 5.android&#xff0c;ipad安装ES文件浏览器访问win7 共享文件夹&#xff0c;互传文件。 6.android手机安装FE文件浏览器&#xff0c;可以利用花生壳外网…

【深度学习之二】正则化函数(weight decay, dropout, label smoothing, and etc)详解,以及不同的函数适用的场景

在深度学习中正则化函数的重要性不言而喻&#xff0c;今天主要总结一些当前常用的一些正则化函数 在深度学习中&#xff0c;正则化&#xff08;Regularization&#xff09;是一种防止模型过拟合的技术。过拟合指的是模型在训练数据上表现很好&#xff0c;但在未见过的测试数据…

神经网络(系统性学习二):单层神经网络(感知机)

此前篇章&#xff1a; 神经网络中常用的激活函数 神经网络&#xff08;系统性学习一&#xff09;&#xff1a;入门篇 单层神经网络&#xff08;又叫感知机&#xff09; 单层网络是最简单的全连接神经网络&#xff0c;它仅有输入层和输出层&#xff0c;没有隐藏层。即&#x…

Linux 手动升级软件保姆级教程,适用所有软件,不限于麒麟等国产系统

1、检查软件版本&#xff0c;及是否安装 openssh为例 是否安装 rpm -qa|grep openssh 备份 mv /etc/ssh/ /home/ssh-bakmv /usr/bin/ssh /usr/bin/ssh.bakmv /usr/sbin/sshd /usr/sbin/sshd.bakmv /etc/pam.d/sshd /etc/pam.d/sshd.old2、机器如果不在身边&#xff0c;机器…

【大数据学习 | Spark-Core】Spark的改变分区的算子

当分区由多变少时&#xff0c;不需要shuffle&#xff0c;也就是父RDD与子RDD之间是窄依赖。 当分区由少变多时&#xff0c;是需要shuffle的。 但极端情况下&#xff08;1000个分区变成1个分区)&#xff0c;这时如果将shuffle设置为false&#xff0c;父子RDD是窄依赖关系&…

java操作doc——java利用Aspose.Words操作Word文档并动态设置单元格合并

在实际工作中&#xff0c;如果业务线是管理类项目或者存在大量报表需要导出的业务时&#xff0c;可以借助第三方插件实现其对应功能。 尤其是需要对word文档的动态操作或者模板数据的定向合并&#xff0c;使用Aspose会相对来说容易一些&#xff0c;而且相关文档比较完整&#…

电商一件发货软件闲管家使用教程

闲鱼闲管家是一款专为闲鱼卖家设计的电脑版工作台&#xff0c;旨在帮助卖家更高效地管理其在闲鱼平台上的业务。以下是关于闲鱼闲管家的一些主要特点和功能&#xff1a; 主要特点&#xff1a; 多账号管理&#xff1a;支持同时管理多达30个闲鱼账号&#xff0c;方便大型卖家或…

Docker Seata分布式事务保护搭建 DB数据源版搭建 结合Nacos服务注册

介绍 Seata&#xff08;Simple Extensible Autonomous Transaction Architecture&#xff09;是一个开源的分布式事务解决方案&#xff0c;旨在为微服务架构中的分布式系统提供事务管理支持。Seata 通过提供全局事务管理&#xff0c;帮助开发者在分布式环境中保持数据一致性 …

HTB:WifineticTwo[WriteUP]

目录 连接至HTB服务器并启动靶机 信息搜集 使用rustscan对靶机TCP端口进行开放扫描 使用nmap对靶机开放端口进行脚本、服务扫描 使用curl访问靶机8080端口 使用浏览器直接访问/login路径 漏洞利用 使用searchsploit搜索该WebAPP漏洞 Payload USER_FLAG&#xff1a;bb…

【MySQL课程学习】:MySQL安装,MySQL如何登录和退出?MySQL的简单配置

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;MySQL课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 MySQL在Centos 7环境下的安装&#xff1a; 卸载…

oracle如何配置第二个监听优化数据传输

oracle如何配置第二个监听优化数据传输 服务器两个网卡&#xff0c;配置两个不同IP和端口的监听。 归档日志量每天很大&#xff0c;为了不影响业务&#xff0c;需要配置一个单独的万兆网络来专门的传输归档日志到DG库&#xff0c;这里就涉及到在19c中增加一个监听用来使用专门…