【数据结构与算法】详解循环队列:基于数组实现高效存储与访问

news2024/11/15 9:56:14

   

            💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

1b7335aca73b41609b7f05d1d366f476.gif

目录

一、引言

🍃队列的概念

🍃循环队列的概念

🍃为什么使用数组实现循环队列

二、循环队列的结构定义

三、循环队列的接口实现

🍃队列初始化

🍃入队列

🍃出队列

🍃取队首元素

🍃取队尾元素

🍃判空

🍃判满

🍃队列销毁

四、C语言实现代码

🍃Circular_Queue.h   //循环队列头文件

🍃Circular_Queue.c   //循环队列源文件

🍃test.c           //main函数测试文件

测试结果

五、循环队列的应用场景

六、总结


一、引言

🍃队列的概念

队列(Queue)是一种常见的数据结构,它遵循先进先出(FIFO)的原则,即最早进入队列的元素将最先被移除。队列在计算机科学中有广泛的应用,比如任务调度、网络流量控制、打印任务管理等。然而,当我们在处理固定大小的空间时,传统的队列实现可能会遇到空间浪费的问题。为了解决这个问题,我们引入了循环队列(Circular Queue)的概念。

关于队列的详细介绍,请参考前置文章

【数据结构与算法】使用单链表实现队列:原理、步骤与应用-CSDN博客

🍃循环队列的概念

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

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。相比于传统的队列实现,循环队列能够更有效地利用存储空间,并在数组大小固定的情况下实现队列的无限循环。在本文中,我们将详细探讨如何使用数组来实现循环队列,并分析其优势和应用场景。

🍃为什么使用数组实现循环队列

循环队列的实现方式主要是基于数组的,但也可以采用其他数据结构,如链表。不过,在实际应用中,数组实现循环队列的方式更为常见和高效。

基于数组实现循环队列的特点和优势

  1. 空间利用率高:通过将数组的最后一个位置与第一个位置相连,循环队列能够充分利用数组的存储空间,避免传统队列在多次入队和出队操作后可能出现的空间浪费现象。
  2. 操作简便:在数组实现中,可以通过简单的数学运算(如取模运算)来更新头指针和尾指针,实现入队和出队操作。这使得循环队列的操作非常简便和高效。
  3. 时间复杂度低:无论是入队还是出队操作,循环队列的时间复杂度都是O(1),即常数时间复杂度。这意味着无论队列中有多少元素,入队和出队操作所需的时间都是固定的。

循环队列在逻辑上的结构是这样的 

但在物理上的结构是这样的

二、循环队列的结构定义

包含

  • 指向数组的指针,这是循环队列的底层结构
  • 指向队首和队尾的整型变量front和rear
  • 循环队列的空间大小k

typedef int CQueueDataType;
typedef struct MyCircularQueue//循环队列结构定义
{
	CQueueDataType* a;
	int front;
	int rear;
	int k;
} MyCircularQueue;

三、循环队列的接口实现

🍃队列初始化

  • 动态开辟一块循环队列结构体大小的空间
  • 为数组指针的指向地址分配一块动态申请的内存,大小为k+1个空间,但实际使用k个(不申请k个是为了区别队列空和队列满,保留一个空间)
  • front和rear初始为0(要注意rear初始为0,意味着指向的是队尾的下一个元素)
  • k初始化为输入的值
  • 最后返回该队列的地址
MyCircularQueue* myCircularQueueCreate(int k) //循环队列初始化
{
	MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	tmp->a = (CQueueDataType*)malloc(sizeof(CQueueDataType) * (k + 1));
	tmp->front = tmp->rear = 0;
	tmp->k = k;
	return tmp;
}

🍃入队列

  • 首先对形参接收的地址判空
  • 然后判断队列是否满
  • 如果有空间可用的话,在rear指向的位置插入数据
  • 调整rear的位置,向后移动注意考虑循环的问题(rear+1)%(k+1),先对rear+1再对数组长度取模

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

🍃出队列

  • 首先对形参接收的地址判空
  • 然后判断队列是否为空
  • 如果有数据可出的话,直接调整front的位置即可(不过应当考虑循环值溢出的问题)(front+1)%(k+1)
  • 先对front+1再对数组长度取模

bool myCircularQueueDeQueue(MyCircularQueue* obj) //出队列
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
		return false;
	obj->front = (obj->front + 1) % (obj->k + 1);
	return true;
}

🍃取队首元素

  • 首先对形参接收的地址判空
  • 然后判断队列是否为空(空队列无数据可取)
  • 然后返回front位置的元素即可

int myCircularQueueFront(MyCircularQueue* obj) //取队首元素
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
		return -1;
	return obj->a[obj->front];
}

