【数据结构取经之路】队列循环队列

目录

引言

队列的性质

队列的基本操作

初始化

判空

销毁

队列的长度

插入

删除

返回队头元素

循环队列

假溢出

空与满的判定

实现 

初始化

插入

判空

销毁

删除

返回队列长度

返回队列头元素

判满


引言

队列和栈一样,也是数据结构的一种,可以用数组实现,也可以用链表实现。它常用于各种应用程序中,包括操作系统、网络通信等。一个最典型的例子就是操作系统中的作业排队。在允许多道程序运行的计算机系统中,同时有几个作业运行。如果运行的结果多需要通过通道输出,那就要按请求输出的先后顺序排队。每当通道传输完毕可以接受新的输出任务时,队头的作业先从队列中退出作输出操作。

队列的性质

队列的性质与栈相反,它是一种“先进先出”的线性表。它只允许在队列的一端进行插入,在另一端进行删除。允许插入的一端叫队尾,允许删除的一端叫队头。

队列的基本操作

队列的操作和栈的操作类似,下面将以链式队列为例,逐一讲解各个操作。因为是用单链表实现队列,如果仅仅记录队头,那么在插入数据时必须得遍历一遍链表,找到队列的尾,才能链接上新的结点,这是一个不小的消耗,所以,除了对链表的结点封装成一个结构体(A)外,还可以再封装一个结构体(B),B结构体成员里包含结构体A。这么说很抽象,还是看代码吧!

typedef int QueueDataType;

typedef struct QueueNode
{
	QueueDataType data;
	struct QueueNode* next;
}QueueNode;

typedef struct Queue
{
	QueueNode* phead;//指向链表头
	QueueNode* ptail;//指向链表尾
	int size;//记录长度
}Queue;

这样就方便对队列进行维护了。

初始化

初始化时,可以malloc出一些空间给队列,也可以不用,等到插入时再开辟空间也行,都无可厚非,我这里选择后者。

代码:


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

判空

链表为空的标志是size == 0.

代码:

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

销毁

因为队列底层是用单链表实现,所以在销毁时得一个节点一个节点的释放。在销毁节点A前,需要先记录A的下一个结点,否则把A释放后,将找不到它的下一个节点。销毁链表时还有一个细节,当只有一个节点时,free头结点后,要将头指针和尾指针都置空,如果仅把头指针置空,那么尾指针就是野指针。

代码:

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

	while (pq->phead)
	{
		QueueNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
		if (next == NULL) pq->ptail = NULL;//只有一个节点时,防止ptail为野指针
	}
}

队列的长度

size就是队列的长度,返回size即可。

代码:

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

插入

由于定义了尾指针,所以在插入前,就不需要遍历链表找尾了。第一种情况,链表不为空,不需要调整头指针,直接将新节点链接到尾节点后面即可,但不要忘了size++。第二种情况,链表为空,插入时,头指针和尾指针的指向都需要修改。

代码:

//创建节点
QueueNode* BuyNode(QueueDataType x)
{
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->next = NULL;
	newnode->data = x;

	return newnode;
}

void QueuePush(Queue* pq, QueueDataType x)
{
	assert(pq);

	QueueNode* newnode = BuyNode(x);

	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;//更新尾指针
	}
	pq->size++;
}

删除

第一点,当队列为空时,无法删除。第二,队列的删除操作是在队头进行的,所以需要更新头指针的指向。最后,不要忘了size--。

代码:

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	QueueNode* newhead = pq->phead->next;
	free(pq->phead);
	pq->phead = newhead;
    pq->size--;
}

返回队头元素

通过头指针便可以访问到队头元素。当队列为空时,无法返回。

代码:

QueueDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

循环队列

循环队列是把顺序队列首尾相连,把存储队列元素的数组从逻辑上看成一个环,成为循环队列。在物理上,它就是个数组,在逻辑上,我们要把它想象成一个圆环。循环队列的实现一般定义两个指针,头指针front和尾指针rear,front和rear开始都赋为0,每插入一个元素,rear++,每删除一个元素,front++,这和顺序栈如出一辙。front指向头,rear指向尾元素的下一个位置。

