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

目录

引言

队列的性质

队列的基本操作

初始化

判空

销毁

队列的长度

插入

删除

返回队头元素

循环队列

假溢出

空与满的判定

实现 

初始化

插入

判空

销毁

删除

返回队列长度

返回队列头元素

判满


引言

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

队列的性质

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

队列的基本操作

队列的操作和栈的操作类似,下面将以链式队列为例,逐一讲解各个操作。因为是用单链表实现队列,如果仅仅记录队头,那么在插入数据时必须得遍历一遍链表,找到队列的尾,才能链接上新的结点,这是一个不小的消耗,所以,除了对链表的结点封装成一个结构体(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 10typedef 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.mzph.cn/news/768997.shtml

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

相关文章

特征工程 | 数据清洗、异常值处理、归一化、标准化、特征提取

目录 一. 数据清洗1. 数据清洗&#xff1a;格式内容错误数据清洗2. 数据清洗&#xff1a;逻辑错误清洗3. 数据清洗&#xff1a;去除不需要的数据4. 数据清洗&#xff1a;关联性验证 二. 异常值的处理1. 删除2. 填充 三. 归一化和标准化1. 归一化2. 标准化 四. 特征提取1. One-H…

MyBatis是纸老虎吗?(六)

经过前面一些列文章的梳理&#xff0c;我们已将MyBatis框架所需要的资源都准备好了&#xff1a;数据库连接信息储存在Configuration对象中的Environment属性中&#xff08;该对象中有这样几个属性String类型的id&#xff0c;TransactionFactory类型的transactionFactory、DataS…

如何优雅的爬取公众号文章

目录 相关函数库介绍 代码例子 IP池免费送 相关函数库介绍 在合法合规的前提下&#xff0c;爬取微信公众号文章可以使用以下几个Python库&#xff1a; requests&#xff1a;这是一个非常流行的HTTP库&#xff0c;用于发送各种HTTP请求。它简单易用&#xff0c;能够高效地处…

关于序列化和反序列化

什么是序列化&#xff0c;什么是反序列化 简单来说&#xff1a; 序列化&#xff1a;将数据结构或对象转换成二进制字节流的过程反序列化&#xff1a;将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程 为什么要进行序列化 我们要将java对象进行网络传输&a…

TorchAcc:基于 TorchXLA 的分布式训练框架

演讲人&#xff1a;林伟&#xff0c;阿里云研究员&#xff0c;阿里云人工智能平台 PAI 技术负责人 本文旨在探讨阿里云 TorchAcc&#xff0c;这是一个基于 PyTorch/XLA 的大模型分布式训练框架。 过去十年 AI 领域的显著进步&#xff0c;关键在于训练技术的革新和模型规模的快…

详细剖析多线程2----线程安全问题(面试高频考点)

文章目录 一、概念二、线程不安全的原因三、解决线程不安全问题--加锁&#xff08;synchronized&#xff09;synchronized的特性 四、死锁问题五、内存可见性导致的线程安全问题 一、概念 想给出⼀个线程安全的确切定义是复杂的&#xff0c;但我们可以这样认为&#xff1a; 在多…

国际结算-汇出汇款和汇入汇款

目录 汇出汇款业务 汇入汇款 汇出汇款业务 汇出汇款业务是本行接受汇款人的委托,以约定的汇款方式委托海外代理行将一定金额的款项付给指定收款人的业务。用于满足国际间资金汇划结算需求。汇款方式包括电汇、信汇和票汇,目前常用的是电汇。 业务特点,费用少,与信用证和托…

C语言 strcmp

在C语言中&#xff0c;strcmp 函数用于比较两个字符串。它的原型定义在 <string.h> 头文件中。strcmp 函数比较两个字符串直到找到一个不同的字符或者到达字符串的末尾。函数的原型如下&#xff1a; int strcmp(const char *str1, const char *str2); 参数 str1&#…

立体统计图表绘制方法(凸显式环图)

立体统计图表绘制方法&#xff08;凸显式环图&#xff09; 记得我学统计学的时候&#xff0c;那些统计图表大都是平面的框框图&#xff0c;很呆板&#xff0c;就只是表现出统计的意义就好了。在网络科技发展进步的当下&#xff0c;原来一些传统的统计图表都有了进一步的创新。在…

RDGCN翻译

RDGCN翻译 Relation-Aware Entity Alignment for Heterogeneous Knowledge Graphs 面向异质知识图谱的关系感知实体对齐 阅读时间&#xff1a;2024.03.24 领域&#xff1a;知识图谱&#xff0c;知识对齐 作者&#xff1a;Yuting Wu等人 PKU 出处&#xff1a;IJCAI Abstract…

[精选]Kimi到底是什么,将带来什么?

## 阿里通义千问重磅升级&#xff1a;免费开放1000万字长文档处理功能。 Kimi突然的泼天富贵&#xff0c;大家都想沾一把。短期这一块大概率会继续热一段时间。 作为月之暗面的创始人&#xff0c;杨植麟常把他的AGI梦想形容为“登月计划”&#xff0c;长文本就是这个伟大计划…

HarmonyOS NEXT应用开发之听歌识曲水波纹特效案例

介绍 在很多应用中&#xff0c;会出现点击按钮出现水波纹的特效。 效果图预览 使用说明 进入页面&#xff0c;点击按钮&#xff0c;触发水波纹动画。再次点击按钮&#xff0c;停止水波纹动画。 实现思路 本例涉及的关键特性和实现方案如下&#xff1a; 要实现存在两个连续…

基于AWS云服务构建智能家居系统的最佳实践

在当今智能家居时代,构建一个安全、高性能、可扩展和灵活的智能家居系统已经成为许多公司的目标。亚马逊网络服务(AWS)提供了一系列云服务,可以帮助企业轻松构建和管理智能家居系统。本文将探讨如何利用AWS云服务构建一个智能家居系统,并分享相关的最佳实践。 系统架构概述 该…

科研学习|论文解读——这取决于你什么时候搜索(MIS Quarterly,2022)

原文题目 It Depends on When you search 摘要 互联网搜索已被证明对股票价格、公司销售和疫情传播具有强大的预测能力。在研究提出搜索频率作为投资者关注的更直接和及时的衡量指标之后,我们探索了搜索数据的异质性,并解决了当前文献中的几个问题。使用来自谷歌的标准普尔50…

C++ - 类和对象(上)

目录 一、类的定义 二、访问限定符 public&#xff08;公有&#xff09; protected&#xff08;保护&#xff09; private&#xff08;私有&#xff09; 三、类声明和定义分离 四、外部变量和成员变量的区别与注意 五、类的实例化 六、类对象的模型 七、类的this指针…

TCP详解

一、TCP报文段结构 1、源端口号和目的端口号都是16位&#xff0c;范围从&#xff08;1-65535&#xff0c;0不可用&#xff09; 2、序列号&#xff1a;在建立连接时由内核生成的随机数作为其初始值&#xff0c;通过 SYN 报文传给接收端主机&#xff0c;每发送一次数据&#xff0…

C语言数据结构易错知识点(5)(插入排序、选择排序)

插入排序&#xff1a;直接插入排序、希尔排序 选择排序&#xff1a;直接选择排序、堆排序 上述排序都是需要掌握的&#xff0c;但原理不会讲解&#xff0c;网上有很多详尽地解释&#xff0c;本文章主要分享一下代码实现上应当注意的事项 1.直接插入排序&#xff1a; 代码实…

拥抱C++的深度和复杂性,挖掘更多可能 !——《C++20高级编程(第5版)》

&#xff0c;C难以掌握&#xff0c;但其广泛的功能使其成为游戏和商业软件应用程序中最常用的语言。即使是有经验的用户通常也不熟悉许多高级特性&#xff0c;但C20的发布提供了探索该语言全部功能的绝佳机会。《C20高级编程(第5版)》为C的必要内容提供了一个代码密集型、面向解…

Redis 教程系列之Redis Java 使用 Redis(十一)

安装 开始在 Java 中使用 Redis 前&#xff0c; 我们需要确保已经安装了 redis 服务及 Java redis 驱动&#xff0c;且你的机器上能正常使用 Java。 Java的安装配置可以参考我们的 Java 开发环境配置 接下来让我们安装 Java redis 驱动&#xff1a; 首先你需要下载驱动包 下载…

Qt笔记 计时器

下面介绍设计计时器的两种方法&#xff0c;分别是利用信号与槽来使用计时器&#xff0c;利用计时器事件来使用计时器。 1. 利用信号与槽来使用计时器 //方法一(利用信号与槽来使用计时器):QTimer *timer new QTimer(this);timer->start(1000);//周期&#xff0c;计时器每隔…