设计一种循环队列,线性数据结构,其操作表现为 FIFO(先进先出)原则且队尾被连接在队首之后形成一个循环,称作“环形缓冲器”
循环队列的好处是可以利用这个队列之前使用过的空间,但是他的空间大小是固定的
循环队列我们使用单链表或者数组都能实现,这里先讨论使用数组的方法
数组不像单链表一样可以直接做到循环,需要进行一定的操作
由 head 和 tail 在之间跳动实现了数据的出入与循环,有限的空间保证先进先出重复使用
tail 指向队尾数据的下一个空间
现在我们来看一下什么时候循环链表为空,什么时候循环链表为满呢?
根据前面的情况,当 head == tail 的时候队列为空
那什么时候满呢?
如此看来如果说空和满的情况都是一样的,那肯定不行。所以我们得换一种判断方式
解决方案一: 增加一个 size 来记录空还是满, size = 0 则为空, size = k 则为满,那size = k 时,tail 的前一个就是尾, head 就是头
解决方法二: 额外多开一个空间(官方推荐)
当 head == tail 的时候为空, 当 tail + 1 % (k + 1) == head 就是满
- 初始化
- 判空
- 判满
- 插入数据,失败返回false
- 删除数据,失败返回false
- 取队头数据
- 取队尾数据
- 销毁该队列
初始化
我们先创建一下这个队列的结构体
typedef struct {
int* a;
int head; //指向头
int tail; //指向尾的下一个
int k;
}MyCircularQueue;
一共有四个成员,一个数组用于存放数据,head 指向头, tail 指向尾, k 是该队列能存放的最大数据个数
MyCircularQueue* MyCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//防止假溢出问题,增加一个空间来判断空与满
obj->a = (int*)malloc(sizeof(int) * (k + 1));
obj->head = 0;
obj->tail = 0;
obj->k = k;
}
我们采用之前说的解决方案二,增加一个空间
判空
bool MyCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->head == obj->tail;
}
判满
//判满
bool MyCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->tail + 1) % (obj->k + 1) == obj->head;
}
当我们有数字循环的时候比如 0 1 2 3 0 1 2 3 这样,多用取模来处理数据
用大于自己的数来模自己还是本身,用这个特性,我们可以轻松化解循环的数字问题
插入一个数据,成功就返回 true 失败就返回 false
bool MyCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if (MyCircularQueueIsFull(obj))
return false;
obj->a[obj->tail] = value;
obj->tail++;
obj->tail %= (obj->k + 1);
return true;
}
我们都知道插入完一个数据 tail 往后走一格,当到了数组的最后的时候 tail 要跳到最前面去,所以不能一味的加加
要解决的情况是当 tail 小于 k 时 ,tail++,当 tail == k 的时候,不能再加加而是变成 0 ,这个时候取模又能直接完成这件事
obj->tail %= (obj->k + 1) 与前面判满的时候取模原理一样,不再过多赘述
删除一个数据,失败返回false
bool MyCircularQueueDeQueue(MyCircularQueue* obj)
{
if (MyCircularQueueIsEmpty(obj))
return false;
obj->head++;
obj->head %= (obj->k + 1);
return true;
}
删除数据的时候不可以直接把需要删除数据的空间给 free 掉, malloc 开辟额一连串的数据不可以分开进行销毁
返回队首的值
int MyCircularQueueFront(MyCircularQueue* obj)
{
if (MyCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->head];
}
返回队尾的值
int MyCircularQueueRear(MyCircularQueue* obj)
{
if (MyCircularQueueIsEmpty(obj))
return -1;
else
return obj->tail == 0 ? obj->a[obj->k] : obj->a[obj->tail - 1];
//return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k + 1)]; 这样写更装逼
}
这里依然要注意当 tail 为 0 的时候,不能再减一了,而是要跳到队尾去
队列的销毁
//队列的销毁
void MyCircularQueueFree(MyCircularQueue* obj)
{
free(obj->a);
free(obj);
}
这里的数组和结构体是嵌套的,所以不能直接销毁了结构体就不管了,要先将 obj 中的数组销毁掉再销毁结构体
插一嘴,如果用单链表来代替数组的话,想要取尾数据就会比较的复杂,各有利弊,两者都需要思考思考才能解决