循环队列详解!!c 语言版本(两种方法)双向链表和数组法!!

目录

1.什么是循环队列

2.循环队列的实现(两种方法)

第一种方法 数组法

1.源代码

2.源代码详解!!

1.创造队列空间和struct变量

2.队列判空

3.队列判满(重点)

4.队列的元素插入

5.队列的元素删除

6.队列的头元素

7.队列的尾元素(重点)

8.队列空间的释放

第二种方法 双向循环链表法

1.源代码

2.源代码详解!!

 1.创造哨兵位 和 struct 变量

2. 队列判空和队列判满

3.元素插入

4.元素删除

5.头元素

6.尾元素

7.空间的释放

三关于这两种方法的差距 

3.循环队列有什么用


1.什么是循环队列

首先关于循环队列,你得先知道什么是队列,详情可以去看看我的另一篇博客

数据结构 栈与队列详解!!-CSDN博客

这里简单的说一下,队列就是字面意思,你在银行排队,你不是什么VIP只是普通人,那你先排队那你就先得服务,队列就是 你先插入的数字(入队),就得先放出(出队)。大致就是这个意思。

而循环队列呢?他的原理也是队列,但和队列不同的地方在于,他的空间是固定的。在一个空间里进行重复的入队出队。

这是一张循环队列的大致图。

其中和队列一样有两个指针指向头和尾。添加实在尾针处加,而删除就是将队首的元素删除。为什么循环呢?就是在你的尾指针走到最后一格的时候(就是总共有5个空间,你的尾指针已经走到5并且已经插入数据)你的队列就满了,这时候循环队列已经不能插入数据,只能删除数据。

删除数据就是把你的头指针的元素删除,如果删除走到尾指针,头和尾在一个位置了。

所以综上可以抽象出,当你的头和尾指针走到一起的时候,这时候队列是空。

但你的尾走到底 头元素的上一个,那就说明你的队列已充满。

当队尾追到了队头那就是满,当队头追到了队尾那就是空

2.循环队列的实现(两种方法)

第一种方法 数组法

数组法采用的是 多开辟一个空间,用来做假溢出,判断队列是否充满,当然还有一种定size法,大家可以讨论一下!!

1.源代码

typedef struct {int* a;int front;int rear;int k;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));obj->a = (int*)malloc(sizeof(int) * (k + 1));obj->k = k;obj->front = 0;obj->rear = 0;return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front == obj->rear;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if (myCircularQueueIsFull(obj)){return false;}obj->rear %= obj->k + 1;obj->a[obj->rear] = value;obj->rear++;return true;
}bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return false;}obj->front %= obj->k + 1;obj->front++;return true;
}int myCircularQueueFront(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->a[obj->front];
}int myCircularQueueRear(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->a[obj->rear - 1];
}void myCircularQueueFree(MyCircularQueue* obj) {free(obj->a);obj->a = NULL;free(obj);obj = NULL;
}

2.源代码详解!!

1.创造队列空间和struct变量
typedef struct {int* a;int front;int rear;int k;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));obj->a = (int*)malloc(sizeof(int) * (k + 1));obj->k = k;obj->front = 0;obj->rear = 0;return obj;
}

 关于 匿名typedef struc 就是定义了一个类型名就叫做MyCircularQueue。

里面的元素第一个用来开辟空间,第二个定义头指针,第三个定义尾指针,第4个就是空间的大小。(这里的指针并不是真的指针,而是对于一个数组来说的下标)

关于 创造队列的空间,首先既然他是一个结构体变量,那得先给他开辟一个空间用来给里面的元素地址存储

第二因为你是要在一个数组里进行循环队列,那你就得开辟一块数组的空间。(k+1是因为这里采用的是多开辟一格进行判满)。

第三给结构体里的变量赋初始值。 

2.队列判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front == obj->rear;
}

 关于队列的判空,就是两个队头和队尾指针如果重叠时,两者应该是满了或者空了的关系,所以两者的判断表达式不能相同,所以下面的判满得想到另一个表达式才能保证队列不报错(当队尾追到了队头那就是满,当队头追到了队尾那就是空)

3.队列判满(重点)
bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % (obj->k + 1) == obj->front;
}

 这里关于队列的判满,采用的是这一串表达式的原因是,假设这时你开辟的有元素的空间是3,那现在的K就是3,但由于采用的是多开辟一格来判满的方法,那就是代表了开辟了4个空间。

4个空间对应的下标为0 1 2 3.那为什么要改两个指针rear和k加一呢?假设这时你的rear和front 都还在下标为 0 的位置,如果你不给 rear 加一 ,那你得出的结果就是 0 % 4 == 0.

这样导致的后果就是和 上面判空的条件 是一样的 0 == 0.会导致插入操作错误,会一直判满从而不能进行插入。

