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

这篇具有很好参考价值的文章主要介绍了数据结构——循环队列的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

💞💞 前言

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:数据结构学习笔记
💥对于数据结构顺序表、链表、堆有疑问的都可以在上面数据结构的专栏进行学习哦~ 有问题可以写在评论区或者私信我哦~

之前我们学习过数据结构中的栈和队列,详情可点击这里数据结构——lesson5栈和队列详解进行查看🥳🥳,队列是一种先进先出的结构,但是我们之前讲的队列都是类似于线性的物理结构,这次我们所介绍的队列则是一直类似于环状的循环结构,它依旧保持着队列的特性——先进先出。

1.循环队列的介绍

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

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

循环队列的实现应该支持如下操作:

  1. MyCircularQueue(k): 构造器,设置队列长度为 k 。
  2. Front: 从队首获取元素。如果队列为空,返回 -1 。
  3. Rear: 获取队尾元素。如果队列为空,返回 -1 。
  4. enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  5. deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  6. isEmpty(): 检查循环队列是否为空。
  7. isFull():检查循环队列是否已满。

题目链接——循环队列OJ题大家也可以在学习完栈和队列后自己尝试写一写。

数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

2.循环队列实现思路分析

首先根据题目要求,队列长度为k,所以一开始我们要使用malloc开辟k个节点的空间,而不是和之前的队列一样在增加数据时再开辟节点,循环队列的长度是固定的,最开始就已经开辟好了,所以不需要再在使用时开辟节点空间。
数据结构——循环队列的实现,数据结构,数据结构,c语言,链表
如上图所示,已经在内存中开辟了完整的空间,最开始队列的头尾指针都指向开始的位置,每加一个元素,rear指针也就是尾指针的位置就往后挪动一位,删除数据就把头指针front往后挪一位,当rear指针指向最后一个元素时,队列满了,此时的判断条件应该时rear->pNext = front;
也就是说rear的下一个位置是front,如下图所示:
数据结构——循环队列的实现,数据结构,数据结构,c语言,链表
思考一下: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;

}

调试时我们发现成功构造了循环队列:
数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

后面pNext又回到了最开始的位置说明我们链接成功啦🎉🎉🎉

数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

我们还将队列的头尾指针与开辟的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结果如下

数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

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指针指向下一个节点,链表可以形成天然的循环结构,而数组却要依靠首尾下标来模拟实现,如图所示有两种情况:
数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

当删除节点时只需将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结果如下:

数据结构——循环队列的实现,数据结构,数据结构,c语言,链表

5.结语

链表来实现循环队列有一个好处就是形成了天然的环形结构,对应数组实现循环队列则需要front,rear不断进行取模运算以防越界;
但是链表实现需要手动将开辟的节点链接在一起,数组则不一样它一开辟就是地址连续的一段空间;
其他的实现链表和数组都差不多;
以上就是循环队列的实现啦~ 大家有什么疑问可以写在评论区或者私信我哦~ 完结撒花~🥳🥳🎉🎉🎉文章来源地址https://www.toymoban.com/news/detail-842951.html

到了这里,关于数据结构——循环队列的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

      当我们编写程序时,经常需要处理各种数据结构。队列是一种常见的数据结构,它有着广泛的应用场景。队列的基本操作包括入队和出队,应用于模拟等待队列、消息队列、计算机缓存等场合。   在实际编程中,我们可以用不同的数据结构来实现队列。本文主要介绍了

    2024年02月08日
    浏览(118)
  • 数据结构:循环队列的实现(leetcode622.设计循环队列)

      目录 一.循环队列简单介绍 二.用静态数组实现循环队列 1.数组循环队列结构设计 2.数组循环队列的堆区内存申请接口  3.数据出队和入队的接口实现 4.其他操作接口 5.数组循环队列的实现代码总览  三.静态单向循环链表实现循环队列  1.链表循环队列的结构设计 2.创建静态

    2024年02月03日
    浏览(45)
  • 数据结构——循环队列的实现

    hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥 个人主页:大耳朵土土垚的博客 💥 所属专栏:数据结构学习笔记 💥对于数据结构顺序表、链表、堆有疑问的都可以在上面数据结构的专栏进行学习哦~ 有问题可以写在评论区或者私信

    2024年03月24日
    浏览(41)
  • 【数据结构与算法】用队列实现栈&&用栈实现队列&&设计循环队列

    🌠 作者:@ 阿亮joy. 🎆 专栏:《数据结构与算法要啸着学》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、

    2024年01月20日
    浏览(45)
  • 数据结构--队列的链表实现

    初始化 判断队列是否为空 入队 初始化 判断队列是否为空 入队 队满 链式存储――一般不会队满,除非内存不足

    2024年02月11日
    浏览(47)
  • 【数据结构】队列的使用|模拟实现|循环队列|双端队列|面试题

    1.1 概念 队列 :只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾(Tail/Rear) 出队列:进行删除操作的一端称为 队头(Head/Front) 队列和栈的区别: 队列是 先进先出(队

    2024年02月03日
    浏览(44)
  • 数据结构:队列(链表和数组模拟实现)

    目录 1.何为队列 2.链表模拟实现 2.1 节点和队列创建 2.2 初始化队列 2.3 入队操作 2.4 出队操作 2.5 遍历队列 2.6 获取队首和队尾元素 2.7 判断队列是否为空 2.8 完整实现 3. 数组模拟实现 3.1 创建队列 3.2 入队和出队操作 3.3 遍历队列 3.4 获取队首和队尾元素  3.5 判断队列是否为空

    2024年02月03日
    浏览(54)
  • 【数据结构】队列篇| 超清晰图解和详解:循环队列模拟、用栈实现队列、用队列实现栈

    博主简介: 努力学习的22级计算机科学与技术本科生一枚🌸 博主主页: @是瑶瑶子啦 每日一言🌼: 每一个不曾起舞的日子,都是对生命的辜负。——尼采 🔗622. 设计循环队列 👧🏻 思路: 🍊数据结构: 使用数组为数据结构,且采用牺牲一个空间的方法来包装判空和判满的

    2024年02月11日
    浏览(40)
  • 探索数据结构:链式队与循环队列的模拟、实现与应用

    队列(queue)是一种只允许在一端进行插入操作,而在另一端进行删除操作的线性表。其严格遵循 先进先出(First In First Out) 的规则,简称 FIFO 。 队头(Front) :允许删除的一端,又称队首。 队尾(Rear) :允许插入的一端。 队列与栈类似,实现方式有两种。一种是以 数组

    2024年04月08日
    浏览(82)
  • 【数据结构】线性表 ⑤ ( 双循环链表 | 双循环链表特点 | 双循环链表插入操作处理 | 代码示例 - 使用 Java 实现 双循环链表 )

    \\\" 双循环链表 \\\" 是 在 单循环链表 的基础上 , 在每个 节点 中 , 新增一个 指针 , 指向 该节点 的 前驱节点 ; 双向循环链表 每个 节点 都包含 数据 和 两个指针 , 一个指针指向前一个节点 , 一个指针指向后一个节点 ; 与 单循环链表相比 , 双循环链表 可以在两个方向上遍历整个链

    2024年02月13日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包