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

   

            💓 博客主页:倔强的石头的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. 模拟系统:在模拟系统中,如模拟银行排队系统或模拟医院挂号系统等,循环队列可以模拟现实中的排队情况。通过循环队列的入队和出队操作,可以模拟客户的到来和离开,以及服务员的接待过程。这有助于分析系统的性能和瓶颈,优化服务流程。

六、总结

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python基础语法 003-1 数据类型列表

1 列表 1.1 列表的定义 关键字&#xff1a;list()标识&#xff1a;[] 用逗号隔开使用最频繁的数据类型&#xff0c;列表可以完成大多数集合类型的数据结构实现支持字符、数字、字符串、甚至可以包含列表&#xff0c;即嵌套打印列表类型&#xff1a;type()1个元素的列表表示&…

【Linux基础】文件基本属性

Linux文件基本属性是指文件或目录在Linux系统中具有的一系列特性和信息。这些属性提供了关于文件或目录的详细信息&#xff0c;包括其类型、权限、大小、创建和修改时间等。 文件属性 在 Linux 中我们可以使用 ll 或者 ls –l 命令来显示一个文件的属性以及文件所属的用户和组…

springCloud组件专题(四) --- sentinel

前言 限流&#xff0c;熔断降级概念 限流&#xff1a;顾名思义&#xff0c;就是对一个资源&#xff08;服务或者接口都可以算资源&#xff09;的访问进行限制。简单来说就是限制单位时间内允许资源被访问的次数。常见的算法就是令牌桶算法。 降级&#xff1a;降级其实是一种资源…

常用主流sip协议软电话客户端软件有哪些?—— 筑梦之路

Ekiga 官网地址&#xff1a;Ekiga ~ Free Your Speech Ekiga&#xff0c;原名GnomeMeeting&#xff0c;支持Windows和Linux&#xff0c;是一个兼容SIP和H.323的视频会议程序&#xff0c;兼容VoIP&#xff0c;IP电话&#xff0c;通过Ekiga可以与使用任何SIP和H.323软硬件的远程…

C语言 | Leetcode C语言题解之第188题买卖股票的最佳时机IV

题目&#xff1a; 题解&#xff1a; int maxProfit(int k, int* prices, int pricesSize) {int n pricesSize;if (n 0) {return 0;}k fmin(k, n / 2);int buy[k 1], sell[k 1];memset(buy, 0, sizeof(buy));memset(sell, 0, sizeof(sell));buy[0] -prices[0];sell[0] 0…

周末设计高端企业_集团官网主题Discuz模板

风格名称: 周末设计_高端企业_集团官网 适用版本: Discuz! X3.0、X3.1、X3.2、X3.3、F1.0 风格编码: 使用语言包结构&#xff0c;适合全部编码 周末设计高端企业_集团官网主题Discuz模板

g++制作C++动态库的简洁例程

g制作C动态库的简洁例程 code review! 文章目录 g\制作C动态库的简洁例程1. 创建 C 动态库1.1 动态库源文件1.2 编译动态库 2. 使用动态库2.1 命令行编译链接然后运行2.2 使用 CMake 编译链接然后运行 3.附加笔记&#xff1a;关于运行时是否能找到libmylib.so的问题汇总3.1.g -…

TLS握手中的RTT

文章目录 TLS 1.2 握手过程中的 RTT 次数TLS 1.3 1-RTT 初次TLS1.3 0-RTT 握手过程总结 TLS 1.2 握手过程中的 RTT 次数 TLS 1.2 握手通常需要2 RTT 才能完成。具体步骤如下&#xff1a; 第一次 RTT&#xff1a; 客户端发送 ClientHello&#xff1a;客户端生成一个随机数&…

【离散数学】图的随机生成和欧拉(回)路的确定(c语言实现)

实验要求 变量定义 因为如果我们使用局部变量&#xff0c;每一个函数都会使用这些变量&#xff0c;会让函数的参数越变越多。所以我们定义全局变量&#xff0c;这样就不用在参数中调用了。 #define MAX 100 int arrMap[MAX][MAX] { 0 };//图的矩阵 int degree[MAX] { 0 };…

