【数据结构与算法】设计循环队列


  •   🧑‍🎓个人主页:简 料

  •   🏆所属专栏:C++

  •   🏆个人社区:越努力越幸运社区

  •   🏆简       介:简料简料,简单有料~在校大学生一枚,专注C/C++/GO的干货分享,立志成为您的好帮手 ~


C/C++学习路线 (点击解锁)
❤️C语言阶段(已结束)
❤️数据结构与算法(ing)
❤️C++(ing)
❤️Linux系统与网络(队列中…)

文章目录

  • 👑前言
  • 如何设计循环队列
  • 设计循环队列
  • 整体的代码
  • 📯写在最后

👑前言

🚩前面我们 用队列实现了一个栈 ,用栈实现了一个队列 ,相信大家随随便便轻松拿捏,而本章将带大家上点难度,我们来 设计一个循环队列
🚩对于循环队列,重点就在一个 “ 循环 ”,意思也就是该队列首尾相连形成一个环,但其本质还是不变,队列 先进先出 的性质依旧存在,只不过环的大小有限定(限定放多少数据就只能放多少数据)。
🚩那么我们如何来设计这样的一个环,使它既能够像队列一样,又可以体现循环的性质?下面就带大家探讨一波。


如何设计循环队列

  • 那么该如何设计一个循环队列呢?首先第一步当然就是选取一个存储结构来存放数据,是选顺序表的数组呢还是链表呢?

  • 我们先来看看对循环队列的介绍:循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

由以上所说,既然是队尾被连接在队首之后形成一个循环,并且之前我们讲解的队列是选用的链式储存结构,那我们第一个想到的就是选用链式的储存结构来存储数据,因为只要链表的尾节点的next指向头节点就形成了循环,这是很容易想到的。那我们就先对链式这一储存结构进行分析,看到底合不合适。

抽象图:

在这里插入图片描述

  • 既然是队列,那就需要两个指针,一个命名为front指向头,一个命名为rear指向尾。由于是循环队列,刚开始就将空间开好(固定长度),那么此时frontrear都指向同一个节点,如下:

在这里插入图片描述

  • 那么此时入队列的操作过程如下:

在这里插入图片描述

  • 可以看到,rear指向有效数据节点的下一个位置。当队列入满的时候,rear指针此时又与front指针相等了,我们再来看看出队列的过程(出队列数据可以不用抹去,访问不到):

在这里插入图片描述

  • 由上面的两个操作我们不难发现,当队列为空的时候,front指针与rear指针相等,当队列满的时候,front指针与rear指针也相等,这不免就会出现冲突问题:判空和判满将如何判断呢?

  • 这里有两种方案:

    1. 第一个是多创建一个变量来统计数据的个数,当判空和判满的时候根据这个变量就可以实现;
    2. 第二个是多开一个空间(注意不是哨兵位节点),也就是规定长度为多少,开空间的时候开长度加一个空间,如下图:

在这里插入图片描述
可以看到,方案二当循环队列为空的时候,front == rear,当循环队列为满的时候,如下图:

在这里插入图片描述
由此图不难得出,当循环队列为满时,有rear->next == front,所以,方案二对于判空和判满的区分也是挺不错的。不过,大家有没有观察到,对于链式储存结构,无论是方案一还是方案二,都不好找队尾数据,因为它处在rear指针的前一个节点,实在是要找的话,就需要遍历一遍循环队列,或者在一开始就另外再定义一个prev指针,指向rear指针的前一个节点,这些都是比较麻烦的。那么根据此问题,由于数组存储形式支持随机访问,所以下面我们再来看看数组的存储形式怎么样。


  • 对于数组的存储形式,整体上来说与链式存储形式差不太多。由于循环队列的长度是固定的,因此数组的存储形式抛弃了扩容这一弱点。

数组存储形式图:
在这里插入图片描述

  • 有了链式存储的分析判断,不难得出,上图的数组存储形式也会出现判空和判满实现的冲突问题。因此这里也需要解决方案,而数组存储形式的解决方案与链式存储形式的解决方案是相同的,无非就是多定义一个变量来统计数据的个数,或者多开一个空间。多定义一个变量来统计数据的个数固然可以实现,但这里我们选取多开一个空间的形式来进行分析。

如果是多开一个空间,那么当循环队列满时,有以下情况:

在这里插入图片描述
2.
在这里插入图片描述

