数据结构与算法:队列

在上篇文章讲解了栈之后,本篇也对这一章进行收尾,来到队列!

队列

  • 队列的介绍
  • 队列的存储结构
    • 队列顺序存储的不足之处
  • 循环队列的定义
  • 队列的链式存储结构
    • 链队列的构建
    • 链队列的初始化
    • 队尾入队
    • 队头出队
    • 获取队头队尾元素
    • 判断队列是否为空
    • 获取队列元素个数
    • 队列的销毁

队列的介绍

队列(Queue)就像是排队买票的人群。想象一下你去电影院看电影,人们在售票窗口形成一条线(队列)等待购票。队列遵循一个很重要的原则:先来先服务(First In, First Out,简称FIFO)。这意味着最先到达并排队的人将会是第一个买到票并离开队列的人,随后到达的人则依次排在队伍的后面,等待买票。

客服服务应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

队列是一种先进先出的线性表,允许插入的一端成为队尾,允许删除的一端称为队头

在这里插入图片描述

队列的存储结构

线性表有两种存储结构:顺序存储和链式存储,在栈中我们知道,栈存在两种存储结构,队列作为特殊的线性表,也同样存在这两种存储结构

队列顺序存储的不足之处

我们假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并把队列所有元素存储在数组的钱n个单元,数组下表为0的一端即为队头

此时入队列操作,其实就是在队尾追加一个元素,并不需要移动任何元素,时间复杂度为O(1).
在这里插入图片描述

与栈不同的是,队列元素的出列在队头,即下表为0的位置,意味着队列中所有元素都得向前移动。此时时间复杂度为O(N);
在这里插入图片描述
如果不去限制队列元素必须存储在数组前n个单元这一条件,出队的性能则会大大增加,即队头不需要一定要在下标为0的位置。
在这里插入图片描述
此时我们则需要设置队头指针为front,rear指针指向队尾元素的下一个位置

假设长度为5的数组,入队四个元素,rear指针指向下标为4的位置
在这里插入图片描述
出队a1,a2,此时front指向下标为2的位置,rear不变
在这里插入图片描述

当front与rear相等时则队列为空

如果我再入队a5,此时front不变,rear移动到数组之外,指向哪里了呢?
在这里插入图片描述

随着队列操作的进行,如果不断地添加和移除元素,队头指针会向数组的末尾移动,这可能会造成队头不在数组的起始位置。

当继续向队列中添加元素而队尾已经达到数组的最末端时,若不采取任何措施,就无法再添加新的元素,即使数组的前部(队头之前的部分)是空闲的。这种情况看起来好像数组已经“溢出”了,但实际上是因为未充分利用数组的空间,称为“假溢出”。

循环队列的定义

所以我们如何解决上述的假溢出呢?

解决假溢出的办法就是后面满了,再从头开始,头尾相接的循环,把队列这种头尾相接的顺序结构称为循环队列

顺着上述例子,当a5入队后,rear可以改为指向下标0,则解决了指针指向不明

在这里插入图片描述
接着入队a6,将它置于下标为0处,rear指向下标为1处

在这里插入图片描述
上述提到,空队列时,front等于rear,若我插入a7,此时front等于rear,如何判断此时的队列是满还是空呢?
在这里插入图片描述
解决办法:
我们让队列判空条件还是front=rear,当队列满时,我们修改条件,使其保留一个元素空间,也就是队列满时,还有一个空闲单元
在这里插入图片描述
rear可能比front大,也可能比front小,相差一个位置即为满
设队列的最大尺寸为QueueSize,则队列满的条件是
(rear+1)%QueueSize==front

这种顺序存储若不是循环队列,算法性能不高,循环队列又面临着数组溢出的问题,我们接下来讲解队列的链式存储结构**

队列的链式存储结构

队列的链式存储结构,就是线性表的单链表,只不过它只能尾进头出

我们在链队列中有两个指针,一个指向头,一个指向尾

链队列的构建

typedef int QDataType;
typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;
typedef struct Queue 
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

这里的队列是通过链表实现的,链式存储方式的好处在于它可以动态地分配内存,避免了顺序队列中可能发生的假溢出问题,同时也不需要在队列初始化时就确定其最大容量。

  • phead指针指向队列的头部(第一个元素),而ptail指针指向队列的尾部(最后一个元素)。这两个指针是实现队列基本操作(如入队和出队)的关键

  • size成员存储队列中当前的元素数量。这个信息对于快速获取队列的大小,以及确定队列是否为空等操作非常有用。

