目录
编辑
前情回顾:
前言:
认识循环队列:
实现循环队列的思路:
题目:《设计循环队列》
1.判满和判空:
2.添加数据和删除
3.计算循环队列的数据个数
4.返回对队尾元素
总结:
我们在上一篇好题分析中,分析了以下几题:
《有效括号》《用队列实现栈》《用栈实现队列》
上一篇的好题分析的blog:好题分享(2023.11.12——2023.11.18)-CSDN博客
在本周的好题分享当中,我们需要了解一下关于循环队列的基础知识,以及代码OJ题。
因此本周的好题分享,有且仅有一道Leecode的OJ题——《设置循环队列》
循环队列是一种特殊类型的队列,它通过使用固定大小的数组(或其他数据结构)来实现队列的基本操作。循环队列克服了普通队列在出队列后无法再次利用队列空间的问题。
以下是循环队列的一些特点和基本操作:
特点:
1.使用数组: 循环队列使用数组来存储元素。
2.固定大小: 循环队列有一个固定的大小,这意味着它的空间是有限的。
3.循环利用空间: 当队列的尾部到达数组的末尾时,下一个元素将被插入到数组的开头,实现了循环利用队列的空间。
基本操作:
4.初始化: 初始化循环队列,需要指定数组的大小,并设置前端(Front)和后端(Rear)的初始位置。
5.入队列(enqueue): 将元素添加到队列的尾部。在普通队列中,尾部指针会一直向后移动;而在循环队列中,如果尾部指针已经到达数组的末尾,下一个元素将被插入到数组的开头。
6.出队列(dequeue): 从队列的前端移除元素。在普通队列中,前端指针会一直向后移动;而在循环队列中,如果前端指针已经到达数组的末尾,下一个元素将在数组的开头。
7.判空(isEmpty): 检查循环队列是否为空。
8.判满(isFull): 检查循环队列是否已满。
示意图:
Front Rear
| |
+---+---+---+---+---+
| 3 | 9 | 7 | | |
+---+---+---+---+---+
在这个示意图中,Front 指向元素3,Rear 指向元素7。如果入队列一个新元素,它将被插入到Rear的后一个位置,实现了循环。
对于我们该如何实现循环队列是一个需要我们深思的问题,我们可以使用顺序表来完成我们循环队列的实现,具体操作如下:
1.开辟k个空间的顺序表,但我们故意多开辟一个空间,其中这个空间里不含有任何数据!
2.此时我们就可以利用这个多余的空间进行一系列的操作,最重要的判空和判满都有了很好地解决措施!
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
typedef struct {
int front;
int back;
int *a;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (int*)malloc(sizeof(int)* (k + 1));
obj->front = obj->back = 0;
obj->k = k;
return obj;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if ((obj->back + 1) % (obj->k + 1) == obj->front)
{
return false;
}
obj->a[obj->back] = value;
obj->back++;
obj->back = obj->back % (obj->k + 1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (obj->front == obj->back)
{
return false;
}
obj->front++;
obj->front = obj->front % (obj->k + 1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (obj->front == obj->back)
{
return -1;
}
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (obj->front == obj->back)
{
return -1;
}
return obj->a[((obj->back) + obj->k) % (obj->k + 1)];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
if (obj->front == obj->back)
{
return true;
}
return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if ((obj->back + 1) % (obj->k + 1) == obj->front)
{
return true;
}
return false;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
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);
*/
对于任何循环队列,我们应当清楚队列为空是什么样子的,队列满了是什么样子的?
队列为空:back == front;
队列满:(back + 1)%(k+1) == front;
这里的“空数据”其实就是队列的头位置,即front指向的地方。
循环队列我们可以把它想象成一个圆圈来看,但是其逻辑存储应当以顺序表的视野来看。
这里的取模操作,意思和之前我们讲的循环链表类似。
即back到达K+1的位置时,相当于转了一圈,因此我们取模k+1则会得到0.
0就为整个队列也是圈的起点。
添加数据好理解,就是back遍历,遍历到的地方就赋值操作,同时back++。
当back如图时,此时就是满的。将不再进行添加数据。
我们先讲到这里,待会还会进行补充。
如果现在我们想要删除数据且删除的是第一个数据。
我们可以直接front++即可。如图:
原本的数据可以不释放,也可以不置空。
因为此时的front指向的是下标为1的地方,所以(back + 1) % (k+1) != front
所以我们还是可以进行增加数据的操作。
此时此刻,我们就可以在我们新开辟出来的那一块空间增加数据。
同时原来front指向的那块空间,就变成了之前的临时空间,如图:
我们再次进行判满操作:(back+1) == 7 (k+1) == 6 7%6 == 1
此时front == 1,所以当前是满的,这个代码实现也没问题。
但是!
如果我继续删除,front就会++到下标为2的地方,如图:
那我还想要增加数据,那back此时已经越界了,增加数据也只能在下标为0的地方增加,那我的back该怎么回到下标为0的地方呢?
很简单
back = back % (k+1);
只要back一到达k+1的位置时,取模操作就会变成0,这样就会直接返回到下标为0的地方。
而只要小于k+1也不会有事,因为%一个比自己大的数就是自己。
所以就有如图:
back就会回到这里。
同理front也会最终++到越界,所以我们也需要将
front = front % k+1.
如此增加数据和删除数据的思路就此全部实现。
我们通过之前的学习,对于数组的下标和数组的元素个数肯定有一定的理解,我们不难发现
下标数 == 该下标之前的元素个数
所以求数据个数可以选择 back - front
但是如果是这种情况:
此时是满的,本来是5个数据,这么算下来缺是-1个数,很明显这是不对的。
因为我们没有考虑到该队列是一个循环队列!
如果出现了以上的情况,back在front和后面,我们可以得出结论,back至少走了一圈
所以我们可以
(back - front + (k+1)) % (k+1)
这里我们同样也需要特别注意,因为我们在添加完数据后,back会++,也可能会返回到下标为0的位置。
因此我们可不能直接return a[back--];
我们应当 (K + back) % k+ 1;
关于实现一个循环队列我们就讲到这里,本题难度不大,我们只要动手画一画图就可以写出这些代码,思路才是最重要的!
下一篇blog我们会开启《树》的内容并且介绍介绍堆。
记住“坐而言不如起而行”
Action speak louder than words.