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

news2025/5/13 17:30:13

队列

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

队列的基本概念

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

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

和栈不同的是,栈的插入或删除规定在同一端进行,但队列将插入和删除分开在了两端进行。我们可以将其理解成排队打饭,先去排队的人先打到饭,自然也是先离开,因此队列遵循的是先进先出的原则。队列逻辑结构示意图如图:
![[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.coloradmin.cn/o/2249318.html

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

相关文章

chrome允许http网站打开摄像头和麦克风

第一步 chrome://flags/#unsafely-treat-insecure-origin-as-secure 第二步 填入网址&#xff0c;点击启用 第三步 重启 Chrome&#xff1a;设置完成后&#xff0c;点击页面底部的 “Relaunch” 按钮&#xff0c;重新启动 Chrome 浏览器&#xff0c;使更改生效。

Spring Boot教程之十: 使用 Spring Boot 实现从数据库动态下拉列表

使用 Spring Boot 实现从数据库动态下拉列表 动态下拉列表&#xff08;或依赖下拉列表&#xff09;的概念令人兴奋&#xff0c;但编写起来却颇具挑战性。动态下拉列表意味着一个下拉列表中的值依赖于前一个下拉列表中选择的值。一个简单的例子是三个下拉框&#xff0c;分别显示…

DRM(数字权限管理技术)防截屏录屏----视频转hls流加密、web解密播放

提示&#xff1a;视频转hls流加密、web解密播放 需求&#xff1a;研究视频截屏时&#xff0c;播放器变黑&#xff0c;所以先研究的视频转hls流加密 文章目录 [TOC](文章目录) 前言一、工具ffmpeg、openssl二、后端nodeexpress三、web播放四、文档总结 前言 ‌HLS流媒体协议‌&a…

视觉经典神经网络与复现:深入解析与实践指南

目录 引言 经典视觉神经网络模型详解 1. LeNet-5&#xff1a;卷积神经网络的先驱 LeNet-5的关键特点&#xff1a; 2. AlexNet&#xff1a;深度学习的突破 AlexNet的关键特点&#xff1a; 3. VGGNet&#xff1a;深度与简洁的平衡 VGGNet的关键特点&#xff1a; 4. ResNe…

【算法day1】数组:双指针算法

题目引用 这里以 1、LeetCode704.二分查找 2、LeetCode27.移除元素 3、LeetCode977.有序数组的平方 这三道题举例来说明数组中双指针的妙用。 1、二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜…

华为云云连接+squid进行正向代理上网冲浪

1 概述 ‌Squid‌是一个高性能的代理缓存服务器&#xff0c;主要用于缓冲Internet数据。它支持多种协议&#xff0c;包括FTP、gopher、HTTPS和HTTP。Squid通过一个单独的、非模块化的、I/O驱动的进程来处理所有的客户端请求&#xff0c;这使得它在处理请求时具有较高的效率‌。…

2024年11月24日Github流行趋势

项目名称&#xff1a;FreeCAD 项目维护者&#xff1a;wwmayer, yorikvanhavre, berndhahnebach, chennes, WandererFan等项目介绍&#xff1a;FreeCAD是一个免费且开源的多平台3D参数化建模工具。项目star数&#xff1a;20,875项目fork数&#xff1a;4,117 项目名称&#xff1…

二叉树:堆的建立和应用

在建立堆之前&#xff0c;我们要知道什么是树和二叉树 树 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个结点组成的一个具有层次关系的集合&#xff0c;之所以把它叫做树&#xff0c;是因为它长得像一棵倒挂的树&#xff0c;也就是根在上面&…

使用 pycharm 新建不使用 python 虚拟环境( venv、conda )的工程

有时候我们发现一个好玩的 demo&#xff0c;想赶快在电脑上 pip install 一下跑起来&#xff0c;发现因为 python 的 venv、conda 环境还挺费劲的&#xff0c;因为随着时间的发展&#xff0c;之前记得很清楚的 venv、conda 的用法&#xff0c;不经常使用&#xff0c;半天跑不起…

(详细文档!)java swing学生信息管理系统 +mysql

第一章&#xff1a;系统功能分析 1.1、系统简介与开发背景 学生信息管理系统是在信息化时代&#xff0c;特别是在教育领域中产生的。随着学校规模的不断扩大和信息化技术的不断发展&#xff0c;传统的纸质档案管理方式已经无法满足学校对学生信息管理的需求&#xff0c;因此需…

租辆酷车小程序开发(二)—— 接入微服务GRPC

vscode中golang的配置 设置依赖管理 go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct GO111MODULEauto 在$GOPATH/src 外面且根目录有go.mod 文件时&#xff0c;开启模块支持 GO111MODULEoff 无模块支持&#xff0c;go会从GOPATH 和 vendor 文件夹寻找包…

集合卡尔曼滤波(EnKF)的三维滤波(模拟平面定位)例程,带逐行注释

这段 MATLAB 代码实现了一个三维动态系统的集合卡尔曼滤波&#xff08;Ensemble Kalman Filter, EnKF&#xff09;示例。代码的主要目的是通过模拟真实状态和测量值&#xff0c;使用 EnKF 方法对动态系统状态进行估计。 文章目录 参数设置初始化真实状态定义状态转移和测量矩阵…

鸿蒙千帆启新程,共绘数字生态蓝图

华为的鸿蒙千帆起计划&#xff1a;共筑数字未来&#xff0c;学习华为创新之路 在当今全球科技竞争日益激烈的背景下&#xff0c;华为作为中国科技企业的代表&#xff0c;正通过其自主创新的鸿蒙系统&#xff0c;引领一场移动应用生态的变革。鸿蒙千帆起计划&#xff0c;作为华…

python控制鼠标,键盘,adb

python控制鼠标&#xff0c;键盘&#xff0c;adb 听说某系因为奖学金互相举报&#xff0c;好像拿不到要命一样。不禁想到几天前老墨偷走丁胖子的狗&#xff0c;被丁胖子逮到。他面对警察的问询面不改色坚持自我&#xff0c;反而是怒气冲冲的丁胖子被警察认为是偷狗贼。我觉得这…

yum安装升级指定版本软件

与apt相同&#xff0c;yum也可以指定升级软件版本。 这里以升级gitlab-ce为例。 获得当前可用版本命令如下&#xff1a; yum list gitlab-ce --showduplicates 查看当前gitlab版本。在/var/opt/gitlab/gitlab-rails/查看VERSION文件内容&#xff1a; cat VERSION 当前版本…

el-table根据接口返回某一个字段合并行

根据名称相同合并行 <template><div><el-table :data"responseSearchIntegralAddData" :span-method"objectSpanMethod1" border style"width: 100%"><el-table-column prop"integralTypeName" label"名称…

Jmeter中的前置处理器

5&#xff09;前置处理器 1--JSR223 PreProcessor 功能特点 自定义数据处理&#xff1a;使用脚本语言处理请求数据&#xff0c;实现高度定制化的数据处理和生成。动态数据生成&#xff1a;在请求发送前生成动态数据&#xff0c;如随机数、时间戳等。变量设置&#xff1a;设置…

ABAP OOALV模板

自用模板&#xff0c;可能存在问题 一、主程序 *&---------------------------------------------------------------------* *& Report ZVIA_OO_ALV *&---------------------------------------------------------------------* REPORT ZVIA_OO_ALV.INCLUDE ZVI…

新能源汽车充电基础设施短板问题多,如何实现高效、综合、智能化管理?

随着城市经济的发展&#xff0c;人民生活水平的提升&#xff0c;新能源汽车保有量快速增长&#xff0c;而日益增长的新能源汽车需求与充电基础设施建设不平衡的矛盾日益突出。由于停车泊位充电基础设施总量不足、布局待优化、利用效率低、建设运营存在短板问题等原因&#xff0…

LeetCode:19.删除链表倒数第N个节点

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;19.删除链表倒数第N个节点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表…