设计循环队列
- 1.题目描述
- 2.思路
- 3.代码实现以及分析
- 3.1 创建结构体
- 3.2创建一个具体的循环队列
- 3.3判断是否为空 和 判断是否为满
- 4. 进队列 和 出队列
- 5.取队首和队尾元素
- 6.释放空间
- 7.总结
1.题目描述
设计循环队列
2.思路
环形队列的抽象图
我们这里使用数组模拟实现循环队列,但是我们知道数组在实际当中并不是如上图这样的,我们要达成上图这样的效果可以多多利用 % 计算。
比如 任意一个数 % 5,得到的结果只能在 0 ~ 4 之间,所以我们可以利用这一性质,来完成我们的数组模拟实现循环队列。
3.代码实现以及分析
我们根据题目给的接口来一个个分析并实现
3.1 创建结构体
typedef struct {
int* a;//a是数组
int front;//队列头
int rear;//队列尾
int k;//k是数组有效空间,为什么说是有效空间?下面会解释
} MyCircularQueue;
3.2创建一个具体的循环队列
MyCircularQueue* myCircularQueueCreate(int k) {//k是队列的有效空间
//创建一个具体的结构
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//给我们的数组开辟 k + 1个空间,k + 1就是实际的空间,这个空间不存数值
obj -> a = (int*)malloc(sizeof(int) * (k + 1));//多开一个空间,实现”假溢出“
obj -> front = 0;//队首和队尾的下标都初始化为0
obj -> rear = 0;
obj -> k = k;//传入接口函数的大小k
return obj;
}
3.3判断是否为空 和 判断是否为满
我们可以让front == rear时,循环队列为空。那么怎么判断是否为满呢?
我们可以多开一个空间这个空间不存放任何值,当rear + 1 == front时,则循环队列为满,这种现象叫做“假溢出”
这就是我们为什么要多开一个空间,目的就是利用假溢出来判断循环队列是否为满。
判空:
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj -> front == obj -> rear;;
}
判满:(需要注意一些情况)
正常情况:这种情况只需判断rear + 1 == front 是否成立就可以了。
特殊情况:下面这种情况当rear + 1时,就会造成数组的越界。这时候就需要用到我们上面提到的 % 操作。
(rear + 1) % (k + 1) == front,可以让等号左边的值在0 ~ k 中循环,避免越界的情况。
如下图 front的下标为0,rear的下标为5,k为5 (5 + 1) % (5 + 1) == 0,可以看到可以成功判满。
利用 % 操作,不管时那种情况都能准确判断是否为满。
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj -> rear + 1) % (obj -> k + 1) == obj -> front;
}
4. 进队列 和 出队列
进队列前,注意判断队列是否为满。
和普通队列一样,进队列让队尾++就可以了,注意当rear在末尾时越界情况。
当rear++后,可能会越界,此时rear应该在0下标处
同样可以进行 % 操作 rear %= k + 1
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj)) return false; //如果队列已满则入队列失败,返回false
obj -> a[obj -> rear++] = value; //进队列以后,rear++
obj -> rear %= obj -> k + 1;//防止越界情况
return true;//进队列成功返回true
}
出队列前,也应该判断队列是否为空,为空则返回false,表示出队列失败
和普通队列一样,出队列让队首++就可以了,注意越界情况
此时front应该在0下标处
%操作 front %= k + 1 即可
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj)) return -1;
return obj -> a[obj -> front];
}
5.取队首和队尾元素
取队首元素很简单,直接取下标对应位置的值就可以了
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj)) return -1;//判空
return obj -> a[obj -> front];
}
取队尾元素比较麻烦一点,因为rear位置是不存放值的所以队尾元素实际是 rear 的前一个位置
下图的队尾是rear 的前一个位置的元素,也就是44,一般情况下rear-1就可以得到队尾元素。
这里是特殊情况,同样利用%操作
(rear - 1 + k + 1) % (k + 1),也就是
(rear + k) % (k + 1),就可得到适应各种情况的获取队尾下标方法。
int myCircularQueueRear(MyCircularQueue* obj) { //尾不是rear,而是rear的前一位
if(myCircularQueueIsEmpty(obj)) return -1;
return obj -> a[(obj -> rear + obj -> k ) % (obj -> k + 1)];
}
6.释放空间
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj -> a); //先释放我们创建的数组
free(obj);//再释放我们创建的结构
}
7.总结
设计循环队列,要设计好判空和判满情况。
其中,判满要利用“假溢出”这一特性。
利用数组设计循环队列的时候,利用%操作来达成我们的“循环”