🍃取队尾元素

  • 首先对形参接收的地址判空
  • 然后判断队列是否为空(空队列无数据可取)
  • 队尾元素是rear位置的前一个元素,考虑到直接-1可能会出错,正确的位置应该是(rear - 1 + k + 1) % (k + 1),也可以简化成(rear  +k ) % (k + 1)
  • 返回该位置数据即可

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

🍃判空

  • 对形参接收的地址判空
  • 然后返回front==rear的结果
bool myCircularQueueIsEmpty(MyCircularQueue* obj) //判空
{
	assert(obj);
	return obj->front == obj->rear;
}

🍃判满

  • 对形参接收的地址判空
  • 队列满的条件理应是rear+1==front,但考虑到队列是一个"环形"的,要考虑值的溢出,所以改为(rear + 1 )% (k +1)==front
bool myCircularQueueIsFull(MyCircularQueue* obj) //判满
{
	assert(obj);
	return (obj->rear + 1) % (obj->k + 1)==(obj->front);
}

🍃队列销毁

  • 对形参接收的地址判空
  • 然后释放动态申请的数组的空间
  • front、rear、k都重置为0
  • 最后释放循环队列结构体的空间
void myCircularQueueFree(MyCircularQueue* obj) //循环队列销毁
{
	free(obj->a);
	obj->front = obj->rear = 0;
	obj->k = 0;
	free(obj);
	obj = NULL;
}

四、C语言实现代码

🍃Circular_Queue.h   //循环队列头文件

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

typedef int CQueueDataType;
typedef struct MyCircularQueue//循环队列结构定义
{
	CQueueDataType* a;
	int front;
	int rear;
	int k;
} 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); //循环队列销毁

🍃Circular_Queue.c   //循环队列源文件

#include"Circular_Queue.h"

MyCircularQueue* myCircularQueueCreate(int k) //循环队列初始化
{
	MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	tmp->a = (CQueueDataType*)malloc(sizeof(CQueueDataType) * (k + 1));
	tmp->front = tmp->rear = 0;
	tmp->k = k;
	return tmp;
}

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

bool myCircularQueueDeQueue(MyCircularQueue* obj) //出队列
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
		return false;
	obj->front = (obj->front + 1) % (obj->k + 1);
	return true;
}

int myCircularQueueFront(MyCircularQueue* obj) //取队首元素
{
	assert(obj);
	if (myCircularQueueIsEmpty(obj))
		return -1;
	return obj->a[obj->front];
}

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

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

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

void myCircularQueueFree(MyCircularQueue* obj) //循环队列销毁
{
	free(obj->a);
	obj->front = obj->rear = 0;
	obj->k = 0;
	free(obj);
	obj = NULL;
}

🍃test.c           //main函数测试文件

#include"Circular_Queue.h"

void test1()
{
	int k = 0;
	scanf("%d", &k);
	MyCircularQueue* CQ = myCircularQueueCreate(k);//创建循环队列并初始化
	if (myCircularQueueIsEmpty(CQ))
		printf("队列空\n");
	myCircularQueueEnQueue(CQ, 1);//插入五个数据
	myCircularQueueEnQueue(CQ, 2);
	myCircularQueueEnQueue(CQ, 3);
	myCircularQueueEnQueue(CQ, 4);
	myCircularQueueEnQueue(CQ, 5);
	if (myCircularQueueIsEmpty(CQ))
		printf("队列空\n");
	else
		printf("队列非空\n");
	if (myCircularQueueIsFull(CQ))
		printf("队列满\n");
	else
		printf("队列非满\n");
	printf("队首元素:%d\n", myCircularQueueFront(CQ));
	printf("队尾元素:%d\n", myCircularQueueRear(CQ));
	while (!myCircularQueueIsEmpty(CQ))//依次打印队首元素并删除
	{
		printf("%d ", myCircularQueueFront(CQ));
		myCircularQueueDeQueue(CQ);
	}
	printf("\n");
	myCircularQueueFree(CQ);
}

int main()
{
	test1();
	return 0;
}

测试结果

五、循环队列的应用场景

