622. 设计循环队列 - 力扣(LeetCode)
思路
思路:首先循环队列的意思是:空间固定,就是提前开辟好,满了就不能插入了,但是删除数据后仍有空间,删除循环队列里面的数据后,保留它的空间,就能再插入数据,即可以重复利用之前删除的空间。
这一题的循环让我们想到一个约瑟夫环的问题,所以这题我们不加哨兵位,因为加哨兵位转的时候就可能不太好操作。
猜想:front和rear指向空(指向同一个位置),问题:得单独判断插入第一个数据的时候如何,因为你0个数据和1个数据的时候是分不清的。
对front和rear都指向空的疑问:队列有头有尾,front为头,rear为尾,两者为空指向同一节点,意味着你插入一个数据时,尾要往后走一下。这样rear有点像栈中的意思了,top意味它不是指向栈顶元素,而是指向栈顶元素的下一个。
对rear指向尾的下一个的疑问:,如果front、rear相等为空,那么满时front也等于rear,所以无法判断空和满。
因此到这里:我们最好选择rear指向尾的下一个。
改进:当然可以用size来记录链表的长度,从而区分满不满,代入接口,发现找尾不好找,所以我们能不能加一个rear的prev前驱指针从而来找尾,但是好像有点不好控制,如果这样的话,还不如直接双向链表,因此链表看起来虽然简单,但越解决越复杂。这里我们想到对于数组能够随意访问下标,我们可以尝试一下数组。
我们先来总结一下链表的点:
1.rear为尾的下一个,得改成双向才好控制。
2.rear指向最后一个,初始化和为空的时候得做特殊处理。
因此综上所述,用数组可能选择更好。
分析:rear指向尾不好,指向尾的话,初始化的话,你先初始化为0时,你是初始化的0还是插入的0是无法判断的,而且在删除的时候也要单独删除最后一个。所以我们使得rear指向尾的下一个。
其次,虽然使得rear指向尾的下一个,但是我们之前上述的疑问中所说的,空和满无法判断,这里我们称为假溢出问题。
以k=4举例,我们要多开一个空间,对多开空间而言,front等于rear即为空,因为是数组吗,我们可以直接用下标来进行访问,循环的话就用一下下标回绕的思路就好了。
rear加1回绕到front就为满。当然回绕吗,就是取模,%(k+1),对于删除来说,删除一次就往后走一次,当front等于rear时就为空。回绕的问题就解决了。
总结数组的好处:数组好找,这里rear是指向尾的下一个的,运用数组可以找到尾。
ok,我们要的条件是:front指向头、rear指向尾的下一个、多开一个空间。
当然这题已经给定空间,数组开辟好了空间之后,初始化就可以控制了,一个指向头,一个指向尾的下一个,所以相当于左闭右开。左闭右开才可以在最开始都为空,如果是左闭右闭初始化就有问题,我们在链表初始化的时候已经说过。
创建结构体
我们先看这个代码中给的这个结构体
typedef struct {
} MyCircularQueue;
前面我们分析过,为了实现这个循环队列我们要一个front代表头,rear指向尾的下一个,当然有个整型指针用来指向我们的数据,这里我们传递个我们这个数字的有效节点。
typedef struct {
int* a;
int front;
int rear;
int k;
} MyCircularQueue;
构建循环队列
接下来开始创建,对应创建的话,先创建整个结构体,在对里面的指针进行malloc,记住这里我们多扩一个。当然也需要初始化一下构建的结构体里面的数据。
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
int* b=(int*)malloc(sizeof(int)*(k+1));
obj->a=b;
obj->front=0;
obj->rear=0;
obj->k=k;
return obj;
}
判空
之后我们先写一下判断是否为空,front==rear时就为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->rear;
}
判满
接下来是判断是否为满,这个时候结对考虑轮转数组的问题了,当然%(k+1)对于没有到达下标为4的没有影响(以k=4时为例)。
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->rear+1)%(obj->k+1)==obj->front;
}
释放
然后我们先写一下比较好写的释放吧,就是释放obj的a,再释放obj,因为obj是指针,这里传递的也是指针,因此这里可以不用把obj置为空。
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
接下来还有四个接口没写,分别是插入数据,删除数据,取队头元素,取队尾元素。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
}
int myCircularQueueFront(MyCircularQueue* obj) {
}
int myCircularQueueRear(MyCircularQueue* obj) {
}
取对头元素和取队尾元素
我们先来写取对头元素和取队尾元素两个接口吧,首先这两个我们都得先判断他们是否为空,如果为空,就直接跳出循环,取对头元素比较好取,直接用front的下标就能取到,但是对于取队尾元素呢,好像不能直接写,可能得考虑取模的情况,因为可能会遇到下面这种情况。
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)];
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[obj->front];
}
插入元素和删除元素
ok,到了这里我们还剩下插入元素和删除元素,首先对应插入元素来说,我们得先判断是否满了,如果元素满了,那么由于空间是给定的,所以无法再继续插入了。如果没有满,就先考虑下图这种情况
这时rear再最后一个,这个时候要插入元素,应该回转rear。 所以应该利用取模。而对于删除元素来说,先要判断是否为空,如果为空,那么就无法删除,就直接退出,然后这里的回转运用到了我们找队尾元素的思路。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->rear]=value;
obj->rear=(obj->rear+1)%(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front=(obj->front+1)%(obj->k+1);
return true;
}
最终代码
typedef struct {
int* a;
int front;
int rear;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
int* b=(int*)malloc(sizeof(int)*(k+1));
obj->a=b;
obj->front=0;
obj->rear=0;
obj->k=k;
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->a[obj->rear]=value;
obj->rear=(obj->rear+1)%(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front=(obj->front+1)%(obj->k+1);
return true;
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)];
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[obj->front];
}
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);
*/