数据结构——循环队列的实现

💞💞 前言

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:数据结构学习笔记
💥对于数据结构顺序表、链表、堆有疑问的都可以在上面数据结构的专栏进行学习哦~ 有问题可以写在评论区或者私信我哦~

之前我们学习过数据结构中的栈和队列,详情可点击这里数据结构——lesson5栈和队列详解进行查看🥳🥳,队列是一种先进先出的结构,但是我们之前讲的队列都是类似于线性的物理结构,这次我们所介绍的队列则是一直类似于环状的循环结构,它依旧保持着队列的特性——先进先出。

1.循环队列的介绍

循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

循环队列的实现应该支持如下操作:

  1. MyCircularQueue(k): 构造器,设置队列长度为 k 。
  2. Front: 从队首获取元素。如果队列为空,返回 -1 。
  3. Rear: 获取队尾元素。如果队列为空,返回 -1 。
  4. enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  5. deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  6. isEmpty(): 检查循环队列是否为空。
  7. isFull():检查循环队列是否已满。

题目链接——循环队列OJ题大家也可以在学习完栈和队列后自己尝试写一写。

在这里插入图片描述

2.循环队列实现思路分析

首先根据题目要求,队列长度为k,所以一开始我们要使用malloc开辟k个节点的空间,而不是和之前的队列一样在增加数据时再开辟节点,循环队列的长度是固定的,最开始就已经开辟好了,所以不需要再在使用时开辟节点空间。
在这里插入图片描述
如上图所示,已经在内存中开辟了完整的空间,最开始队列的头尾指针都指向开始的位置,每加一个元素,rear指针也就是尾指针的位置就往后挪动一位,删除数据就把头指针front往后挪一位,当rear指针指向最后一个元素时,队列满了,此时的判断条件应该时rear->pNext = front;
也就是说rear的下一个位置是front,如下图所示:
在这里插入图片描述
思考一下:rear指向的是尾部的元素还是尾部元素的下一位呢?
1.zz如果rear指向的是尾部元素,那么在实现时判断循环队列是否满的条件就是rear->pNext = front;
但是💥💥判断循环队列是否为空的条件就不简单是rear == front,因为在插入第一个元素时,front指向该元素,rear指向最后一个元素也就是刚刚插入的第一个元素,因为此时队列中只有一个元素,此时rear == front ,但此时循环队列不为空;
2.但如果循环队列的rear指针指向尾部元素的下一个就好判断了,为空时rear==front;
不为空时rear!=front;即使只有一个元素,rear也不指向该元素而是指向该元素的下一位,但是💥💥在判断循环队列是否满时又出了问题,当循环队列满了的时候,rear指向队尾元素的下一个,此时rear指向front,这不就和为空的条件冲突了吗?
解决办法
🥳🥳1.针对第一种rear指向尾部元素:
可以考虑在创建队列时不单单创建front(队列头指针)和rear(队列尾指针)这两个元素,可以增加一个 size来记录此时队列里的节点个数;
🥳🥳2.针对第二种rear指向尾部元素的下一个位置:
①也可以增加一个size来记录存放的节点个数
②考虑多开辟一个节点的空间(需要k个节点就开辟k+1个节点的空间),剩下一个节点的位置不存放数据,是专门用来防止队列满时rear的下一个元素指向front,如果增加一个空闲的位置,队列满时rear的下一个位置就不再指向front;

💫💫在决定选哪种方法之前,我们先要考虑一下是使用链表来实现还是使用数组也就是顺序表来实现循环队列;当然这里土土会将两种方法都写下来,并和大家一起分析两种方法的优劣之处,以便大家选择合适和喜欢的形式🥰🥰(对于顺序表链表有疑问的可以在土土的数据结构专栏里——数据结构学习笔记 进行查看复习哦~)

3.用单链表实现循环队列

✨✨经过我深思熟虑(其实是随便选的😎😎),决定采用第一种rear指向队尾元素的方法来实现,虽然第二种方法我给出了两种解决办法,但是我发现第一种方法在求队尾元素时异常的方便,只要return rear->data就好了,如果是第二种rear指向队尾元素的下一个,那么我们求队尾元素时还需要找到rear的前一个指针,如果我们使用双向链表就会很简单,但这里我选择使用单链表来实现;

3.1设计队列结构

前面我们提到过设计队列时考虑加上一个size记录存放节点个数以便判断队列是否为空