循环队列遵循先进先出的原则。

从队尾入队列,从队头出队列。

用数组实现的循环队列支持下标的随机访问,访问速度快。 

假溢出

请看上图,往队列里插入8个元素使队列呈满的状态,接着删除两个元素,此时,队列里有两个空位,但这两个空位是无法使用的,因为rear已经越界了,无法再继续插入。这种现象就叫作“假溢出”。这就导致了空间的浪费。

克服假溢出的方法有两种,第一种,把数组元素往前挪动,使其起始位置从0开始,显然,这种方法是很浪费时间的。第二种方法,就是使用循环队列,当存到最后一个地址后,下一个存放的位置就是从0开始。这也使得用数组实现的循环队列长度是固定的,不能动态增长。 

空与满的判定

循环队列的循环效果是用取余运算来实现的。在实现循环队列之前,我们首先搞清楚下面的问题——如何区分循环队列的空与满。

对比图a和图d1,我们发现,当循环队列为空时,存在front == rear,当循环队列满时,也存在front == rear,这样,就导致无法区分循环队列的空和满。解决方案有两种,第一种,令设一个变量来记录队列中的元素个数,以区分空和满;第二种,留出一个元素的空间,当队列头指针在队列尾指针的下一位置时,队列为满,队列为空的标志是front == rear,这样就将空和满这两种情况给区分开了。 

实现 

#define MIXSIZE 10

typedef int CQueueDataType;

typedef struct CycleQueue
{
	CQueueDataType a[MIXSIZE];
	int front;
	int rear;
}CycleQueue;

初始化

代码:

void CycleQueueInit(CycleQueue* pcq)
{
	assert(pcq);
	pcq->front = 0;
	pcq->rear = 0;
}

插入

如果队列已满,那么将无法插入。因为循环队列是定长的,长度不可动态增长。往队列中插入数据时还有以下细节:

第一种情况,直接rear++即可。第二种情况,在插入数据以后,如果直接rear++,将会越界,需要做特殊处理。

以上两种情况的统一处理方式为:

rear = (rear + 1)% MIXSIZE; 

当rear为最后一个元素的下标时,rear + 1 就是MIXSIZE,再模上MIXSIZE,正好为0,回到了数组的起始位置。由于取模操作本质上是去掉整除的部分,所以当rear < MIXSIZE时,进行取模操作后也可以得到正确的结果。

代码:

void CycleQueuePush(CycleQueue* pcq, CQueueDataType x)
{
	assert(pcq);
	assert(!CycleQueueFull(pcq));

	pcq->a[pcq->rear] = x;
	pcq->rear = (pcq->rear + 1) % MIXSIZE;
}

判空

循环队列为空的标志是:front == rear。

代码:

bool CycleQueueEmtpy(CycleQueue* pcq)
{
	assert(pcq);
	return pcq->front == pcq->rear;
}

销毁

代码:

void CycleQueueDestroy(CycleQueue* pcq)
{
	assert(pcq);
	free(pcq->a);
	pcq->front = 0;
	pcq->rear = 0;
}

删除

由于循环队列是用数组实现,所谓的删除并不是抹除数据,而是通过控制下标,让该元素在删除后无法被访问到。这里和插入时一样,有一个很类似的细节:

front++后可能会越界,统一的处理方式如下:

front = (front + 1)% MIXSIZE; 

代码:

void CycleQueuePop(CycleQueue* pcq)
{
	assert(pcq);
	pcq->front = (pcq->front + 1) % MIXSIZE;
}

返回队列长度

求循环队列长度的公式:

len = (rear - front + MIXSIZE) % MIXSIZE;

代码:

int CycleQueueLen(CycleQueue* pcq)
{
	assert(pcq);
	return (pcq->rear - pcq->front + MIXSIZE) % MIXSIZE;
}