而如果你给 rear 和 k 都加一 就变成了 1 % 4 == 1.而这时 front 是 = 0的

就不会出现误判的情况。只有当你的 rear 走到你多开辟的那一块空间后才会出现判满的情况。(这只是当 front 没变的情况下的讨论,关于 front改变后的讨论,你可以构思一下大致相同。)

4.队列的元素插入
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if (myCircularQueueIsFull(obj)){return false;}obj->rear %= obj->k + 1;obj->a[obj->rear] = value;obj->rear++;return true;
}

 队列的插入就是在rear的 下标初进行插入。首先你要进行判满,是因为循环队列如果满了以后就不会在追加新的元素,就很像游戏里的背包,满了以后就不能获得物品。为什么要让rear % k+1呢?因为当你的元素走到最后一格的下一格,这时候是越界访问的。因为是循环队列,所以可以把他重新变到下标为 0 的位置。

我们的rear 的位置,每次都会加1 所以每次是在rear 的位置插入数据。

5.队列的元素删除
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return false;}obj->front++;obj->front %= obj->k + 1;return true;
}

 元素删除,首先如果你的队列为空那就删除不了了,返回false。

如果不为空,那front++ 即可,并且和 rear 一样要 % k+1,是为了使队列变成循环。如果不这样就会越界访问并且与题意不符(循环)。

要先++ 的原因是,如果你后++ 就会导致,如果此时你的front已经处于最后一个位置,

后++就会导致取头元素时 越界。如果先++ ,然后在%k+1 这样在出了最后一格以后可以及时,变回0 下标,不会越界。

6.队列的头元素
int myCircularQueueFront(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->a[obj->front];
}

 返回头元素相较简单,只要返回此时front 位置的元素即可。

7.队列的尾元素(重点)
int myCircularQueueRear(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}//if (obj->rear == 0)//{//	return obj->a[obj->k];//}//else//{//	return obj->a[obj->rear - 1];//}return obj->a[((obj->rear - 1) + (obj->k + 1)) % (obj->k + 1)];
}

 

 和头元素的不同点在于,队尾元素不能直接取队尾的下标。

因为由前面的插入操作我们可以知道,在rear位置插入数据后,rear是要进行++的,此时在rear下标里是没有元素的,队尾实际上是在rear 的前一个下标的位置。

但如果你只用 obj->a[obj->rear - 1] 来访问的话(如果你的rear是先%k +1,再++是可以的),但如果是先++,再%就不可以了,你可以思考一下当rear == 0,的时候,那你访问的就是obj->[-1] 那就是未知访问,错误。有两种方法。

第一种,判断当rear == 0的时候,你可以直接选择返回 最后一格 obj->k 的元素,

rear != 0时就返回 obj->rear - 1的下标的元素。

第二种,直接判断不用 if else,obj->a[((obj->rear - 1) + (obj->k + 1)) % (obj->k + 1)]。

这串表达式的意思就是,因为我们是要返回 rear - 1下标位置的元素,所以我们 rear - 1;

而 加上 k +1的原因是 在 rear -1 的位置下,你加+ 上这个循环队列的大小,那你还是在原地的,这里是为了防止特殊情况 rear = -1 时,rear = -1 原本是想访问 队列最后一个空间的元素,但等于 - 1访问不到,所以 rear = -1 加上 一个 k+1,其实就是等于 k ,k % k +1 = k。所以访问最后一个位置的元素。

8.队列空间的释放
void myCircularQueueFree(MyCircularQueue* obj) {free(obj->a);obj->a = NULL;free(obj);obj = NULL;
}

 将开辟的空间释放即可,但要先释放obj->a 处的空间。

第二种方法 双向循环链表法

1.源代码

typedef struct MyCircularQueue {struct MyCircularQueue* prev;struct MyCircularQueue* next;int val;int k;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* phead = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));phead->next = phead;phead->prev = phead;phead->val = -1;phead->k = k;return phead;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {if (obj->next == obj){return true;}return false;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {MyCircularQueue* cur = obj->next;int size = 1;while (cur != obj){cur = cur->next;size++;}return size == obj->k;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if (myCircularQueueIsFull(obj)){return false;}MyCircularQueue* newnode = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (newnode == NULL){perror("malloc failed");exit(-1);}newnode->val = value;MyCircularQueue* prev = obj->prev;newnode->prev = prev;newnode->next = obj;prev->next = newnode;obj->prev = newnode;return true;
}bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return false;}MyCircularQueue* Next = obj->next;obj->next = Next->next;Next->next->prev = obj;free(Next);Next = NULL;return true;
}int myCircularQueueFront(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->next->val;
}int myCircularQueueRear(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->prev->val;
}void myCircularQueueFree(MyCircularQueue* obj) {MyCircularQueue* cur = obj->next;while (cur != obj){MyCircularQueue* del = cur;cur = cur->next;free(del);del = NULL;}free(obj);obj = NULL;
}

