前言
队列会出现“假溢出”现象,即队列的空间有限,队列是在头和尾进行操作的,当元素个数已经达到最大个数时,队尾已经在空间的最后面了,但是对头前面的不一定是满的。针对这一现象,引入了循环队列。循环队列也是一种数据结构,小编在本篇文章中,是以力扣的一道题目为例来设计循环队列。
此时队尾rear
已经到最后面了,但是队头front
前面没有填满元素,因此并没有满
循环队列就是将队尾rear
再次回到数组的前面,解决“假溢出”的现象
继续在队尾rear
插入元素,直到真的满了
描述
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
- MyCircularQueue(k): 构造器,设置队列长度为 k 。
- Front: 从队首获取元素。如果队列为空,返回 -1 。
- Rear: 获取队尾元素。如果队列为空,返回 -1 。
- enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
- deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
- isEmpty(): 检查循环队列是否为空。
- isFull(): 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3 circularQueue.enQueue(1); // 返回 true circularQueue.enQueue(2); // 返回 true circularQueue.enQueue(3); // 返回 true circularQueue.enQueue(4); // 返回 false,队列已满 circularQueue.Rear(); // 返回 3 circularQueue.isFull(); // 返回 true circularQueue.deQueue(); // 返回 true circularQueue.enQueue(4); // 返回 true circularQueue.Rear(); // 返回 4
分析
在普通的队列中,队满的条件是rear==front
,那么在循环队列中怎么判断队列已经满了?
常用的一个解决方法是,多开一个空间。也就是说,当队列满的时候,还是有一个空间没有被使用。
rear
可能比front
大,也可能比front
所以尽管它们只相差一个位置时就是满的情,但也可能是相差整整一圈,所以若队列长度为k
,那么需要多开辟一个空间,即k+1
。因此,队满的额条件为(rear+1)%(k+1)==front
。取模“%”的目的就是为了整合front
和rear
大小的问题,解决多绕了一圈的问题。
什么时候队列为空呢?
rear==front
时队列为空。
总结一下就是:
- 当
rear==front
时队列为空; - 当
rear
的下一个和front
相等,则队列满
分析完队满、对空问题,回到本道题目就很好解决了。
构造器:
MyCircularQueue* obj
是一个结构体指针,需要给obj
开辟一个空间,其次开辟一个数组a
,初始化front
和 back
以及k
MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue)); obj->a=(int *)malloc((k+1)*sizeof(int)); obj->front=0; obj->back=0; obj->k=k; return obj; }
判断队空、队满:
bool myCircularQueueIsEmpty(MyCircularQueue* obj) { return obj->front==obj->back; } bool myCircularQueueIsFull(MyCircularQueue* obj) { return (obj->back+1)%(obj->k+1)==obj->front; }
队尾插入:
首先需要判断队列是否已经满了,如果满了返回false
,如果没有,执行插入操作。先插入,再将队尾obj->back++
。
需要注意的是,这里是一个循环队列,如果此时已经在最后一个单元里面插入元素,那么obj->back
应该是回到前面,而不是继续往后。此时有需要用到取模“%”操作,使得 obj->back=(obj->back)%(obj->k+1)
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { if(myCircularQueueIsFull(obj)) return false; obj->a[obj->back]=value; obj->back++; obj->back=(obj->back)%(obj->k+1); return true; }
队头删除:
删除只需要将obj->front++即可
但是依然需要注意,这是一个循环队列,当obj-front在最后一片单元时,需要回到前面,obj->front=(obj->front)%(obj->k+1)
bool myCircularQueueDeQueue(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return false; } obj->front++; obj->front=(obj->front)%(obj->k+1); return true; }
取队头元素:
先判断队列是否为空,不为空执行取队头操作
int myCircularQueueFront(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) return -1; return obj->a[obj->front]; }
取队尾元素:
首先判断队列是否为空,不为空执行取队尾元素操作
需要注意的是,当obj->back==0
时,此时取得应该是最后一个存储单元的元素,这里需要单独判断一下,其余的情况都是obj->back
前面一个单元的元素。
int myCircularQueueRear(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)){ return -1; } else if(obj->back==0) { return obj->a[obj->k]; } else { return obj->a[obj->back-1]; } }
销毁:
void myCircularQueueFree(MyCircularQueue* obj) { free(obj->a); free(obj); }
力扣AC代码
typedef struct { int *a; int front; int back; int k; } MyCircularQueue; MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue)); obj->a=(int *)malloc((k+1)*sizeof(int)); obj->front=0; obj->back=0; obj->k=k; return obj; } bool myCircularQueueIsEmpty(MyCircularQueue* obj) { return obj->front==obj->back; } bool myCircularQueueIsFull(MyCircularQueue* obj) { return (obj->back+1)%(obj->k+1)==obj->front; } bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { if(myCircularQueueIsFull(obj)) return false; obj->a[obj->back]=value; obj->back++; obj->back=(obj->back)%(obj->k+1); return true; } bool myCircularQueueDeQueue(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return false; } obj->front++; obj->front=(obj->front)%(obj->k+1); 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->back+obj->k)%obj->k+1]; else if(obj->back==0) { return obj->a[obj->k]; } else { return obj->a[obj->back-1]; } } 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); */