(注意:frontrear两个指针分别是对应数据的下标 | 设规定的长度为k)

  • 如果是2情况,判满可以判断 rear + 1 == front ? ,但还有1情况rear + 1不会等于front,并且会超出数组的下标范围,因此这里可以对rear + 1取模,也就是判断 (rear + 1) % (k + 1) == front ? 即可。有了这种判断方式,无论rear是否指向数组的最后一个位置,他都可以判断,因为:当rear不是指向数组的尾时,它加一模上一个(k + 1)完全不会受到影响,就相当于是判断 rear + 1 == front ? 一样。而rear指向数组的尾时,这样操作最终就是判断 front == 0 ? 一样。

  • 那数组的判满解决了,判空如何呢?其实判空很简单,只需要判断rear是否等于front即可。

由于是数组存储形式,因此支持下标的随机访问,所以这里获取队头和队尾元素都非常的方便。

  • 如果是获取队头元素,直接返回front指向的数据即可;如果是获取队尾元素,只需要返回rear的前一个位置的数据即可,但是,如果rear此时指向数组的开头又该怎么找队尾元素呢?

在这里插入图片描述

  • 这里我们将 rear + k 然后模上 k + 1 即可。因为,如果rear是指向数组开头,rear加上k后刚好指向数组的最后一个位置,也就是循环队列的队尾。如果rear不是指向数组开头, (rear + k) % (k + 1) 刚好是rear的前一个位置,所以,这样的取模的方式,完美的实现了取队尾的功能。

综上来看,链式储存结构与数组储存结构还是数组储存结构更胜一筹,因为在取队尾元素这一块数组储存结构碾压链式储存结构,所以,这里我们选择数组储存结构来实现循环队列。

  • 这里我们采用多开一个空间的方式来实现。

  • 确定了以数组存储结构来存储数据后,那么该如何实现数据的入队和出队呢?

首先来看看数据入队列示意图:

在这里插入图片描述
在这里插入图片描述

  • 对于第一张图的入队列,就是在当前rear位置插入,然后rear加加即可。但是对于第二张图,rear在最后位置的时候插入数据,此时rear加加就超过数组的长度了,因此我们每次在rear加加后需要执行一下 rear %= (k + 1) 这条语句,这样做,rear在数组的尾插入数据时,会将rear转算成0,也就是指向数组的开头位置,如果rear不在数组的尾插入数据,此时rear的位置不会受任何的影响。

然后再来看看数据出队列示意图:

在这里插入图片描述

在这里插入图片描述

  • 对于第一张图,每次删除其实只需要front加加即可,如果为空就不能删了。对于第二张图,当front指向数组的最后一个位置时,出队过后,front加加会越过数组,因此,每次出队列完成,都需要执行 front %= (k + 1) 这条语句,情况其实与上面入队列时的rear一样。

入队列与出队列介绍过了,后面就是一些队列基本的接口操作了。有取队头数据,取队尾数据,判空和判满,还有循环队列的销毁。

接下来就带大家来实现喽!


设计循环队列

  • 这里我们直接以题目的方式来实现,题目链接:-> 传送门 <-

题目描述:设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
在这里插入图片描述
在这里插入图片描述

该题提供的需要我们实现的接口:

typedef struct {} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {}bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {}bool myCircularQueueDeQueue(MyCircularQueue* obj) {}int myCircularQueueFront(MyCircularQueue* obj) {}int myCircularQueueRear(MyCircularQueue* obj) {}bool myCircularQueueIsEmpty(MyCircularQueue* obj) {}bool myCircularQueueIsFull(MyCircularQueue* obj) {}void myCircularQueueFree(MyCircularQueue* obj) {}

接下来,就是对循环队列的一系列功能接口的实现了:

1.

  • 首先当然是定义一个循环队列的结构体。

相关代码实现:

// 循环队列的结构
typedef struct {// 底层存储结构为数组int* a;// 指向队头int front;// 指向队尾int rear;// 规定的循环队列的长度// 这里存放一下规定的循环队列的长度是为了后面取模更方便int k;
} MyCircularQueue;

2.

  • 然后便是对一个循环队列的创造。

  • 这里开辟一段连续的空间,可以存放规定的长度加一个数据,但其实总有一个空间是用不上的。

  • 然后将frontrear都指向循环队列的队头,就是置为0

  • 最后将规定的长度存起来即可。

相关代码实现:

// 循环队列的创造(构造器),规定创造的循环队列的长度为k
MyCircularQueue* myCircularQueueCreate(int k) {// 先开辟一个循环队列的空间MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));// assert防止开辟空间失败assert(obj);// 多开一个空间,也就是开辟(k + 1)个数据空间obj->a = (int*)malloc(sizeof(int) * (k + 1));// 开始头指针和尾指针都指向0(也就是队头)obj->front = obj->rear = 0;// 存放规定的队列长度obj->k = k;return obj;
}

3.

  • 这里是入队列的实现。
  • 入队列就是在当前的rear位置插入数据,然后rear加加。
  • 根据前面的解析,入队列之后都需要取模一次,避免rear越过数组。(当然也可以通过判断的方式来处理特殊情况)
  • 当队列为满的时候就不能入队列了。

相关代码实现:

// 入队列插入数据,插入成功返回true,不成功(队列已满)那就返回false
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {// 如果此时循环队列已经满了,说明入队列不能进行,直接返回 falseif (myCircularQueueIsFull(obj)) return false;// 在rear位置上插入数据,然后rear加加obj->a[obj->rear ++ ] = value;// 每次都模上个 (k + 1)obj->rear %= (obj->k + 1);// 入队列成功返回truereturn true;
}

4.

  • 接下来是出队列操作。
  • 出队列就是front加加,根据前面的讲解,每次出队列后都要记得需模上(k + 1)。(当然也可以通过判断的方式来处理特殊情况)
  • 当队列为空的时候就不能出队列了。

相关代码实现:

// 出队列删除数据,删除成功返回true,不成功(队列为空)那就返回false
bool myCircularQueueDeQueue(MyCircularQueue* obj) {// 当循环队列为空,说明不能再出队列了,表明出队列失败,直接返回falseif (myCircularQueueIsEmpty(obj)) return false;// 出队列直接front加加obj->front ++ ;// 每次都模上个 (k + 1)obj->front %= (obj->k + 1);// 出队列成功返回truereturn true;
}

5.

  • 获取队头数据,直接返回front此时指向的那个位置上的数据即可。
  • 如果循环队列为空的话,就取不了了,依题目要求直接返回-1

相关代码实现:

// 获取队头元素,如果队列为空,返回-1
int myCircularQueueFront(MyCircularQueue* obj) {// 如果循环队列为空,说明没有数据,直接返回-1if (myCircularQueueIsEmpty(obj)) return -1;return obj->a[obj->front];
}

6.

  • 获取队尾数据,根据前面的分析,采用取模的方式获取。(当然也可以通过判断的方式)
  • 如果循环队列为空的话,就取不了了,依题目要求直接返回-1

相关代码实现:

// 获取队尾元素,如果队列为空,返回-1
int myCircularQueueRear(MyCircularQueue* obj) {// 如果循环队列此时为空,直接返回-1if (myCircularQueueIsEmpty(obj)) return -1;// 以取模的方式来获取return obj->a[(obj->rear + obj->k) % (obj->k + 1)];
}

7.

  • 对于判空,就是判断front是否等于rear即可。

相关代码实现:

// 判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front == obj->rear;
}

8.

  • 对于判满,根据前面的分析,也是通过取模的方式来实现的。(当然也可以通过判断的方式来处理特殊情况)

相关代码实现:

// 判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % (obj->k + 1) == obj->front;
}

9.

  • 最后就是销毁循环链表,malloc了几次,就free(释放)几次。

相关代码实现:

// 销毁循环队列
void myCircularQueueFree(MyCircularQueue* obj) {// 有内到外依次释放空间// 释放数组free(obj->a);// 释放循环队列free(obj);
}

整体的代码