2.源代码详解!!

 1.创造哨兵位 和 struct 变量
typedef struct MyCircularQueue {struct MyCircularQueue* prev;struct MyCircularQueue* next;int val;int k;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* phead = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));phead->next = phead;phead->prev = phead;phead->val = -1;phead->k = k;return phead;
}

 我们定义一个双链表的struct 结构。

然后再定义一个头结点,并且将 变量赋值。

2. 队列判空和队列判满
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {if (obj->next == obj){return true;}return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {MyCircularQueue* cur = obj->next;int size = 0;while (cur != obj){cur = cur->next;size++;}return size == obj->k;
}

 相较于 数组的循环队列,双链表的判空判满就比较简单。空就是 只有一个哨兵位就是空。

满就是用一个size ++ 遍历链表,后如果size == obj->k 就是满。size 必须从 0 开始。

因为不从0 开始 ,总体队列会少一个空间。

3.元素插入
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if (myCircularQueueIsFull(obj)){return false;}MyCircularQueue* newnode = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (newnode == NULL){perror("malloc failed");exit(-1);}newnode->val = value;MyCircularQueue* prev = obj->prev;newnode->prev = prev;newnode->next = obj;prev->next = newnode;obj->prev = newnode;return true;
}

元素的插入就是双链表的尾插。 

4.元素删除
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return false;}MyCircularQueue* Next = obj->next;obj->next = Next->next;Next->next->prev = obj;free(Next);Next = NULL;return true;
}

元素删除就是双链表的头删。 

5.头元素
int myCircularQueueFront(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->next->val;
}

返回 头元素 

6.尾元素
int myCircularQueueRear(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)){return -1;}return obj->prev->val;
}

返回尾元素 

7.空间的释放
void myCircularQueueFree(MyCircularQueue* obj) {MyCircularQueue* cur = obj->next;while (cur != obj){MyCircularQueue* del = cur;cur = cur->next;free(del);del = NULL;}free(obj);obj = NULL;
}

双链表的销毁。 

三关于这两种方法的差距 

首先在数组 方法中,我们并没有采用循环来执行,这样的时间复杂度是0(1),而双向链表中有多处我们采用了while 循环所以时间复杂度是 o(n)。

数组写法中 坑很多需要很多思考,但是总体代码量是偏少的。

而链表虽然简单明了,但代码量是稍微多一点。需要细心检查。

3.循环队列有什么用

应用场景广泛:由于其高效的插入和删除操作、空间利用率高以及能够动态调整队列大小的特性,循环队列在许多领域都有广泛的应用,如操作系统中的任务调度、通信协议中的数据包处理、线程池中的线程管理等。所以循环队列是比较重要的

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

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

相关文章

GIT实践与常用命令---回退

实践场景 场景1 回退提交 在日常工作中,我们可能会和多个同事在同一个分支进行开发,有时候我们可能会出现一些错误提交,这些错误提交如果想撤销,可以有两种解决办法:回退( reset )、反做(revert) keywords:reset、rev…

2023软件测试的4个技术等级,你在哪个级别?

最近,我们讨论了软件测试工程的的分级,大家都贡献了自己的想法,对于大家来说,软件测试人的分级其实也代表了我们的进阶方向,职业发展。总体来说,测试工程师未来发展有三个方向: 技术精英 行业专…

层次分析法--可以帮助你做决策的简单算法