链队列的初始化

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size=0;
}

在封装的Queue结构体背景下,通过QueueInit(Queue* pq)函数以指针形式传递
传递指向Queue结构体的指针允许QueueInit函数直接对实例进行初始化操作,包括设置头尾指针为NULL和队列大小为0。

队尾入队

只有入队时需要创造新节点,这里我们直接在函数里完成新节点的构造,不需要单独的函数

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;

	if (pq->ptail == NULL)
	{
		pq->ptail = pq->phead = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
  • 如果队列为空(即pq->ptail为NULL),则新节点既是队列的头节点也是尾节点,因此将pq->phead和pq->ptail都指向新节点。
  • 如果队列不为空,则将当前尾节点的next指针指向新节点,然后更新pq->ptail指向新节点,这样新节点就成为了队列的尾节点。
  • 将队列的size成员加1,表示队列中元素的数量增加

队头出队

void QueuePop(Queue* pq) {
	assert(pq);                     
	if (pq->phead == NULL) return;  
	QNode* temp = pq->phead;        
	pq->phead = pq->phead->next;  
	free(temp);                    

	temp = NULL;
	if (pq->phead == NULL) {      
		pq->ptail = NULL;
	}

	pq->size--;  
}
  • if (pq->phead == NULL) return;如果队列为空,则没有元素可以弹出
  • 构建temp中间变量来指向要释放的节点,将头结点指向下一个节点
  • 如果弹出之后队列变为空,则尾指针也要更新为 NULL

获取队头队尾元素

QDataType QueueFront(Queue* pq)
{
	assert(pq);                     // 确保 pq 不是 NULL
	assert(pq->phead != NULL);
	return pq->phead->val; 

}
QDataType QueueBack(Queue* pq) {
	assert(pq != NULL); // 确保队列指针不为NULL
	assert(pq->ptail != NULL); // 确保队列不为空,即队尾指针不为NULL

	// 返回队列尾部元素的值
	return pq->ptail->val;
}

这两串获取元素的代码变得十分简单了

判断队列是否为空

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL;
}

获取队列元素个数

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

队列的销毁

void QueueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

在前面的铺垫之后,我们理解这几个函数就十分简单了

本节内容到此结束!后续我们会更新例题来加强对这个地方的理解!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【北京游戏业:出海竞争实力全面】

本文将深入分析北京的游戏行业发展。在上海、广州、北京、深圳、成都、杭州、福建七大游戏产业中心城市中,北京无疑是出海竞争力最强的游戏产业集群。本文将全面剖析北京游戏行业的发展现状。 北京是中国游戏产业的发源地。拥有从游戏引擎到美术设计等完整的产业链…

奇异递归模板模式应用5-静态多态

动态多态:C动态多态是利用虚函数特性实现的,即基类指针(引用)指向派生类指针(引用)。由于虚函数的实现是在运行期进行的,因而会产生运行期开销(虚表指针偏移,与分支预测器和CPU指令流水线相关)。…

【C++】类和对象---const成员,取地址及const取地址操作符重载,static成员

目录 ⭐const成员 ⭐取地址及const取地址操作符重载 ⭐static成员 ⭐概念 ⭐特性 ⭐const成员 将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何…

怎样使用Pyglet库给推箱子游戏画关卡地图

目录 pyglet库 画图事件 按键事件 程序扩展 关卡地图 pyglet库 是一个跨平台的Python多媒体库,提供了一个简单易用的接口来创建窗口、加载图像和视频、播放音频、处理用户输入事件以及进行2D图形绘制。特别适合用于游戏开发、视听应用以及其它需要高效图形渲染…

笔记:torch.roll

最近在准备写 swin transformer 的文章,记录下 torch.roll 的用法: >>> x torch.tensor([1, 2, 3, 4, 5, 6, 7, 8]).view(4, 2) >>> x tensor([[1, 2],[3, 4],[5, 6],[7, 8]]) 第0维度向下移1位,多出的[7,8]补充到顶部 &g…

如何使用idea连接服务器上的mysql?

安全组进行开放 具体步骤 关闭防火墙 开放端口号 重启防火墙 firewall-cmd --reload在mysql进行修改配置 update user set host % where user root;flush privileges;使得其他网络也可以连接这个数据库 另外如果想要sqlyog或者其他图形化界面要连接到数据库可以看下面这…

K8S实战:Centos7部署Kubernetes1.20.0集群

