循环队列的实现
- 概念以及结构
- 实现
- 初始化
- 判空
- 判满
- 入队
- 出队
- 从队头获得元素
- 从队尾获得元素
- 释放
概念以及结构
循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
队尾指针永远指向队尾元素的下一个位置。
实现
循环队列既可以选择用数组实现也可以选择用链表(循环链表)实现。我们看看下面情况在来看看选什么结构时候循环队列。
队空
队满
既然是循环队列,那么数组下标到最后一个位置的下一个位置,这里需要特殊处理,让下标回到数组为0的地方,变成环的结构。而链表呢,我们只需要让它等于next就行了,不用特殊处理。从这个地方看链表似乎更像一个环,并且比数组更方便一些。我们先暂且不谈这个。
先谈谈如何判断队空,队满的情况
能简单用Q.front == Q.back 进行判断吗?
那么下面这种情况是队空还是队满呢?
显然Q.front == Q.back 并不能帮我们直接判断队空,队满情况。
这里有两种方法解决
1.增加一个size变量。
Q.front == Q.back && size == 0 判空
Q.front == Q.back && size == Maxsize 判满
2.多申请一个空间。队满的时候留着一个空间不用。并不是说这个空间不能插入和删除数据。
有5个数据就申请6个空间。
现在队空,队满问题解决了,那我们回过头继续来看,相比于链表,数组在满的时候需要特殊处理一下,才能回到下标为0的位置,而链表直接去next就可以了,我们会感觉链表比数组更好一些。
那么我要取队尾元素呢,数组可以根据队尾下标-1去取。链表呢,是不是得遍历一下才能取到,不然的话就在来一个prev指针记录队尾前一个位置。要不考虑用双向循环链表,但是这弄得更麻烦了。并且我malloc申请空间得时候,数组可以一下就解决,链表需要循环申请并且链接起来,比较麻烦。
结论:数组和链表实现循环队列,各有优缺点,但是更多得选择数组来实现。下面的实现我采用得也是数组实现,来源于LeetCode一道题。622. 设计循环队列
初始化
typedef struct {
int*a;
int front;
int back;
int N;//空间大小
} 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->N=k+1;
return obj;
}
判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->back;
}
判满
back下一个位置是front是满。
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->back+1)%obj->N == obj->front;
}
入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->a[obj->back]=val;
obj->back++;
//当back是最后一个位置,需要回到下标为0的位置
obj->back%=obj->N;
return true;
}
}
出队
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
obj->front++;
//当front是最后一个位置,需要回到下标为0的位置
obj->front%=obj->N;
return true;
}
}
从队头获得元素
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->a[obj->front];
}
}
从队尾获得元素
返回队尾的后一个元素。
但是如果是下面这种情况需要处理一下,back在0的位置
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->a[(obj->back-1+obj->N)%obj->N];
}
}
释放
为什么释放两次可以参考225. 用队列实现栈最后面的解释
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}