好题分享(2023.11.19——2023.11.25)

好题分享(2023.11.19——2023.11.25)_第1张图片

目录

​编辑

前情回顾:

前言:

认识循环队列:

实现循环队列的思路:

题目:《设计循环队列》 

1.判满和判空:

2.添加数据和删除

3.计算循环队列的数据个数

 4.返回对队尾元素

总结:


前情回顾:

我们在上一篇好题分析中,分析了以下几题:

《有效括号》《用队列实现栈》《用栈实现队列》

上一篇的好题分析的blog:好题分享(2023.11.12——2023.11.18)-CSDN博客

前言:

 在本周的好题分享当中,我们需要了解一下关于循环队列的基础知识,以及代码OJ题。

因此本周的好题分享,有且仅有一道Leecode的OJ题——《设置循环队列》

认识循环队列:

好题分享(2023.11.19——2023.11.25)_第2张图片

循环队列是一种特殊类型的队列,它通过使用固定大小的数组(或其他数据结构)来实现队列的基本操作。循环队列克服了普通队列在出队列后无法再次利用队列空间的问题。
以下是循环队列的一些特点和基本操作:
特点:

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.此时我们就可以利用这个多余的空间进行一系列的操作,最重要的判空和判满都有了很好地解决措施!

好题分享(2023.11.19——2023.11.25)_第3张图片

 

 

题目:《设计循环队列》 

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

好题分享(2023.11.19——2023.11.25)_第4张图片

 




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);
*/

1.判满和判空:

对于任何循环队列,我们应当清楚队列为空是什么样子的,队列满了是什么样子的?

队列为空:back == front;

好题分享(2023.11.19——2023.11.25)_第5张图片队列满:(back + 1)%(k+1) == front;

好题分享(2023.11.19——2023.11.25)_第6张图片

这里的“空数据”其实就是队列的头位置,即front指向的地方。

循环队列我们可以把它想象成一个圆圈来看,但是其逻辑存储应当以顺序表的视野来看。

这里的取模操作,意思和之前我们讲的循环链表类似。

即back到达K+1的位置时,相当于转了一圈,因此我们取模k+1则会得到0.

0就为整个队列也是圈的起点。

 好题分享(2023.11.19——2023.11.25)_第7张图片

 

2.添加数据和删除

 好题分享(2023.11.19——2023.11.25)_第8张图片

 添加数据好理解,就是back遍历,遍历到的地方就赋值操作,同时back++。

当back如图时,此时就是满的。将不再进行添加数据。

我们先讲到这里,待会还会进行补充。

如果现在我们想要删除数据且删除的是第一个数据。

我们可以直接front++即可。如图:

好题分享(2023.11.19——2023.11.25)_第9张图片

原本的数据可以不释放,也可以不置空。

因为此时的front指向的是下标为1的地方,所以(back + 1) % (k+1) != front

所以我们还是可以进行增加数据的操作。

此时此刻,我们就可以在我们新开辟出来的那一块空间增加数据。

同时原来front指向的那块空间,就变成了之前的临时空间,如图:

好题分享(2023.11.19——2023.11.25)_第10张图片

我们再次进行判满操作:(back+1) == 7   (k+1) == 6   7%6 == 1

此时front == 1,所以当前是满的,这个代码实现也没问题。

但是!

如果我继续删除,front就会++到下标为2的地方,如图:

好题分享(2023.11.19——2023.11.25)_第11张图片 

那我还想要增加数据,那back此时已经越界了,增加数据也只能在下标为0的地方增加,那我的back该怎么回到下标为0的地方呢?

很简单

back = back % (k+1);

只要back一到达k+1的位置时,取模操作就会变成0,这样就会直接返回到下标为0的地方。

而只要小于k+1也不会有事,因为%一个比自己大的数就是自己。

所以就有如图:

好题分享(2023.11.19——2023.11.25)_第12张图片 

back就会回到这里。

 同理front也会最终++到越界,所以我们也需要将

front = front % k+1.

如此增加数据和删除数据的思路就此全部实现。

3.计算循环队列的数据个数

 我们通过之前的学习,对于数组的下标和数组的元素个数肯定有一定的理解,我们不难发现

下标数 == 该下标之前的元素个数

所以求数据个数可以选择 back - front

但是如果是这种情况:

好题分享(2023.11.19——2023.11.25)_第13张图片

此时是满的,本来是5个数据,这么算下来缺是-1个数,很明显这是不对的。

因为我们没有考虑到该队列是一个循环队列!

如果出现了以上的情况,back在front和后面,我们可以得出结论,back至少走了一圈

所以我们可以

(back - front + (k+1)) % (k+1)

 4.返回对队尾元素

 这里我们同样也需要特别注意,因为我们在添加完数据后,back会++,也可能会返回到下标为0的位置。

因此我们可不能直接return a[back--];

我们应当 (K + back) % k+ 1;

总结:

关于实现一个循环队列我们就讲到这里,本题难度不大,我们只要动手画一画图就可以写出这些代码,思路才是最重要的!

下一篇blog我们会开启《树》的内容并且介绍介绍堆。

记住“坐而言不如起而行”

Action speak louder than words.

你可能感兴趣的:(好题分享,数据结构,c语言,经验分享,笔记,LeeCode,leetcode)