typedef struct {CQNode* front;CQNode* rear;int size;//记录容量
} MyCircularQueue;

队列的节点和单链表一致:data存放数据;pNext存放下一个节点指针

// 链式结构:表示队列 
typedef int CQDataType;
typedef struct CQListNode
{struct CQListNode* pNext;CQDataType data;
}CQNode;

3.2构造队列(k个节点)

MyCircularQueue(k): 构造器,设置队列长度为 k

这里我们除了需要malloc k个节点外,还需要将这k个节点串联在一起使他们物理结构上成环(对于malloc有疑问的可以查看土土的博客——C语言动态内存函数介绍)代码如下:

//构造k个节点的循环队列
MyCircularQueue* myCircularQueueCreate(int k) {//开辟k个空间并链接k个节点CQNode* kqueue = (CQNode*)malloc(sizeof(CQNode) * k);if (kqueue == NULL){perror("malloc fail1");return NULL;}for (int i = 0; i < k - 1; i++)//注意这里是k-1,因为尾节点需要单独链接头节点{(kqueue + i)->pNext = kqueue + i + 1;}//最后再把尾指针指向的下一位指向头即可成环(kqueue + k - 1)->pNext = kqueue;//开辟循环队列空间MyCircularQueue* queue = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (queue == NULL){perror("malloc fail2");return NULL;}queue->front = kqueue;queue->rear = kqueue;//尾指向最后一个元素,不是最后元素的下一个queue->size = 0;//记录队列大小return queue;}

调试时我们发现成功构造了循环队列:
在这里插入图片描述

后面pNext又回到了最开始的位置说明我们链接成功啦🎉🎉🎉

在这里插入图片描述

我们还将队列的头尾指针与开辟的k个节点做了连接,使得队列的头尾指针指向了k个节点的地址,并将size初始化为0;

3.3 向循环队列插入一个元素

myCircularQueueEnQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。代码如下:

//插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{assert(obj);//判断队列是否满了if(myCircularQueueIsFull(obj)){return false;}//插入队列//1.队列无元素的情况if (myCircularQueueIsEmpty(obj)){obj->front->data = value;obj->rear->data = value;obj->size++;return true;}
//2.队列有元素的情况obj->rear->pNext->data = value;obj->rear = obj->rear->pNext;obj->size++;return true;
}

🥳🥳 ①这里首先要判断队列是否满了,如果满了则不能再插入元素直接返回false;
🥳🥳② 其次因为我们的rear是指向最后一个元素的所以在插入元素时要分两种情况来看——一种只有一个节点的情况头尾指针都需要变;另一种存放多个节点的情况只需要改变尾指针;
🥳🥳③此外增加元素size++;
✨✨④最后判断循环队列是否为空函数在下面介绍;

3.4判断循环队列是否为空

myCircularQueueIsEmpty(): 检查循环队列是否为空。

bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{assert(obj);if (obj->size == 0)//size为0时队列为空return true;return false;
}

3.5判断循环队列是否满了

myCircularQueueIsFull(): 检查循环队列是否已满。

bool myCircularQueueIsFull(MyCircularQueue* obj)
{assert(obj);//?if(obj->size == k)if (obj->rear->pNext == obj->front){//只有一个节点的情况if (obj->size != 0){return true;}}return false;
}

这里我们看到我打了一个?,obj->size ==k是不能判断队列是否满了的,因为k并没有作为参数传给判满函数,我们根本不能使用k,k
只在构造队列时出现过;
✨✨我们还发现这里出现了只有一个节点的情况,因为当队列构造时传的k == 1,也就是整个循环队列只有一个节点,那么无论队列中是否有节点rear的下一个位置始终时front,此时该判断条件不成立,所以我们需要将该情况单独列出来,当rear->pNext 等于front时并且obj->size 不等于0时才能判断队列满了return true;其他情况return false;

3.6从循环队列中删除一个元素

myCircularQueueDeQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。注意队列先进先出,是头删。

//删除数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return false;}//头删,因为是先进先出//1.只有一个元素,此时头尾指针指向同一个位置,都需要改if (obj->front == obj->rear){obj->front = obj->front->pNext;obj->rear = obj->rear->pNext;obj->size--;return true;}//2.多个元素obj->front = obj->front->pNext;obj->size--;return true;
}

这里和插入一个元素一样也要分两种情况,size对应-1;就不过多赘述

3.7 从队首获取元素