返回队列头元素

队列头元素的下标为front。

代码:

CQueueDataType CycleQueueFront(CycleQueue* pcq)
{
	assert(pcq);
	return pcq->a[pcq->front];
}

判满

循环队列满的标志:

(rear + 1)% MIXSIZE == front; 

代码:

bool CycleQueueFull(CycleQueue* pcq)
{
	assert(pcq);
	return (pcq->rear + 1) % MIXSIZE == pcq->front;
}

 完! 

————————————————————————————————————————————————————————————

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

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

相关文章

python初级2条件与循环笔记

两个课堂小练习&#xff1a; 1、(计算圆柱体的体积) 编写一个读取圆柱的半径和高并利用公式计算圆柱体底面积和体积的程序 iimport math reval(input("enter the r")) heval(input("enter the h")) arear*r*math.pi print("the area ",area,…

java 三元搜索 - 迭代与递归(Ternary Search)

计算机系统使用不同的方法来查找特定数据。有多种搜索算法&#xff0c;每种算法更适合特定情况。例如&#xff0c;二分搜索将信息分为两部分&#xff0c;而三元搜索则执行相同的操作&#xff0c;但分为三个相等的部分。值得注意的是&#xff0c;三元搜索仅对排序数据有效。在本…

SELinux详解

文章目录 SELinux详解什么是SELinux当初设计的目标&#xff1a;避免资源的误用传统的文件权限与账号主要的关系&#xff1a;自主访问控制(DAC)以策略规则制定特定进程读取特定文件&#xff1a;强制访问控制(MAC) SELinux的运行模式安全上下文进程与文件SELinux类型字段的相关性…

代码随想录算法训练营第五十一天|309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费、股票问题总结

309.最佳买卖股票时机含冷冻期 刷题https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/文章讲解https://programmercarl.com/0309.%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%8…

java第一次作业(二)

先写思路&#xff0c;再写代码&#xff0c;思路清晰&#xff0c;才能写对代码 7-6 求12...n的和 思路&#xff1a; 运用expression的字符串输出 重点&#xff1a; expression输出 代码&#xff1a; import java.util.Scanner; public class Main {public static void main…

Vue.js前端开发零基础教学(三)

目录 2.6 计算属性 2.7侦听器 2.8 样式绑定 2.8.1 绑定class属性 2.8.2 绑定style属性 2.9 阶段案例——学习计划表 2.6 计算属性 概念&#xff1a;Vue提供了计算属性来描述依赖响应式数据的复杂逻辑。 计算属性可以实时监听数据的变化&#xff0c;返回一个计算…

爬虫Day3

用到的网页--豆瓣电影Top250 需要爬取信息&#xff1a; 数据保存在网页源代码中&#xff0c;是服务加载方式。先拿到网页源代码--request。再通过re提取想要的信息---re。 新知识&#xff1a;用csv存数据&#xff0c;可以用excel表格展示数据 import csv result obj.findite…

AI大模型引领未来智慧科研暨ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

浅谈WPF之MVVM工具包

在之前的WPF示例中&#xff0c;都会用到一个MVVM框&#xff0c;也是一个比较常的MVVM框架&#xff0c;就是MVVM工具包【CommunityToolkit.Mvvm】&#xff0c;今天专门以一个简单的小例子&#xff0c;简述一下MVVM工具包的常见用法&#xff0c;仅供学习分享使用&#xff0c;如有…

Docker 安装 Nginx 容器,反向代理

Docker官方镜像https://hub.docker.com/ 寻找Nginx镜像 下载Nginx镜像 docker pull nginx #下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx:xxx #下载指定版本的Nginx镜像 (xxx指具体版本号)检查当前所有Docker下载的镜像 docker…

Spring Security之认证过滤器

前言 上回我们探讨了关于Spring Security&#xff0c;着实复杂。这次咱们聊的认证过滤器就先聊聊认证功能。涉及到多方协同的功能&#xff0c;咱分开聊。也给小伙伴喘口气&#xff0c;嘻嘻。此外也是因为只有登录认证了&#xff0c;才有后续的更多功能集成的可能。 认证过滤器…

