这篇我们说一下队列里的循环队列,然后做一下LeetCode里的题。
环形队列可以使用数组实现,也可以使用循环链表实现。
循环队列的特点:
1.符合先进先出
2.空间大小是固定的
然后,我们来看一下空的循环队列,和满的循环队列是什么样子:
为什么会这样设计:
原因:为了避免空和满混洗,无法区分。在设计循环队列时,无论使用数组还是链表实现,都要多开一个空间,也就意味着,要是一个存k个数据的循环队列,要开k+1个空间。
也就是说:
front==rear 就为空
front=rear+1 就为满
然后,我们来看一下循环队列的使用流程:
假设我们要插入3个数据:
此时,循环队列就为满的,不能在插入了。
当我们想要删除数据,我们只需要front++一下就行:
此时,循环队列就是空,当我们在插入数据:
此时,循环队列就为满,不能插入了。
这就是循环队列的一个基本流程,那么我们是用数组实现还是链表实现呢?
答案是:我们用数组来实现。
我们根据LeetCode里的题来实现:
OJ链接
首先,我们先把循环队列的结构弄出来:
typedef int CQueueDataType;
typedef struct {
CQueueDataType* arr;
int front;//队头
int rear;//队尾
int k;//有效数据
} MyCircularQueue;
第二,我们要开辟一个循环队列的空间,然后给里面的内容初始化,返回指向这个循环队列的指针:
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr=(CQueueDataType*)malloc(sizeof(CQueueDataType)*(k+1));
obj->front=0;
obj->rear=0;
obj->k=k;
return obj;
}
第三,我们要向循环队列插入一个元素:
插入,我们要分情况来讨论:
第一种情况:rear不在边界
这样,我们直接插到rear处,然后rear++。
第二种情况:rear在边界
这种情况,插入到rear之后,我们需要将rear置为0处。
代码如下:
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//如果满了,返回假
if(myCircularQueueIsFull(obj))
return false;
//rear在边界
if(obj->rear==obj->k)
{
obj->arr[obj->rear]=value;
obj->rear=0;
}
else
{
obj->arr[obj->rear]=value;
obj->rear++;
}
return true;
}
第四,我们要循环队列删除一个元素:
删除,我们也要分情况来讨论:
第一种情况:front不在边界
这种情况,我们直接让front++
第二种情况:front在边界
这种情况,就是让front置为0
代码如下:
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//如果为空,返回假
if(myCircularQueueIsEmpty(obj))
return false;
//front在边界
if(obj->front==obj->k)
{
obj->front=0;
}
else
{
obj->front++;
}
return true;
}
第五,我们要取循环队列队头元素:
这个非常简单,front就是队头元素:
int myCircularQueueFront(MyCircularQueue* obj) {
//如果为空,返回-1
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->arr[obj->front];
}
第六,我们要取循环队列队尾元素:
取队尾元素,我们要分情况讨论:
第一种情况:
因为rear是指向数据元素的下一个位置,所以最后一个元素就是rear-1
第二种情况:
这里rear在数据0位置处,那么队尾元素就是最后一个,也就是第k个
代码如下:
int myCircularQueueRear(MyCircularQueue* obj) {
//如果为空,返回-1
if(myCircularQueueIsEmpty(obj))
return -1;
//rear在0处
if(obj->rear==0)
{
return obj->arr[obj->k];
}
else
{
return obj->arr[obj->rear-1];
}
}
第七,我们要判断循环队列是否为空:
这个非常简单,我们在前面说过rear==front就为空:
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->rear==obj->front;
}
第八,我们要判断循环队列是否为满:
第一种情况:
此时,rear+1==front就为满,前面已经说过。
代码如下:
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if(obj->front==0&&obj->rear==obj->k)
{
return true;
}
else
{
return obj->rear+1==obj->front;
}
}
第九,销毁循环队列:
第一,我们要销毁里面的数组。
第二,我们要销毁结构体。
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
free(obj);
}
到这里,循环队列的基本接口函数就完成了,这个以后我们会在生产者消费者模型里用到。如果大家觉得这篇文章有帮助,大家可以多加支持一下,谢谢大家。