myCircularQueueFront: 从队首获取元素。如果队列为空,返回 -1 。

int myCircularQueueFront(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj))return -1;return obj->front->data;
}

3.8获取队尾元素

Rear: 获取队尾元素。如果队列为空,返回 -1 。

int myCircularQueueRear(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return -1;}return obj->rear->data;
}

3.9销毁循环队列

void myCircularQueueFree(MyCircularQueue* obj)
{assert(obj);free(obj->front);free(obj);return;
}

3.10结果如下

在这里插入图片描述

4.用数组实现循环队列

4.1设计队列结构

前面我们提到过设计队列时考虑加上一个size记录存放节点个数以便判断队列是否为空

typedef struct {int front;//首下标int rear;//尾下标int* a;//数组int k;//记录k值
} MyCircularQueue;

这里采用一个结构体封装,并记录数组的首尾下标,并保留一个k来记录k值,以便后面使用。

4.2构造队列(k+1个节点)

MyCircularQueue(k): 构造器,设置队列长度为 k

MyCircularQueue* myCircularQueueCreate(int k) {//开辟循环队列空间MyCircularQueue* queue = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (queue == NULL){perror("malloc fail1");return NULL;}//开辟数组空间queue->a = (int*)malloc(sizeof(int)*(k+1));if (queue->a == NULL){perror("malloc fail2");return NULL;}queue->front = 0;queue->rear = 0;//尾指向最后一个元素的下一个queue->k = k;//记录k,后面使用return queue;}

这里注意开辟了(k+1)个节点空间,采用的是前面说的第二种情况,rear指向最后一个元素的下一个位置。

4.3向循环队列插入一个元素

myCircularQueueEnQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。代码如下:

//插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{assert(obj);//判断队列是否满了if(myCircularQueueIsFull(obj)){return false;}//插入队列//1.队列无元素的情况if (myCircularQueueIsEmpty(obj)){obj->a[obj->front] = value;obj->rear++;obj->rear %= obj->k+1;return true;}
//2.队列有元素的情况obj->a[obj->rear] = value;obj->rear ++;obj->rear %= obj->k+1;return true;
}

这里也分两种情况来插入,💥💥此外还要注意插入元素之后rear要++,但是如果rear超过数组的长度k+1就会造成越界访问,所以这里提供了一个方法:每次rear++之后再与k+1取模运算,这样就可以得到小于等于k的值,不会造成越界访问。

4.4判断循环队列是否为空

myCircularQueueIsEmpty(): 检查循环队列是否为空。

bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{assert(obj);if (obj->front == obj->rear)return true;return false;
}

因为rear指向最后一个元素的下一个元素,所以当rear等于front时,数组为空,此时一个数据也没有插入。

4.5判断循环队列是否满了

myCircularQueueIsFull(): 检查循环队列是否已满。

bool myCircularQueueIsFull(MyCircularQueue* obj)
{assert(obj);if((obj->rear+1)%(obj->k+1)==obj->front){return true;}return false;
}

数组并非像链表那样有pNext指针指向下一个节点,链表可以形成天然的循环结构,而数组却要依靠首尾下标来模拟实现,如图所示有两种情况:
在这里插入图片描述

当删除节点时只需将front++即可,所以front位置可能不是0;

4.6从循环队列中删除一个元素

myCircularQueueDeQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。注意队列先进先出,是头删。

//删除数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return false;}//头删,因为是先进先出obj->front++;obj->front %= obj->k+1;return true;
}

注意这里front也是一样,在+1后可能大于k造成越界,所以要进行取模运算。

4.7 从队首获取元素

myCircularQueueFront: 从队首获取元素。如果队列为空,返回 -1 。

int myCircularQueueFront(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj))return -1;return obj->a[obj->front];
}

4.8获取队尾元素

Rear: 获取队尾元素。如果队列为空,返回 -1 。

int myCircularQueueRear(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return -1;}return obj->a[(obj->rear+obj->k)%(obj->k+1)];
}

这里要注意本来应该返回rear-1;因为rear指向尾节点的下一位,但是如果rear值为0时,再-1就变成-1了,也会造成越界访问,所以也要进行取模运算(rear-1+k+1)%(k+1),即可,因为数组有k+1个节点所以要+(k+1)并%(k+1)

3.9销毁循环队列

void myCircularQueueFree(MyCircularQueue* obj) 
{assert(obj);free(obj->a);free(obj);return;
}