循环队列在实际应用中有着广泛的用途。以下是一些常见的应用场景:

  1. 任务调度:在操作系统中,任务调度器通常使用队列来管理待执行的任务。循环队列可以有效地处理这些任务,确保它们按照先进先出的顺序被执行。由于操作系统的资源有限,使用循环队列可以最大化地利用这些资源,避免不必要的空间浪费。

  2. 网络通信:在网络通信中,数据包经常需要在不同的节点之间传输。循环队列可以用于在节点上管理这些数据包,确保它们按照正确的顺序被发送和接收。特别是在路由器和交换机等网络设备中,循环队列可以有效地处理大量的数据包,提高网络性能。

  3. 打印机管理:在打印系统中,多个打印任务可能需要同时发送到打印机。循环队列可以用于管理这些打印任务,确保它们按照接收的顺序被打印出来。使用循环队列可以避免打印任务的混乱和丢失,提高打印效率。

  4. 模拟系统:在模拟系统中,如模拟银行排队系统或模拟医院挂号系统等,循环队列可以模拟现实中的排队情况。通过循环队列的入队和出队操作,可以模拟客户的到来和离开,以及服务员的接待过程。这有助于分析系统的性能和瓶颈,优化服务流程。

六、总结

循环队列是一种利用数组循环特性实现队列操作的数据结构。它通过维护头指针和尾指针来管理队列的入队和出队操作,实现了对固定大小空间的高效利用。

循环队列在任务调度、网络通信、打印机管理以及模拟系统等多个领域都有广泛的应用。

通过本文的介绍和分析,我们可以看到循环队列在解决实际问题时具有显著的优势和灵活性。因此,掌握循环队列的实现原理和应用方法对于提高编程能力和解决实际问题具有重要意义。

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

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

相关文章

【Linux】Centos升级到国产操作系统Openeuler

一、前言 迁移工具采用Openeuler官网提供的x2openEuler工具&#xff0c;是一款将源操作系统迁移到目标操作系统的迁移工具套件&#xff0c;具有批量化原地升级能力&#xff0c;当前支持将源 OS 升级至 openEuler 20.03。 官网链接&#xff1a;openEuler迁移专区 | 迁移专区首页…

陀螺仪LSM6DSV16X与AI集成(8)----MotionFX库解析空间坐标

陀螺仪LSM6DSV16X与AI集成.8--MotionFX库解析空间坐标 概述视频教学样品申请源码下载开启CRC串口设置开启X-CUBE-MEMS1设置加速度和角速度量程速率选择设置FIFO速率设置FIFO时间戳批处理速率配置过滤链初始化定义MotionFX文件卡尔曼滤波算法主程序执行流程lsm6dsv16x_motion_fx…

问题:以下哪个不是报名“天天特价“活动必须具有的条件( ) #其他#其他#媒体

问题&#xff1a;以下哪个不是报名"天天特价"活动必须具有的条件( ) A、店铺信誉达到一钻 B、开通淘金币抵扣 C、宝贝月销量达到10个 D、店铺同类产品要达到10个以上 参考答案如图所示

重学java 84.Java枚举

那些你暗自努力的时光&#xff0c;终究会照亮你前行的路 —— 24.6.24 一、枚举介绍&#xff08;开发中表示状态&#xff09; 1.概述&#xff1a; 五大引用数据类型&#xff1a;类型、数组、接口、注解、枚举 2.定义&#xff1a; public enum 枚举类名{} 所有的枚举类父类…

Excel 宏录制与VBA编程 —— 12、日期相关

代码1 - 获取当前时间日期信息 代码2 - 时间日期格式 代码3 - 时间日期计算 代码4 - 时间日期案例 关注 笔者 - jxd

数据库断言

在数据库验证断言 目的&#xff1a;不能相信接口返回结果&#xff0c;通过到数据库检验可知接口返回结果是否真的正确 如何校验&#xff1a;代码与mymql建立网络连接&#xff0c;操作数据库&#xff0c;断开连接 代码&#xff1a;java操作数据库 pom文件配置依赖 步骤&…

作为一名车载测试工程师,核心能力是什么?

最近经常有人会问我&#xff0c;说XX培训机构专门培训车载测试&#xff0c;我要去&#xff0c;而且薪资很高&#xff0c;现在是风口&#xff0c;你是否也听过这样的销售话语&#xff1f; 然后进去培训2-3个月&#xff0c;包括上车测试&#xff0c;后来进去后发现原来真实的场景…

端到端的全人体关键点检测:手把手实现从YOLOPose到YOLOWhole

目录 一、搭建yolopose平台二、迁移训练任务2.1 任务拓展数据准备训练模型测试训练模型结论To-do list: 1、数据集,COCO-whole, Halpe;下载好; 2、模型搭建,先基于yolov8来检测人体姿态,17个点; 3、迁移任务,17个点,把它拓展到133个点; 4、优化133个点的模型; 一、搭…

解释一下在React中,什么是“渲染Props”模式,以及它与使用Hooks之前的状态管理有何不同?

