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

💞💞 前言

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不断进行取模运算以防越界;
但是链表实现需要手动将开辟的节点链接在一起,数组则不一样它一开辟就是地址连续的一段空间;
其他的实现链表和数组都差不多;
以上就是循环队列的实现啦~ 大家有什么疑问可以写在评论区或者私信我哦~ 完结撒花~🥳🥳🎉🎉🎉

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

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

相关文章

Python 小而精Web开发框架Flask精通指南

文章目录 Flask 简介说明Flask 核心依赖Flask 常用扩展Flask 快速启动工作流程代码示例Flask 快速启动控制台Flask 快速启动效果 Flask 启动参数Flask 路由定义Flask 支持的 HTTP 请求方式&#xff1a;路由装饰器中的参数 Flask 路由参数Flask 路由蓝图路由蓝图的优点路由蓝图的…

痛失offer的八股

java面试八股 mysql篇&#xff1a; 事物的性质&#xff1a; 事物的性质有acid四特性。 a&#xff1a;automic&#xff0c;原子性&#xff0c;要么全部成功&#xff0c;要么全部失败&#xff0c;mysql的undolog&#xff0c;事物在执行的时候&#xff0c;mysql会进行一个快照读…

获取KEGG通路的基因列表 做单细胞GSEA、GSVA分析

使用KEGG通路的基因列表进行单细胞GSEA GSVA分析的过程&#xff0c;我们需要遵循以下步骤&#xff1a; 获取KEGG通路的基因列表&#xff1a;这通常涉及使用专门的R包&#xff0c;如KEGGREST或biomaRt&#xff0c;来查询KEGG数据库并检索特定通路的基因列表。 准备单细胞表达数…

详解JS原型与原型链的关系

1、构造函数原型prototype (1)、构造函数通过原型分配的函数是所有对象所共享的&#xff1b; (2)、JavaScript规定&#xff0c;每一个构造函数都有一个prototype属性&#xff0c;指向另一个对象&#xff1b; (3)、注意这个prototype就是一个对象&#xff0c;这个对象的所有属性…

Scikit-Learn逻辑回归(二)

Scikit-Learn逻辑回归二&#xff1a;多项式与正则化 1、多项式回归回顾1.1、逻辑回归为什么要使用多项式1.2、多项式回归及原理 2、逻辑回归与多项式 1、多项式回归回顾 本文接上篇&#xff1a;Scikit-Learn逻辑回归(一) 上篇中&#xff0c;我们详细介绍了逻辑回归的概念、原理…

使用 React antd 的ProFormSelect组件 搜索查询 多选的写法

使用 React antd 的ProFormSelect组件 搜索查询 多选的写法 需求&#xff1a;需要一个搜索框&#xff0c;可以选择员工&#xff0c;&#xff08;员工人数多无法一次性获取&#xff0c;全部放入options中&#xff09;&#xff0c;所以需要使用搜索功能&#xff0c;而且是可以多…

XR“黑话”

MTP&#xff08;Motion-To-Photon Latency&#xff09;&#xff1a;实际人体发生运动到图像显示到屏幕上的时间延迟。早期一些vr产生晕动症的主要原因。 ATW&#xff08;Asynchronous Timewarp&#xff09;&#xff1a;主要解决两个问题&#xff0c;一是延迟&#xff0c;二是补…

CSS弹性盒模型(学习笔记)

一、厂商前缀 1.1 作用 解决浏览器对C3新特性的兼容&#xff0c;不同的浏览器厂商&#xff0c;定义了自己的厂商前缀 1.2 语法 浏览器 厂商前缀内核(渲染引擎)&#xff1a;解析htmlcssjs谷歌 -webkit-blink苹果-webkit-webkit欧朋-o-blink火狐 -moz-geckoIE-ms- trid…

OpenCV4.9.0开源计算机视觉库安装教程

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 引言&#xff1a;OpenCV系列文章中的安装部分今天全部完成了&#xff0c;为了读者更方便阅读&#xff0c;大家可以按下列索引前往&#xff0c;成文较为仓促有错漏在所难免&#xff0c;欢迎大家指正…