3.10结果如下:

在这里插入图片描述

5.结语

链表来实现循环队列有一个好处就是形成了天然的环形结构,对应数组实现循环队列则需要front,rear不断进行取模运算以防越界;
但是链表实现需要手动将开辟的节点链接在一起,数组则不一样它一开辟就是地址连续的一段空间;
其他的实现链表和数组都差不多;
以上就是循环队列的实现啦~ 大家有什么疑问可以写在评论区或者私信我哦~ 完结撒花~🥳🥳🎉🎉🎉

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

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

相关文章

【回顾练习】静态路由配置综合实验报告

一、分析要求 为R6设备配置公有IP地址&#xff0c;并确保只能进行IP地址配置&#xff0c;无法进行其他配置。为R1-R5设备分配私有IP地址。为R1、R2、R4配置两个环回地址&#xff0c;为R5、R6配置一个环回地址。在R3上配置DHCP服务&#xff0c;以供两台PC自动获取IP地址。使用动…

数字电源浅析

电力电子技术是关于能量转换、调节、控制和管理等方面的学科,而数字电源则是电力电子技术的一种应用,是利用数字电路技术实现电源控制和管理的新型电源。 一、什么是数字电源 数字电源是一种数字控制的电源设备,可以通过数字控制芯片(DSP、MCU等)实现输出电压、电流、功…

向上生长

&#xff08;1&#xff09; 我记得2010年&#xff0c;在中国的苹果应用商店里&#xff0c;充斥的App还有很多&#xff1a;日历App、天气App、电池省电App、记事本App…。但这已经过去了2007-2008-2009三年&#xff0c;这些应用仍然很欢。 我有一个朋友算是中国最早一批开发iOS …

U盘插入电脑没有显示怎么办?

U盘可以备份/转移数据&#xff0c;还可以作为启动盘重装系统&#xff0c;但很多用户在使用u盘的时候&#xff0c;都遇到过这样的问题&#xff0c;就是u盘插入电脑没有反应&#xff0c;在电脑上找不到u盘设备。那么今天就跟大家等下U盘插入电脑没有显示怎么办。 原因/解决方法一…

html5cssjs代码 036 CSS默认值

html5&css&js代码 036 CSS默认值 一、代码二、解释 CSS默认值&#xff08;也称为浏览器默认样式&#xff09;是指当HTML元素没有应用任何外部CSS样式时&#xff0c;浏览器自动为这些元素赋予的一组基本样式。这些样式是由浏览器的默认样式表&#xff08;User Agent sty…

关系型数据库mysql(3)索引

目录 一.索引的概念 二.索引的作用 三.创建索引的原则依据 四.索引的分类 五.索引的创建 5.1 普通索引 5.1.1 直接创建索引 5.1.2 修改表方式创建 5.1.3 创建表的时候指定索引 5.2 唯一索引 5.2.1 直接创建唯一索引 5.2.2 修改表方式创建 5.2.3 创建表的时候指…

机器学习——决策树(三)预剪枝

观前提示&#xff1a;这是本人机器学习决策树内容的第三篇博客&#xff0c;沿用了之前相关的代码&#xff0c;包括信息增益计算函数、结点类、预测函数和分类精度计算函数 完整代码指路 DrawPixel/decisionTree.ipynb at main ndsoi/DrawPixel (github.com) 前两篇博客详见…

Linux学习-进程

目录 进程基本概念 进程相关命令 进程的创建 进程的调度 进程相关函数接口 进程的消亡 实例&#xff1a;创建九个子进程 目录 进程基本概念 进程相关命令 进程的创建 进程的调度 进程相关函数接口 进程的消亡 实例&#xff1a;创建九个子进程 exec函数…

人脸表情识别系统项目完整实现详解——(三)训练MobileNet深度神经网络识别表情

摘要&#xff1a;之前的表情识别系统升级到v3.0版本&#xff0c;本篇博客详细介绍使用PyTorch框架来构建并训练MobileNet V3模型以进行实现表情识别&#xff0c;给出了完整实现代码和数据集可供下载。从构建数据集、搭建深度学习模型、数据增强、早停等多种技术&#xff0c;到模…

计算联合体union的大小

一&#xff1a;联合类型的定义 联合也是一种特殊的自定义类型&#xff0c;这种类型定义的变量也包含一系列的成员&#xff0c;特征是这些成员公用同一块空间&#xff08;所以联合也叫共用体&#xff09; 比如&#xff1a;共用了 i 这个较大的空间 二&#xff1a; 联合的特点 …