在React中&#xff0c;"渲染Props"模式是一种组件设计模式&#xff0c;它通过将一个函数作为prop传递给组件&#xff0c;允许父组件定义子组件的渲染逻辑。这种模式使得组件更加灵活和可复用&#xff0c;因为它们可以接受一个渲染函数来决定如何渲染自己。 渲染Prop…

同元软控智能电动汽车数字化解决方案亮相CICV 2024

2024年6月18日-20日&#xff0c;由中国汽车工程学会、国家智能网联汽车创新中心、清华大学车辆与运载学院、清华大学智能绿色车辆与交通全国重点实验室举办的第十一届国际智能网联汽车技术年会&#xff08;CICV 2024&#xff09;在北京召开。苏州同元软控信息技术有限公司&…

【zip密码】忘了zip密码,怎么办?

Zip压缩包设置了密码&#xff0c;解压的时候就需要输入正确对密码才能顺利解压出文件&#xff0c;正常当我们解压文件或者删除密码的时候&#xff0c;虽然方法多&#xff0c;但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么&#xff0c;忘记了zip压缩包的密…

Django教程(002):模板语法的使用

目录 1 字符串2 列表3 字典4 列表中是字典5 if语句6 案例&#xff1a;使用爬虫爬取联通新闻并在页面渲染 模板语法本质上&#xff1a;在HTML中写一些占位符&#xff0c;由数据对这些占位符进行替换和处理。模板语法主要是方便对方法返回的数据在前端进行渲染&#xff0c;这些数…

什么是车载测试?车载测试怎么学!

1、车载测试是什么&#xff1f; 车载测试分很多种&#xff0c;有软件测试、硬件测试、性能测试、功能测试等等&#xff0c;每一项测试的内容都不一样&#xff0c;我们所说的车载测试主要指的是汽车软件的功能测试&#xff0c;也就是针对汽车实现的某一个功能&#xff0c;而进行…

tp5学习基本控制器和视图

1 文件结构 正在上传…重新上传取消 application 主要操作目录 extend 扩展 public 入口文件 runtime 运行时文件 thinkphp 核心代码 vendor 三方扩展 2 public/index.php 解析 正在上传…重新上传取消 .htaccess Apache 可写文件 index.php 主目录 router.php 路由文件 3 inde…

【Oracle APEX开发小技巧3】Oracle apex pl/sql动态拼接的注意点和技巧

【开发小技巧】在开发过程中使用动态拼接进行sql查询及需要注意的点 在开发过程中&#xff0c;我们经常需要根据不同的条件执行不同的SQL查询。例如&#xff0c;当权限类型为0&#xff08;非超级管理员权限&#xff09;时&#xff0c;我们只能查询特定平台的数据&#xff1b;而…

MySQL----undo log回滚日志原理、流程以及与redo log比较

回滚日志 回滚日志&#xff0c;保存了事务发生之前的数据的一个版本&#xff0c;用于事务执行时的回滚操作&#xff0c;同时也是实现多版本并发控制&#xff08;MVCC&#xff09;下读操作的关键技术。 如何理解Undo Log 事务需要保证原子性&#xff0c;也就是事务中的操作要…

c++ 正则匹配得使用

标头&#xff1a;#include <regex> 相关函数&#xff1a; regex_match regex_replace regex_search 名称描述regex_match测试正则表达式是否与整个目标字符串相完全匹配。regex_replace替换匹配正则表达式。regex_search搜索正则表达式匹配项。 1. regex_search 成功搜…

Java开发-实际工作经验和技巧-0001-PostgreSQL数据库存储磁盘满了重启以及应急措施

Java开发-实际工作经验和技巧-0001-PostgreSQL数据库存储磁盘满了重启以及应急措施 更多内容欢迎关注我&#xff08;持续更新中&#xff0c;欢迎Star✨&#xff09; Github&#xff1a;CodeZeng1998/Java-Developer-Work-Note 技术公众号&#xff1a;CodeZeng1998&#xff0…

外贸行业 - 收汇日期

“收汇日期”指的是外贸业务中&#xff0c;出口方从进口方收到货款的具体日期。在外贸交易中&#xff0c;特别是使用信用证&#xff08;Letter of Credit, L/C&#xff09;、电汇&#xff08;Telegraphic Transfer, T/T&#xff09;、付款交单&#xff08;Documents against Pa…

SOLIDWORKS教育认证计划

当今职场竞争激烈&#xff0c;CAD专业人员需在各个方面拥有优势。SOLIDWORKS教育认证计划为学生和教师提供了一个机会&#xff0c;让他们能够通过SOLIDWORKS 3D实体建模软件、设计理念、可持续设计和对专业发展的投入来证明自身的专业水准。凭借这个行业标准认证&#xff0c;学…