作用 层次分析法是一个多指标的评价算法,主要用来在做决策时,给目标的多个影响因子做权重评分。特别是那些需要主观决策的、或者需要用经验判断的决策方案,例如: 买房子(主观决策)选择旅游地(…

android11 申请所有文件访问权限

Android 11 引入了强制执行分区存储的限制,导致应用默认不能访问外部文件。 针对以前涉及较多文件的操作,可采用申请所有文件访问权限的方式来解决这一问题,实现方式如下。 (虽然这样做安全性低,官方并不推荐这样&…

Shell判断:模式匹配:case(三)

系统管理工具箱 1、需求:Linux提供的丰富的管理命令,用户管理,内存管理,磁盘管理,进程管理,日志管理,文件管理,软件管理,网络管理等等数十个工具包。如果你能通过shell编…

【代码随想录】算法训练计划30

【代码随想录】算法训练计划30 1、51. N 皇后 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n ,…

分布式锁3: zk实现分布式锁

一 zk 实现分布式锁 1.1 zk分布式操作命令 1.指令: ls / get /zookeeper create /aa "test" delete /aa set /aa "test1" 2..znode节点类型: 永久节点:create /pa…

优秀智慧园区案例 - 上海世博文化公园智慧园区,先进智慧园区建设方案经验

一、项目背景 世博文化公园是上海的绿色新地标,是生态自然永续、文化融合创新、市民欢聚共享的大公园。作为世博地区的城市更新项目,世博文化公园的建设关乎上海城市风貌、上海文化展示、城市生态环境、市民游客体验、上海服务品牌等,被赋予…

依托数据、平台、知识增强等优势 夸克大模型大幅降低问答幻觉率

“大模型时代,夸克有巨大机会创造出革新性搜索产品。”11月22日,夸克大模型公布了其面向搜索、生产力工具和资产管理助手的大模型技术布局。数据显示,夸克千亿级参数大模型登顶C-Eval和CMMLU两大权威榜单,夸克百亿级参数大模型同样…

电大搜题——让学习变得轻松高效

作为一名现代学者,您一定时刻关注着教育领域的进展和创新。今天,我将向大家介绍一个名为“电大搜题”的神奇工具,它将为您的学习之路带来一场完美的革命。 在快节奏的现代社会中,学习已经成为每个人追求成功的必经之路。然而&…

【数据结构】动态顺序表详解

目录 1.顺序表的概念及结构 2.动态顺序表的实现 2.1创建新项目 2.2动态顺序表的创建 2.3接口的实现及测其功能 2.3.1初始化 2.3.2尾插 2.3.3头插 2.3.4尾删&头删 2.3.5打印&从任意位置插入 2.3.6删除任意位置的数据 2.3.7查找 2.3.8销毁顺序表 3.结语 He…

java项目之社区互助平台(ssm+vue)

项目简介 社区互助平台实现了以下功能: 1、一般用户的功能及权限 所谓一般用户就是指还没有注册的过客,他们可以浏览主页面上的信息。但如果有中意的社区互助信息时,要登录注册,只有注册成功才有的权限。2、管理员的功能及权限 用户信息的添…

react大文件上传

目录 大文件上传优点: 大文件上传缺点: 大文件上传原理: 为什么要用md5 实现流程: 部分代码1: 部分代码2:​ 大文件上传优点: 文件太大分片上传能加快上传速度,提高用户体验能断点续传 如果上次上传失败…

简单工程模式

代码实现 //simpleFactory.h #ifndef _SimpleFactory_H_ #define _SimpleFactory_H_#include <iostream> #include <exception> #include <string>using namespace std;class Operation { protected:double _numberA 0;double _numberB 0; public:Operat…

MybatisX插件使用

Mybatis X插件 MybatisX 是一款基于 IDEA 的快速开发插件&#xff0c;为效率而生。MybatisX官网&#xff1a;https://baomidou.com/pages/ba5b24/#%E5%8A%9F%E8%83%BD安装方法&#xff1a;打开 IDEA&#xff0c;进入 File -> Settings -> Plugins&#xff0c;输入 mybat…

三维控件中定位一个点_vtkPointWidget

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 demo解决问题&#xff1a;允许用户使用三维光标在三维空间中定位一个点。关键类vtkPointWidget , 光标具有轮廓边界框、轴对齐十字准线和轴阴影&#xff…

AD7021C 触摸感应加灯光调节芯片IC 可用于触摸台灯、触摸玩具灯等

AD7021C触摸感应 IC 是为实现人体触摸界面而设计的集成电路。可替代机械式轻触按键&#xff0c;实现防水防尘、密封隔离、坚固美观的操作界面。使用该芯片可以实现 LED 灯光亮度调节&#xff0c;方案所需的外围电路简单&#xff0c;操作方便。确定好灵敏度选择电容&#xff…

【华为OD题库-033】经典屏保-java

题目 DVD机在视频输出时&#xff0c;为了保护电视显像管&#xff0c;在待机状态会显示"屏保动画”&#xff0c;如下图所示,DVD Logo在屏幕内来回运动&#xff0c;碰到边缘会反弹:请根据如下要求&#xff0c;实现屏保Logo坐标的计算算法 1、屏幕是一个800 * 600像素的矩形&…

Vue3 provide 和 inject 实现祖组件和后代组件通信

provide 和 inject 能够实现祖组件和其任意的后代组件之间通信&#xff1a; 一、provide 提供数据 我们在祖组件中使用provide 将数据提供出去。 使用provide 之前需要先进行引入&#xff1a; import { provide } from "vue"; 语法格式如下&#xff1a; provide(&q…

山西电力市场日前价格预测【2023-11-23】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-11-23&#xff09;山西电力市场全天平均日前电价为148.77元/MWh。其中&#xff0c;最高日前电价为420.40元/MWh&#xff0c;预计出现在18:00。最低日前电价为0.00元/MWh&#xff0c;预计出…