// 循环队列的结构
typedef struct {// 底层存储结构为数组int* a;// 指向队头int front;// 指向队尾int rear;// 规定的循环队列的长度int k;
} MyCircularQueue;// 在这声明一下判空和判满
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);// 循环队列的创造(构造器),规定创造的循环队列的长度为k
MyCircularQueue* myCircularQueueCreate(int k) {// 先开辟一个循环队列的空间MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));// assert防止开辟空间失败assert(obj);// 多开一个空间,也就是开辟(k + 1)个数据空间obj->a = (int*)malloc(sizeof(int) * (k + 1));// 开始头指针和尾指针都指向0(也就是队头)obj->front = obj->rear = 0;// 存放规定的队列长度obj->k = k;return obj;
}// 入队列插入数据,插入成功返回true,不成功(队列已满)那就返回false
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if (myCircularQueueIsFull(obj)) return false;obj->a[obj->rear ++ ] = value;obj->rear %= (obj->k + 1);return true;
}// 出队列删除数据,删除成功返回true,不成功(队列为空)那就返回false
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)) return false;obj->front ++ ;obj->front %= (obj->k + 1);return true;
}// 获取队头元素,如果队列为空,返回-1
int myCircularQueueFront(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)) return -1;return obj->a[obj->front];
}// 获取队尾元素,如果队列为空,返回-1
int myCircularQueueRear(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)) return -1;return obj->a[(obj->rear + obj->k) % (obj->k + 1)];
}// 判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front == obj->rear;
}// 判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % (obj->k + 1) == obj->front;
}// 销毁循环队列
void myCircularQueueFree(MyCircularQueue* obj) {free(obj->a);free(obj);
}

📯写在最后

💝设计一个循环队列还是有些复杂的呢,不过看过本文章,相信大家也可以轻松拿捏。
❤️‍🔥后续将会持续输出有关数据结构与算法的文章,你们的支持就是我写作的最大动力!

感谢阅读本小白的博客,错误的地方请严厉指出噢~

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

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

相关文章

2022年第十三届蓝桥杯比赛Java B组 【全部真题答案解析-第一部分】

最近回顾了Java B组的试题&#xff0c;深有感触&#xff1a;脑子长时间不用会锈住&#xff0c;很可怕。 兄弟们&#xff0c;都给我从被窝里爬起来&#xff0c;赶紧开始卷&#xff01;&#xff01;&#xff01; 2022年第十三届蓝桥杯Java B组(第一部分 A~F题) 目录 一、填空题 …

详解基于快速排序算法的qsort的模拟实现

目录 1. 快速排序 1.1 快速排序理论分析 1.2 快速排序的模拟实现 2. qsort的模拟实现 2.1 qsort的理论分析 2.2 qsort的模拟实现 qsort函数是基于快速排序思想设计的可以针对任意数据类型的c语言函数。要对qsort进行模拟实现&#xff0c;首先就要理解快速排序。 1. 快…

【数据结构】堆的创建

文章目录 一、堆的概念及结构1、什么是堆2、堆的性质3、堆的结构及分类 二、堆的创建1、堆向下调整算法2、堆向上调整算法3、堆的创建&#xff08;向上调整算法&#xff09; 一、堆的概念及结构 1、什么是堆 堆就是以二叉树的顺序存储方式来存储元素&#xff0c;同时又要满足父…

智慧城市中的智慧生活:便捷、舒适与高效

目录 一、智慧城市中的智慧生活概述 二、智慧生活带来的便捷性 1、智慧交通的便捷出行 2、智慧购物的轻松体验 3、智慧政务的一站式服务 三、智慧生活带来的舒适性 1、智慧环境的绿色宜居 2、智慧医疗的健康保障 3、智慧教育的均衡发展 四、智慧生活带来的高效性 1、…

CSS案例-5.margin产品模块练习

效果1 相关数据 整体长&#xff1a;298px&#xff0c;高&#xff1a;415px 效果2 知识点 外边距margin 块级盒子水平居中 条件&#xff1a; 必须有宽度左右外边距设为auto 三种写法&#xff1a; margin-left&#xff1a;auto&#xff1b;margin-right&#xff1a;auto&…

高架学习笔记之信息安全基础

目录 一、信息安全基础 1.1. 概念 1.2. 信息存储安全 1.3. 网络安全 二、信息安全系统的组成框架 三、信息加解密技术和数字签名 四、访问控制技术 五、信息安全的保障体系与评估方法 5.1. 计算机信息系统安全保护等级 5.2. 安全风险管理 一、信息安全基础 1.1. 概念 …

linux安装WordPress问题汇总,老是提示无法连接到FTP服务器解决方案

最近在做一些建站相关的事情&#xff0c;遇到一些大大小小的问题都整理在这里 1.数据库密码和端口&#xff0c;千万要复杂一点&#xff0c;不要使用默认的3306端口 2.wordpress算是一个php应用吧&#xff0c;所以安装流程一般是 apache http/nginx——php——mysql——ftp &…

MQTT和Modbus的物联网网关协议区别分析

