【LeetCode数据结构】栈和队列的应用——设计循环队列问题详解
🔥个人主页:艾莉丝努力练剑
❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题
🍉学习方向:C/C++方向
⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平
前言:牛客网和LeetCode的刷题都不可或缺,我们都要做一做,无论是参加竞赛还是笔试面试,至少能提升你的代码能力!洛谷的题目也可以去做一做。力扣的题目对提升代码能力很有帮助,需要有一点基础,几乎都是接口型的题目,关于接口型和IO型的区别我们在本专栏的第一篇【LeetCode】力扣题——轮转数组、消失的数字、数组串联中就介绍过了,这里不再赘述,我们进入今天的力扣题目介绍——
目录
正文
一、设计循环队列问题
(一)设计循环队列
(二)思路
1、摸索
2、思路
(三)解题过程
1、结构体定义
2、循环队列初始化
3、判断是否为空
4、判断是否为满
5、向循环队列插入一个元素
6、从循环队列中删除一个元素
7、取队头——从队首获取一个元素
8、取队尾——获取队尾元素
9、循环队列的销毁
(四)完整代码
1、写法1
2、写法2
结尾
正文
一、设计循环队列问题
622.设计循环队列
博主题解链接:额外申请一个空间,里面不保存任何数据,解决设计循环队列问题
题目描述:
题目示例和提示——
(一)设计循环队列
有一种特殊的队列叫循环队列,环形队列首尾相连成环
环形队列特点:
(1)队头删数据,队尾插入数据;
(2)给定固定的空间,使用过程中不可以扩容;
(3)环形队列满了,不能继续插入数据(即插入数据失败)。
环形队列可以使用数组实现,也可以使用循环链表实现。
(二)思路
1、摸索
(1)用链表实现——
循环通过ptail实现——
我们在定义链表的时候,既要定义里面的值,还要让它指向下一个节点,所以每个节点里面就有两个成员,除此之外我们针对这个链表要再创建一个结构体分别指向ptail和phead,所以对于链表来说我们定义这个结构的时候代价会更大,要创建两个结构体。
(2)用数组实现——
我们用除余法实现循环——
数组就不一样了,只需要一个结构体就可以搞定。
(3)综上
环形队列可以使用数组实现,也可以使用循环链表实现。只不过从结构以及初始化角度来说,我们用数组来实现会更简单一些。
2、思路
rear表示尾,front表示头,front和rear都指向下标为0这个位置。
环形队列为空:front == rear
环形队列满了:front == rear
那么我们如何去表示当前的环形队列是空了还是满了?
如果我们再增加一个int类型的计数器size来记录有效个数,是不是又额外增加4个字节?
要求在环形队列结构中不额外增加计数器成员来保存队列中有效的数据个数。
只说队列环形队列只能保存四个数据,没说只能是四个空间呀!那么我就多增加一块空间,理论上我应该申请k个空间,那我们申请k+1个大小的空间,每次增加数据,rear就++。
我们的思路是:我们额外申请一个空间,但是里面不保存任何数据。这里我们无非是浪费了一个空间,而不需要额外去增加结构体的变量。
我们来分析一下题目——
front会不会越界?会。也要除余,回到下标为0的位置
(三)解题过程
1、结构体定义
//结构体定义
typedef struct
{int* arr;int front;//队头int rear;//队尾int capacity;//循环队列的空间大小
} MyCircularQueue;
2、循环队列初始化
//循环队列初始化
MyCircularQueue* myCircularQueueCreate(int k)
{MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));//申请k+1个空间pq->arr = (int*)malloc(sizeof(k+1));pq->front = pq->rear = 0;pq->capacity = k;return pq;//返回指向循环队列的指针
}
3、判断是否为空
//判断是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{return obj->front == obj->rear;
}
4、判断是否为满
//判断是否为满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{//把k保存在capacity里面return (obj->rear+1)%(obj->capacity+1) == obj->front;
}
5、向循环队列插入一个元素
//向循环队列插入一个元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{//先判断是否满了//因为后面要用到,所以我们把判断是否为空、判断是否为满拿到上面来if(myCircularQueueIsFull()obj){return false;}//空间足够,没有满obj->arr[obj->rear++] = value;obj->rear %= obj->capacity + 1; return true;
}
6、从循环队列中删除一个元素
//从循环队列中删除一个元素
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{if(myCircularQueueIsEmpty(obj)){return false;}//非空++obj->front;obj->front %= obj->capacity + 1;return true;
}
7、取队头——从队首获取一个元素
//取队头——从队首获取一个元素
int myCircularQueueFront(MyCircularQueue* obj)
{//判断是否为空if(myCircularQueueIsEmpty(obj)){return -1;}//不为空return obj->arr[obj->front];
}
8、取队尾——获取队尾元素
这里再-1,就变成这样了——
存在这种情况,所以我们这边要特殊处理一下,让prev走到下标为4的位置——
// 取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{if (myCircularQueueIsEmpty(obj)) {return -1;}// 计算队尾索引:rear 指向的是下一个空位,队尾是 rear 的前一位int prev = (obj->rear - 1 + obj->capacity + 1) % (obj->capacity + 1);return obj->arr[prev];
}
9、循环队列的销毁
// 循环队列的销毁
void myCircularQueueFree(MyCircularQueue* obj)
{if (obj) {free(obj->arr); // 释放数组free(obj); // 释放队列结构体}
}
(四)完整代码
1、写法1
//结构体定义
typedef struct {int* arr;int front;//队头int rear;//队尾int capacity;//循环队列的空间大小
} MyCircularQueue;//循环队列初始化
MyCircularQueue* myCircularQueueCreate(int k)
{MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));//申请k+1个空间pq->arr = (int*)malloc((k+1)*sizeof(int));pq->front = pq->rear = 0;pq->capacity = k;return pq;//返回指向循环队列的指针
}
//判断是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{return obj->front == obj->rear;
}
//判断是否为满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{//把k保存在capacity里面return (obj->rear+1)%(obj->capacity+1) == obj->front;
}
//向循环队列插入一个元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{//先判断是否满了//因为后面要用到,所以我们把判断是否为空、判断是否为满拿到上面来if(myCircularQueueIsFull(obj)){return false;}//空间足够,没有满obj->arr[obj->rear++] = value;obj->rear = (obj->rear)%( obj->capacity + 1); return true;
}
//从循环队列中删除一个元素
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return false;}//非空++obj->front;obj->front %= obj->capacity + 1;return true;
}//取队头——从队首获取一个元素
int myCircularQueueFront(MyCircularQueue* obj) {//判断是否为空if(myCircularQueueIsEmpty(obj)){return -1;}//不为空return obj->arr[obj->front];
}
//取队尾——获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}int prev = obj->rear-1;if(obj->rear == 0){prev = obj->capacity;}return obj->arr[prev];
}
//循环队列的销毁
void myCircularQueueFree(MyCircularQueue* obj) {//arr pqif(obj->arr)free(obj->arr);free(obj);obj = NULL;
}
2、写法2
// 结构体定义
typedef struct
{int* arr;int front; // 队头int rear; // 队尾int capacity; // 循环队列的空间大小(实际可存储元素数量)
} MyCircularQueue;// 循环队列初始化
MyCircularQueue* myCircularQueueCreate(int k)
{MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));// 修正:分配 k+1 个 int 类型的空间(而非 k+1 的类型大小)pq->arr = (int*)malloc((k + 1) * sizeof(int)); // 关键修复处pq->front = pq->rear = 0;pq->capacity = k; // 实际可存 k 个元素,数组总大小为 k+1(留一个空位区分满/空)return pq;
}// 判断是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{return obj->front == obj->rear;
}// 判断是否为满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{// 满的条件:(rear + 1) 对 (capacity + 1) 取模等于 frontreturn (obj->rear + 1) % (obj->capacity + 1) == obj->front;
}// 向循环队列插入一个元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{if (myCircularQueueIsFull(obj)) {return false;}obj->arr[obj->rear] = value;obj->rear = (obj->rear + 1) % (obj->capacity + 1); // 合并自增和取模,更简洁return true;
}// 从循环队列中删除一个元素
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{if (myCircularQueueIsEmpty(obj)) {return false;}obj->front = (obj->front + 1) % (obj->capacity + 1); // 合并自增和取模return true;
}// 取队头元素
int myCircularQueueFront(MyCircularQueue* obj)
{if (myCircularQueueIsEmpty(obj))
{return -1;}return obj->arr[obj->front];
}// 取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{if (myCircularQueueIsEmpty(obj)) {return -1;}// 计算队尾索引:rear 指向的是下一个空位,队尾是 rear 的前一位int prev = (obj->rear - 1 + obj->capacity + 1) % (obj->capacity + 1);return obj->arr[prev];
}// 循环队列的销毁
void myCircularQueueFree(MyCircularQueue* obj)
{if (obj)
{free(obj->arr); // 释放数组free(obj); // 释放队列结构体}
}/*** Your MyCircularQueue struct will be instantiated and called as such:* MyCircularQueue* obj = myCircularQueueCreate(k);* bool param_1 = myCircularQueueEnQueue(obj, value);* bool param_2 = myCircularQueueDeQueue(obj);* int param_3 = myCircularQueueFront(obj);* int param_4 = myCircularQueueRear(obj);* bool param_5 = myCircularQueueIsEmpty(obj);* bool param_6 = myCircularQueueIsFull(obj);* myCircularQueueFree(obj);
*/
结尾
往期回顾:
【LeetCode&数据结构】栈和队列的应用——用队列实现栈问题、用栈实现队列问题详解
关于【往期回顾】的说明:
由于本专栏的篇数越来越多,为了避免文章链接挂太多影响观感,博主之后的每一篇力扣刷题详解都只会附上前一篇的链接,最后两次完整的链接是在之后会发布的【栈的应用——有效的括号问题详解】和【单链表的应用——环形链表问题详解】的文章结尾,本意是为了方便大家找到相应的详细的题解,现在文章多了,铸币博主没办法一一罗列,而且还会有“凑字数”的嫌疑,力扣刷题专栏的链接每次都会放在文章开头的位置,大家可自行前往!
以后每次的【往期回顾】都会放上一篇的链接,在<关于【往期回顾】的说明:>的后面博主会放上本专栏中最后一次完整呈现之前全部文章(到那一篇为止)的【单链表的应用——环形链表问题详解】或者【栈的应用——有效的括号问题详解】的链接。
再次感谢大家的理解与支持!
【LeetCode&数据结构】栈的应用——有效的括号问题详解
【LeetCode&数据结构】单链表的应用——环形链表问题详解
结语:本篇文章到这里就结束了,本文讲述的两道代码题并不适合C语言初学者,需要有一定的C语言基础,最好要学过数据结构与算法的算法复杂度和链表的知识,才能写出复杂度较优的代码来。大家一定要自己动手敲一敲,不敲的话不仅容易忘记,也不方便将来复习。