题目如下:
解题过程如下:
先简单介绍一下循环队列吧!
循环队列是一种特殊的队列,环形队列首尾相连成环。
环形队列特点:
- 队头删数据,队尾插入数据
- 给定固定的空间,使用过程中不可以扩容
- 环形队列满了,不能继续插入数据(即插入数据失败)
- 要求在环形队列结构中不额外增加计数器成员来保存队列中有效的数据个数
循环队列可以使用数组实现,也可以使用循环链表实现,那么哪一种实现方式更好呢?
其实数组从结构(数组就一个结构体)、初始化上更简单。而用链表实现,定义的结构体会更大些,因为除了要定义结点的结构还要定义队列的结构。
综上,选择数组实现循环队列!
链表可以通过指针来实现循环,那么数组怎么实现呢?
以下图为例,当数组中最后一个空间被填满时,rear此时是4,rear余上数组空间大小即可,这里是rear %= 4
,这样,rear又会回到下标为0的位置:
我们发现:不管环形队列为空还是满了,都满足front == rear,那么如何区分环形队列是空还是满呢?
可以使队列长度加1但是不影响原来的队列长度,环形队列满了满足(rear + 1) % (k + 1) == front
那么我们之后都是用上面的图来分析循环队列,环形队列满了满足(rear + 1) % (k + 1) == front
,环形队列空了满足front == rear
。
数组怎么实现出队列呢?以下图为例,链表实现循环队列的时候,phead始终指向队头数据,此时phead指向1数据结点,1数据结点出队列,此时phead指向2数据结点,也就意味着队头数据发生了改变,原先数据1结点里数据域就被空出来了;同理,数组就是front指向队头数据,直接front++,队头数据来到了2,下标为0处没有存储数据了。
写代码时需要注意:
-
判断为空为满函数放到判断为入队列出队列函数的前面,因为入队列出队列函数要用到判断为空为满函数。
-
分析"向循环队列中插入一个数据"实现方法:
下图所示,当队列没有满时,第一种情况直接用arr[rear++] == value
即可插入一个数据;第二种情况是特殊情况,使用第一种情况的代码rear会有越界的风险,此时rear应在下标为0处,那么在代码arr[rear++] == value
的基础上加上代码rear %= (k + 1)
。
//向循环队列中插入一个数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//先判断队列是否为满
if (myCircularQueueIsFull(obj))
return false;
//没满,插入数据
obj->arr[obj->rear++] = value;
obj->rear %= obj->capacity + 1;
return true;
}
- 分析"向循环队列中删除一个数据"实现方法:
同理,front也会有越界的风险,代码相似
//向循环队列中删除一个数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//先判断队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return false;
}
//没有空,删除一个数据
++obj->front;
obj->front %= obj->capacity + 1;
return true;
}
- 分析"获取队尾元素"实现方法:
建立一个变量prev指向rear的前一个下标位置,prev指向的数据就是队尾数据!
但是当rear指向下标0,prev就会有越界的风险:
//获取队尾元素
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];
}
完整代码:
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(sizeof(int)* (k + 1));
pq->front = pq->rear = 0;
pq->capacity = k;
return pq;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
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->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) {
if (obj->arr)
free(obj->arr);
free(obj);
obj = NULL;
}
/**
* 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);
*/