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

news2025/2/27 4:58:25

目录

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.coloradmin.cn/o/1240865.html

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

相关文章

2023亚太杯数学建模竞赛(亚太赛)选题建议+初步分析

如下为C君的2023亚太杯数学建模竞赛&#xff08;亚太赛&#xff09;选题建议初步分析&#xff1a; 提示&#xff1a;DS C君认为的难度&#xff1a;C<A<B&#xff0c;开放度&#xff1a;A<B<C。 以下为ABC题选题建议及初步分析&#xff1a; A题&#xff1a;Image…

读像火箭科学家一样思考笔记06_初学者之心

1. 专业化是目前流行的趋势 1.1. 通才&#xff08;generalist&#xff09;是指博而不精之人 1.2. 懂得的手艺越多&#xff0c;反而会家徒四壁 1.2.1. 希腊谚语 1.3. 这种态度代价很大&#xff0c;它阻断了不同学科思想的交融 2. 组合游戏 2.1. 某个行业的变革可能始于另一…

《微信小程序开发从入门到实战》学习二十六

3.4 开发参与投票页面 参与投票页面同样需要收集用户提交的信息&#xff0c;哪个用户在哪个投票选择了什么选项&#xff0c;因此它也是一个表单页面 3.4.1 如何获取投票信息 假设用户A在投票创建页面后填了表单&#xff08;1.创建投票&#xff09;&#xff0c;用户A 点了提交…

Antd Design的inputNumber实现千位分隔符和小数点并存

代码来自文章: react中使用antDesign的Input/InputNumber最多保留两位小数&#xff0c;多的小数位禁止输入&#xff0c;且实现输入实时校验并添加千位分隔符, 正则忘了很多, 我主要做个笔记. //定义InputNumber的参数 const NumberProps {min: 0,//最小值max: …

SQLite3

数据库简介 常用的数据库 大型数据库&#xff1a;Oracle 中型数据库&#xff1a;Server 是微软开发的数据库产品&#xff0c;主要支持 windows 平台。 小型数据库&#xff1a;mySQL 是一个小型关系型数据库管理系统&#xff0c;开放源码 。(嵌入式不需要存储太多数据。) SQL…

spark shuffle 剖析

ShuffleExchangeExec private lazy val writeMetrics SQLShuffleWriteMetricsReporter.createShuffleWriteMetrics(sparkContext)private[sql] lazy val readMetrics SQLShuffleReadMetricsReporter.createShuffleReadMetrics(sparkContext)用在了两个地方&#xff0c;承接的是…

HarmonyOS ArkTS 应用添加弹窗(八)

概述 在我们日常使用应用的时候&#xff0c;可能会进行一些敏感的操作&#xff0c;比如删除联系人&#xff0c;这时候我们给应用添加弹窗来提示用户是否需要执行该操作&#xff0c;如下图所示&#xff1a; 弹窗是一种模态窗口&#xff0c;通常用来展示用户当前需要的或用户必须…

基于金枪鱼群算法优化概率神经网络PNN的分类预测 - 附代码

基于金枪鱼群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于金枪鱼群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于金枪鱼群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

oracle “ORA-25153:临时表空间为空”

从生产上面备份出来了一个数据库&#xff0c;应用在使用时显示ORA-25153临时表空间为空的报错&#xff0c;原因一般是数据库迁移时&#xff0c;没有迁移完整造成的 解决方法 1.创建新的临时表空间temp2 create temporary tablespace temp2 tempfile DATA size 100M autoexten…

通过AX6000路由器,实现外部访问内网的任意主机

概述 这里遇到一个场景,就是需要外部的人员,访问我内网的一台设备,进行内外部的设备联调。 这也是实际环境中,很常见的一种场景。 之前的做法是子设备上运行edge节点,可以直接访问。 但有的设备无法运行edge节点,那么可以参考一下这个方案来实现。 此方案可以摒弃了…

OpenAI再次与Sam Altman谈判;ChatGPT Voice正式上线

11月22日&#xff0c;金融时报消息&#xff0c;OpenAI迫于超过700名员工联名信的压力&#xff0c;再次启动了与Sam Altman的谈判&#xff0c;希望他回归董事会。 在Sam确定加入微软后&#xff0c;OpenAI超700名员工签署了一封联名信&#xff0c;要求Sam和Greg Brockman&#x…

从0开始学习JavaScript--JavaScript迭代器

JavaScript迭代器&#xff08;Iterator&#xff09;是一种强大的编程工具&#xff0c;它提供了一种统一的方式来遍历不同数据结构中的元素。本文将深入探讨JavaScript迭代器的基本概念、用法&#xff0c;并通过丰富的示例代码展示其在实际应用中的灵活性和强大功能。 迭代器的…

本地训练,开箱可用,Bert-VITS2 V2.0.2版本本地基于现有数据集训练(原神刻晴)

按照固有思维方式&#xff0c;深度学习的训练环节应该在云端&#xff0c;毕竟本地硬件条件有限。但事实上&#xff0c;在语音识别和自然语言处理层面&#xff0c;即使相对较少的数据量也可以训练出高性能的模型&#xff0c;对于预算有限的同学们来说&#xff0c;也没必要花冤枉…

如何用惯性动作捕捉系统,快速创建数字人三维动画?

在动画制作领域&#xff0c;惯性动作捕捉技术已经逐渐成为一种重要的制作手段。通过动捕设备能够将动捕演员真实的动作转化为数字数据&#xff0c;然后在动画中再现这些动作。为了创造出逼真、流畅的数字人动画&#xff0c;惯性动作捕捉系统成为了一大工具。 根据采集方式的不…

【计算机网络笔记】路由算法之层次路由

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

2023年亚太地区数学建模大赛 问题B

玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素&#xff0c;温室的设计通常采用带有温室风扇的通风系统&#xff0c;如…

《数学之美》第三版的读书笔记一、主要是马尔可夫假设、隐马尔可夫模型、图论深度/广度、PageRank相关算法、TF-IDF词频算法

1、马尔可夫假设 从19世纪到20世纪初,俄国有个数学家叫马尔可夫他提出了一种方法,假设任意一个词出现的概率只同它前面的词有关。这种假设在数学上称为马尔可夫假设。 2、二元组的相对频度 利用条件概率的公式,某个句子出现的概率等于每一个词出现的条件概率相乘,于是可展…

2023亚太杯数学建模B题思路 - 玻璃温室中的微气候法规

# 1 赛题 问题B 玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适 宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素 , 温室的设计通常采用带有温室风扇的通风系统&#x…

minio集群部署(k8s内)

一、前言 minio的部署有几种方式&#xff0c;分别是单节点单磁盘&#xff0c;单节点多磁盘&#xff0c;多节点多磁盘三种方式&#xff0c;本次部署使用多节点多磁盘的方式进行部署&#xff0c;minio集群多节点部署最低要求需要4个节点&#xff0c;集群扩容时也是要求扩容的节点…

GPIO模式详解:推挽/开漏/浮空/上拉/下拉/施密特(迟滞)输入

GPIO(General Purpose Input Output)可用于执行数字输入或输出功能。典型的应用包括从/向模拟或数字传感器/设备读写数值、驱动LED、为I2C通信驱动时钟、生成外部组件的触发、发出中断等。 文章目录 1 GPIO简介2 输出模式2.1 推挽输出2.2 开漏输出 3 输入模式3.1 高阻态(浮空)、…