目录 一、准备工作1.1、创建3台虚拟机1.1.1、下载虚拟机管理工具1.1.2、安装虚拟机管理工具1.1.3、下载虚Centos镜像1.1.4、创建3台虚拟机1.1.5、设置虚拟机网络环境 1.2、虚拟机基础配置(3台虚拟机进行相同处理)1.2.1、配置host1.2.2、关闭防火墙1.2.3、…

2.21数据与结构算法学习日记(最小生成树prim算法)

目录 最小生成树prim 最小生成树算法是一种用来在一个加权连通图中找到最小生成树的算法。最小生成树是一个包含图中所有顶点的树,其总权值最小。 prim算法 洛谷题目示例 P3366 【模板】最小生成树 题目描述 输入格式 输出格式 输入输出样例 说明/提示 题…

合并Windows电脑的不同分区(不同的盘)的方法

本文介绍在Windows操作系统的电脑中,将磁盘上的不同分区(例如E盘与F盘)加以合并的方法。 最近,想着将新电脑的2个分区加以合并;如下图所示,希望将E盘与F盘合并为一个分区。本文就介绍一下实现这一需求的具体…

深度学习基础——SSD目标检测

SSD网络介绍 使用多个特征图作为特征预测层。 SSD (Single Shot MultiBox Detector)于2016年提出。当网络输入为300300大小时,在VOC2007测试集上达到74.3%的mAP;当输入是512512大小时,达到了76.9%的mAP SSD_Backbone部分介绍 不变的部分 特征提取网…

CMake的简单使用

一、一个最简单的CMake项目 在Ubuntu上使用CMake构建一个最简单的项目。 1. 安装CMake 首先安装CMake,这里使用的是Ubuntu系统。 sudo apt-get install cmake2. 编写源程序 编写代码,新建文件main.c。 // main.c #include "stdio.h"int …

国内最全的AIGC大模型软件都是免费的,不比chatgpt香吗?我都为你准备好了,又可以提前下班了

无极低码 :https://wheart.cn 豆包(云雀大模型)、文心一言、悟空、星火、百度文库、360智脑、天宫AI、智谱清言(GLM大模型)、百川模型(百川智能)、日日新(商汤)、上海人工智能实验室(书生通用大模型)、夸克。 国内最全的AIGC大模型软件都是…

devOps系列(七)grafana+prometheus监控告警

前言 作者目前打算分享一期关于devOps系列的文章,希望对热爱学习和探索的你有所帮助。 文章主要记录一些简洁、高效的运维部署指令,旨在 记录和能够快速地构建系统。就像运维文档或者手册一样,方便进行系统的重建、改造和优化。每篇文章独立…

微信小程序 -- npm 支持

目录 npm 支持 1. 构建 npm 2. 自定义构建 npm 3. Vant 组件的使用方式 4. Vant 组件的样式覆盖 npm 支持 1. 构建 npm 目前小程序已经支持使用 npm 安装第三方包,但是这些 npm 包在小程序中不能够直接使用,必须得使用小程序开发者工具进行构建后才…

【力扣 - 二叉树的直径】

题目描述 给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 提示: 树中节点数目在范围 [1, 10000] 内…

电流回路是分析电路图的基础,看看这个电路你会更明白

任何电器要想开始工作,都离不开供电,而要供电就离不开电源。电源有两个极即:电源正极()、电源负极(-),电源要实现向负载供电,必须是电源正极()流出电流经负载再流回电源负极(-),这时可以说这个电路构成了供电电流回路了…

tokenizer添加token的详细demo

文章目录 前言一、tokenizer添加token二、结果比较1、手动添加token2、代码验证添加token3、结果显示 前言 我们在Hugging Face不同模型对应的tokenizer映射字典,不存在某些专有词汇,我们需要新增对应的token,以便我们使用对应模型处理不存在…

消息中间件-面试题

MQ选择 一、Kafka 1、消息队列如何保证消息可靠性 消息不重复 生产者控制消费者幂等消息不丢失 生产者发送,要确认broker收到并持久化broker确认消费者消费完,再删除消息2、kafka是什么 Kafka是一种高吞吐量、分布式、基于发布/订阅的消息中间件,是Apache的开源项目。broke…

打造智能物品租赁平台:Java与SpringBoot的实践

✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 |…

每日一题——LeetCode1470.重新排列数组

方法一 把数组的前n项看做一个数组&#xff0c;后n项看做一个数组&#xff0c;两个数组循环先后往res里push元素 var shuffle function(nums, n) {let res[]for(let i0;i<n;i){res.push(nums[i])res.push(nums[in])}return res }; 消耗时间和内存情况&#xff1a; 方法二…