MQTT和Modbus的物联网网关协议区别分析 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;与Modbus是两种广泛应用在物联网环境中的通信协议&#xff0c;它们各自具有独特的优势和适用场景&#xff0c;下面将从多个维度对这两种网关协议进行详细区别分析。 首…

win10 使用 IIS 搭建 FTP

0. 背景 首先描述一下需求&#xff0c;大概情况就是&#xff0c;视频文件是存储在笔记本电脑里面&#xff0c;然后偶尔需要投屏到电视上。之前考虑过是否可以通过U盘拷贝的方式&#xff0c;后来发现不行&#xff0c;这样太局限了&#xff0c;需要先明确可能用到的教程&#xf…

汽车功能安全整体方法

摘 要 ISO26262道路车辆功能安全标准已经制定实践了多年&#xff0c;主要目标是应对车辆的电子和电气&#xff08;E/E&#xff09;系统失效。该方法践行至今&#xff0c;有些系统功能安全方法已经成熟&#xff0c;例如电池管理系统&#xff08;BMS&#xff09;&#xff0c;并且…

Latex插入pdf图片,去除空白部分

目录 参考链接&#xff1a; 流程&#xff1a; 参考链接&#xff1a; ​科研锦囊之Latex-如何插入图片、表格、参考文献 http://t.csdnimg.cn/vpSJ3 流程&#xff1a; Latex的图片插入支持PDF文件&#xff0c;这里笔者建议都使用PDF文件进行图片的插入&#xff0c;因为PDF作…

基于spring boot实现接口管理平台

数据库结构 /* Navicat MySQL Data TransferSource Server : localhost_3306 Source Server Version : 50724 Source Host : localhost:3306 Source Database : interfaceTarget Server Type : MYSQL Target Server Version : 50724 File Encoding…

Android14之HIDL报错:Invalid sparse file format at header magic(一百九十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

学习笔记Day8:GEO数据挖掘-基因表达芯片

GEO数据挖掘 数据库&#xff1a;GEO、NHANCE、TCGA、ICGC、CCLE、SEER等 数据类型&#xff1a;基因表达芯片、转录组、单细胞、突变、甲基化、拷贝数变异等等 常见图表 表达矩阵 一行为一个基因&#xff0c;一列为一个样本&#xff0c;内容是基因表达量。 热图 输入数据…

Java语言: JVM

1.1 内存管理 1.1.1 JVM内存区域 编号 名字 功能 备注 1 堆 主要用于存放新创建的对象 (所有对象都在这里分配内存) jdk1.8之后永久代被替换成为了元空间&#xff08;Metaspace&#xff09; 2 方法区(加、常、静、即) 被虚拟机加载的类信息(版本、字段、方法、接口…

Git——GitHub远端协作详解

目录 Git&GitHub1、将内容Push到GitHub上1.1、在GitHub上创建新项目1.2、upstream1.3、如果不想要相同的分支名称 2、Pull下载更新2.1、Fetch指令2.2、Fetch原理2.3、Pull指令2.4、PullRebase 3、为什么有时候推不上去3.1、问题复现3.2、解决方案一&#xff1a;先拉再推3.3…

孙溟㠭于北京大学北大书店现场创作

孙溟㠭于北京大学北大书店现场创作篆刻作品 孙溟㠭北大书店现场创作 孙溟㠭于北京大学北大书店展览期间现场创作 孙溟㠭北京大学篆刻展现场创作 图文/氿波

Nadaraya-Watson核回归

目录 基本原理 ​编辑 核函数的选择 带宽的选择 特点 应用 与注意力机制的关系 参考内容 在统计学中&#xff0c;核回归是一种估计随机变量的条件期望的非参数技术。目标是找到一对随机变量 X 和 Y 之间的非线性关系。 在任何非参数回归中&#xff0c;变量 Y 相对于变量…

【c++】c++基本语法知识-命名空间-输入输出-缺省参数

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;c_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.命名空间 1.2 命名空间定义 1.3 命名空间使用 命名空间的三种使用方式 2.C输入&输出 std命名空间的使用惯例 3.缺省参数 3…

linux scp 免密传输配置 案例

目录 说明准备实现结果步骤生成RSA公钥和私钥查看密钥生成结果将公钥传输到目标服务器 额外内容自动备份文件脚本定时删除备份文件 说明 日常工作中常常会使用到ssh 的scp命令进行文件传输。有时候甚至使用自定义的脚本配合定时任务来对文件进行异地备份&#xff0c;那么此时就…