MySQL--Buffer Pool

虽然说 MySQL 的数据是存储在磁盘里的&#xff0c;但是也不能每次都从磁盘里面读取数据&#xff0c;这样性能是极差的。为此&#xff0c;Innodb 存储引擎设计了一个缓冲池&#xff08;Buffer Pool&#xff09;&#xff0c;来提高数据库的读写性能。 有了缓冲池后&#xff1a; …

Eclipse For ABAP:安装依赖报错

1.安装好Eclipse后需要添加依赖,这里的地址: https://tools.hana.ondemand.com/latest 全部勾选等待安装结束; 重启后报错:ABAP communication layer is not configured properly. This might be caused by missing Microsoft Visual C++ 2013 (x64) Runtime DLLs. Consu…

小程序云开发实战:通用企业产品信息展示小程序

之前做小程序都是自己搭建数据管理后台&#xff0c;比如我之前做的小程序&#xff1a;一搜就学&#xff0c;就是使用java来做管理后台&#xff0c;小程序做前端展示。但是对于简单的小程序来说&#xff0c;做一套管理后台有点拿大炮打蚊子&#xff0c;所以使用云开发就是不错的…

【开源】SpringBoot框架开发知识图谱构建系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 知识图谱模块2.2 知识点模块2.3 学生测评模块2.4 学生成绩模块 三、系统展示四、核心代码4.1 查询知识点4.2 新增知识点4.3 查询知识图谱4.4 查询学生成绩4.5 查询学生成绩 五、免责说明 一、摘要 1.1 项目介绍 基于J…

论文阅读:Forget-Me-Not: Learning to Forget in Text-to-Image Diffusion Models

Forget-Me-Not: Learning to Forget in Text-to-Image Diffusion Models 论文链接 代码链接 这篇文章提出了Forget-Me-Not (FMN)&#xff0c;用来消除文生图扩散模型中的特定内容。FMN的流程图如下&#xff1a; 可以看到&#xff0c;FMN的损失函数是最小化要消除的概念对应的…

一文了解AI长文本工具:Kimi Chat;与ChatGPT比较对比

一文了解AI长文本工具&#xff1a;Kimi Chat&#xff1b;与ChatGPT比较对比 在人工智能领域&#xff0c;ChatGPT、Claude2.1和Kimi Chat都是备受关注的大型模型。它们在文本生成、理解和处理方面展现了强大的能力。本文将深入探讨这三个工具的核心功能、优劣势以及适用场景&am…

Markdown快速入门(常用技巧)

目录 说明常用书写技巧1.标题分级2.代码块3.字体4.引用语法5.分割线6.图片插入(本地/网络&#xff09;7.超链接8.列表9.表格10.换行&#xff08;两个空格&#xff09;11.插入公式12.脚注 说明 markdown实际上类似于网页&#xff0c;可以根据喜欢设置其样式变化&#xff0c;创建…

iOS18系统中,苹果可能不再使用Siri,转用Gemini

生成式人工智能&#xff08;Generative AI&#xff09;是苹果公司近两年来默默投资的强大人工智能工具。 坊间流有多种传闻&#xff0c;官方最近终于曝光结果&#xff1a;苹果和谷歌正在谈判将 Gemini AI 引入 iPhone&#xff0c;预计将于今年在所有 iOS 18 设备上推出。 到目前…

GitHub Copilot怎么取消付费?

0. 前言 GitHub Copilot非常好用&#xff0c;还没有使用过的同学可以参考教程白嫖一个月&#xff1a;【保姆级】VsCode 安装GitHub Copilot实操教程 GitHub Copilot每月10美元的费用对于一些用户来说可能是一笔不小的开销。如果你已经完成了GitHub Copilot的免费试用&#xf…

【超详细图文讲解】如何利用VMware创建CentOS虚拟机(包括如何更改网络设置 + 远程访问虚拟机方法)

文章目录 前言1. 准备相关软件环境1.1 获取 ISO 镜像包1.2 VMware 的安装 2. 使用 VMware 安装 CentOS3. 初始化虚拟机4. 虚拟机网络的设置4.1 虚拟机的三种网络连接模式桥接模式NAT 模式仅主机模式 4.2 如何更改网络设置 5. 远程访问虚拟机的方法5.1 使用 cmd 进行访问5.2 使用…