服务器运行一段时间后

自己记录一下。 一、查看目录占用情况 df -h 命令查看磁盘空间 du -ah --max-depth=1 / 查看根目录下各个文件占用情况 二、mysql日志清空 这个日志是可以清空的 echo > /usr/local/mysql/data/syzl-db2.log #将文件清空 说明: 这个文件这么大是因为,开启 …

将OpenCV与gdb驱动的IDE结合使用

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV4.9.0开源计算机视觉库在 Linux 中安装 下一篇&#xff1a;将OpenCV与gcc和CMake结合使用 ​ 能力 这个漂亮的打印机可以显示元素类型、、标志is_continuous和is_subm…

微信小程序分销返佣模式--小程序1-3级分销插件--小程序分销--

团购小程序是一种基于社区团购模式的电商平台&#xff0c;主要面向社区居民用户。 如果你想要开发一款分销团购小程序可以参考以下功能需求进行开发制作。 1、用户注册和登录 提供用户注册和登录功能&#xff0c;使用户能够创建和管理他们的账户。 2、会员管理 包括会员注…

springboot网站开发-诡异的static/images读取故障

springboot网站开发-诡异的static/images读取故障!我在本地环境测试代码&#xff0c;一切正常。可以读取到该路径下的图片模板&#xff0c;正常生成图片存储在本地D盘下面的文件夹。但是改成服务器linux环境后就不行了。打包发布后&#xff0c;死活读取不到图片模板。 这个故障…

HTML(一)

一、网页 1.1 什么是网页 网站是指在因特网上根据一定的规则&#xff0c;使用 HTML 等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是 HTML 格式的文件&#xff0c;它要通过浏览器来阅读。 网页是构成网站的基本元素&#xff0c;它通常由…

基于python+vue智慧农业小程序flask-django-php-nodejs

传统智慧农业采取了人工的管理方法&#xff0c;但这种管理方法存在着许多弊端&#xff0c;比如效率低下、安全性低以及信息传输的不准确等&#xff0c;同时由于智慧农业中会形成众多的个人文档和信息系统数据&#xff0c;通过人工方法对知识科普、土壤信息、水质信息、购物商城…

FreeRTOS任务相关API函数

1. FreeRTOS任务相关API函数介绍 函数描述uxTaskPriorityGet()获取任务优先级vTaskPrioritySet()设置任务优先级uxTaskGetNumberOfTasks()获取系统中任务的数量uxTaskGetSystemState()获取所有任务状态信息vTaskGetInfo()获取指定单个的任务信息xTaskGetCurrentTaskHandle()获…

解决1130-Host‘ ‘is not allowed to connect to this MySQL server,实现远程连接本地数据库

参考:https://blog.csdn.net/CoCo629vanilla/article/details/129008644 在使用Navicat远程连接本地数据库时&#xff0c;遇到了这样一个问题&#xff0c; 我使用 本地主机的地址&#xff0c;连接本地的数据库&#xff0c;报错host ‘’ is not allowed to connect to this my…

转座子插入序列分析2-自制分析流程

我们先观察一下测序的结果&#xff0c;是否有一些什么规律&#xff0c;因为使用的靶向富集法的测序&#xff0c;我们使用了特殊序列将插入了转座子的部分钓了出来&#xff0c;然后进行的测序&#xff0c;所以理论上富集到的所有序列都应该存在一段与我们钓鱼序列互补的“靶点序…

Redis实战:缓存穿透及其解决思路 实战演示

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏Redis实战与进阶 本专栏讲解Redis从原理到实践 …

YOLOv5改进 | 图像去雾 | 利用图像去雾网络AOD-PONO-Net网络增改进图像物体检测(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是利用AODNet图像去雾网络结合PONO机制实现二次增强&#xff0c;我将该网络结合YOLOv5针对图像进行去雾检测&#xff08;也适用于一些模糊场景&#xff0c;图片不清晰的检测&#xff09;&#xff0c;同时本文的内容不影响其它的模块改进…