GEO数据挖掘-富集分析、TinyArray简化流程、多组样本分析more

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 富集分析一些理论知识具体代码 富集不到的补救措施更多资料---问题数据和常见错误分析Part4-复杂数据及其分析多分组数据分析流程 tinyarray简化版本分析流程多分组…

ROS机器人虚拟仿真挑战赛持续学习笔记-20240619

cartographer 需要全手工编译……比较麻烦。 如果使用新版ceres-solver&#xff0c;版本2.x&#xff0c;需要修改源码&#xff0c;部分“接口代码”有改动。 稳妥使用ceres-solver-1.13.0&#xff0c;且需要安装abseil-cpp。 验证是否成功&#xff0c;使用roscd或roslaunch…

C语言| 数组元素的删除

同数组元素的插入差不多。 数组元素的插入&#xff0c;是先移动要插入元素位置后面的所有元素&#xff0c;再插入新元素&#xff0c;长度1。 C语言| 数组的插入-CSDN博客 数组元素的删除&#xff0c;是先删除元素&#xff0c;再把后面的元素往前移动一位&#xff0c;而本程序…

三国之家网站的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;论坛管理&#xff0c;公告管理&#xff0c;三国视频管理&#xff0c;基础数据管理&#xff0c;三国图文管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#…

2.树莓派4b+ubuntu18.04(ros版本melodic)+arduino mega自制两轮差速小车,实现建图导航功能

这篇文章介绍arduino使用和安装arduino_bridge 将arduino与树莓派连接 查看arduino的端口号&#xff0c;我们这里查看到的时ttyUSB0 ll /dev/ttyUSB*将当前用户添加进dialout组 sudo usermod -a -G dialout your_user_name然后重启树莓派&#xff0c;然后才能生效 然后如果你…

“Driver not loaded“问题解决方案

这两天又碰到了离谱的&#xff0c;愚蠢的&#xff0c;莫名其妙的&#xff0c;丧尽天良的错误。 之前已经解决过这个问题。这几天又碰上了&#xff0c;明明都已经把相应的dll放到了exe的同级目录&#xff0c;NND还是有问题&#xff01;&#xff01;&#xff01;卡了我一个晚上加…

Linux C/C++ socket函数

目录 socket函数 函数原型 头文件 功能 返回值 参数 错误码 socket函数 函数原型 int socket(int domain, int type, int protocol); 头文件 #include <sys/types.h> #include <sys/socket.h> 功能 创建一个用于通信的端点&#xff0c;并返回一个文件描述符…

tensorRT C++使用pt转engine模型进行推理

目录 1. 前言2. 模型转换3. 修改Binding4. 修改后处理 1. 前言 本文不讲tensorRT的推理流程&#xff0c;因为这种文章很多&#xff0c;这里着重讲从标准yolov5的tensort推理代码&#xff08;模型转pt->wts->engine&#xff09;改造成TPH-yolov5&#xff08;pt->onnx-…

Linux - 探秘 Linux 的 /proc/sys/vm 常见核心配置

文章目录 PreLinux 的 /proc/sys/vm 简述什么是 /proc/sys/vm&#xff1f;主要的配置文件及其用途参数调整对系统的影响dirty_background_ratio 和 dirty_ratioswappinessovercommit_memory 和 overcommit_ratiomin_free_kbytes 实例与使用建议调整 swappiness设置 min_free_kb…

6.XSS-钓鱼攻击展示(存储型xss)

xss钓鱼演示 钓鱼攻击利用页面 D:\phpStudy\WWW\pikachu\pkxss\xfish 修改配置文件里面对应自己的入侵者的IP地址或者域名&#xff0c;对应的路径下的fish.php脚本如下&#xff1a; <?php error_reporting(0); // var_dump($_SERVER); if ((!isset($_SERVER[PHP_AUTH_USE…

分类预测 | Matlab实现GWO-CNN-SVM灰狼冰算法优化卷积支持向量机分类预测

分类预测 | Matlab实现GWO-CNN-SVM灰狼冰算法优化卷积支持向量机分类预测 目录 分类预测 | Matlab实现GWO-CNN-SVM灰狼冰算法优化卷积支持向量机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现GWO-CNN-SVM灰狼冰算法优化卷积支持向量机分类预测&…