unity学习(69)——多人位置同步

简单的很&#xff0c;每个客户端向服务器发送位置信息&#xff0c;服务器再把这些位置信息发送给其他客户端。 1.客户端发送。 1.1在SocketModel脚本中添加一个新的类MoveDTO public class MoveDTO {public string Id{get; set;}public int Dir{get;set;}public Assets.Mode…

Leetcode第13题:罗马数转为十进制数

利用等价换算法将罗马数转为十进制数 class Solution:def romanToInt(self, s: str) -> int:roman_map{I:1,V:5,X:10,L:50,C:100,D:500,M:1000}before_val,countroman_map[s[0]],0for c in s:valroman_map[c]if val<before_val:countvalelse:countcount-val2*(val-befor…

echarts 柱形图如何让其中一个柱子的颜色跟其他柱子不同

如何让其中一个柱子的颜色跟其他柱子不同 series: [{data: [120,// 使用对象的形式&#xff0c; value代表当前值, itemStyle设置样式{value: 200,itemStyle: {color: #a90000}},150,80,70,110,130],type: bar}]设置单个柱子颜色&#xff1a; 柱形图单个柱子颜色: https://e…

c 语言 三元搜索 - 迭代与递归(Ternary Search)

计算机系统使用不同的方法来查找特定数据。有多种搜索算法&#xff0c;每种算法更适合特定情况。例如&#xff0c;二分搜索将信息分为两部分&#xff0c;而三元搜索则执行相同的操作&#xff0c;但分为三个相等的部分。值得注意的是&#xff0c;三元搜索仅对排序数据有效。在本…

video.js自定义预览组件-旋转、下载、画中画、放大缩小功能

使用video.js实现视频播放功能 效果图 - 这里以弹窗展示为例 注意&#xff1a;记得安装video.js插件&#xff01;&#xff01;&#xff01; 代码 父级使用&#xff1a; videoPreview.vue文件 <!-- 视频预览组件 --> <template><el-dialogid"previewFi…

【战略前沿】丹麦正在建造一台英伟达人工智能超级计算机

【原文】Denmark is building an Nvidia AI supercomputer 【作者】Linnea Ahlgren 它将于今年上线&#xff0c;并以新的量子计算软件为特色。 过去一年最大的赢家——芯片制造商英伟达&#xff08;Nvidia&#xff09;和制药制造商诺和诺德&#xff08;Novo Nordisk&#xff0…

【C语言】linux内核pci_alloc_irq_vectors

一、注释 代码中包含了几个关于PCI&#xff08;外围组件互联&#xff09;设备中断请求&#xff08;IRQ&#xff09;向量分配的函数&#xff0c;以及内联函数声明&#xff0c;下面是对这些函数的中文注释&#xff1a; static inline int pci_alloc_irq_vectors_affinity(struc…

曲线生成 | 图解Reeds-Shepp曲线生成原理(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 什么是Reeds-Shepp曲线&#xff1f;2 Reeds-Shepp曲线的运动模式3 Reeds-Shepp曲线算法原理3.1 坐标变换3.2 时间翻转(time-flip)3.3 反射变换(reflect)3.4 后向变换(backwards) 4 仿真实现4.1 ROS C实现4.2 Python实现4.3 Matlab实现 0 专栏介绍 &#x1f5…

【竞技宝】DOTA2:lou神带队速推 AR力克Zero晋级决赛

北京时间2024年3月24日,DOTA2梦幻联赛S23中国区预选赛正在进行之中,昨日进行了本次预选赛的胜者组决赛Zero对阵AR。本场比赛双方前两局战至1-1平,决胜局AR选出一套前期进攻性十足的阵容早早取得优势,最终AR鏖战三局力克Zero晋级决赛。以下是本场比赛的详细战报。 第一局: Zero…
最新文章