片头
嗨! 小伙伴们,大家好! 今天我们来一起看看这道题目---设计循环队列,准备好了吗? Ready Go ! ! !
emmm,题目有点长,我们一起来分析分析~
题目要求我们设计一个循环队列,如果我们采用链表的形式会很麻烦,因为必须设计成双向循环链表,还要考虑队头,队尾的问题,因此,我们采用另外一种方法: 用数组来实现循环队列。
思路一: 先定义一个动态数组,然后定义2个变量,分别为front和rear,front指向第一个元素,rear指向最后一个元素的下一个位置,最后定义变量k,表示队列的长度。
初始时,front和rear都指向数组的第一个位置(下标为0处)
(ps: 题目条件说: 队列的长度为k,说明有效元素为k个,我们多开辟一个空间,队列的空间总数是 k+1 ,待会儿后面慢慢讲, 这里看不懂莫着急)
①队列为空的条件
观察上图,此时循环队列为空,那么为空的条件是什么呢? 那就是 front == rear ! 不管front和rear在哪里相同,只要它们两个相等,都可以判断循环队列此时为空。
②队列插入新元素
假设队列的长度为5, 也就是k = 5,队列里面有5个有效的空间, 我们往循环队列里面插入元素 1, 2,3, 4, 5
第一次:数字“1”插入到下标为0的位置,rear继续向后走一步
第二次: 数字“2”插入到下标为0的位置,rear继续向后走一步
第三次: 数字“3”插入到下标为0的位置,rear继续向后走一步
第四次: 数字“4”插入到下标为0的位置,rear继续向后走一步
第四次: 数字“5”插入到下标为0的位置,rear继续向后走一步
呀! 不得了,rear和front又相遇了,这个时候,循环队列是满的,可是,判断循环队列为空的条件也是 front == rear 呀, 这怎么区分啊?
方法有2种 :①设置一个标志变量flag,当front == rear,且flag = 0时,队列为空, 当front == rear, flag = 1时为队列满 ②在原数组的基础上,多开辟一个空间,总空间从原来的k变成了k+1个空间,也就是说,队列满时,数组中还有一个空闲单元。例如下图所示,k = 5, 有5个有效元素, 分别为1, 2 ,3 ,4, 5, 有效空间数为5, 我们再多开辟一个空间,总空间数变成了6
再举一个例子:
front不再指向下标为0的位置,而是指向中间的位置,现在我们插入元素"1",rear指向下一个位置
接着我们插入下一个元素"2",rear指向下一个位置
接着我们插入下一个元素"3",rear指向下一个位置
咦? 不是说rear指向下一个位置吗? 怎么让它回到下标为0的位置呢? 很简单,就让 (rear+1)%(k+1)就可以啦!还是上面这幅图, 假设k = 4, rear的下标为4,rear+1 = 5, 5%(k+1) == 0, 因此rear回到了下标为0的位置。
③队列为满的条件
那怎么判断队列是否为满呢? 由于rear可能比front大,也可能比front小,所以尽管它们只相差一个位置时就是满的情况,也有可能是相差整整一圈,比如下面这幅图,也是队列为满的情况。
所以,若队列的最大尺寸为QueueSize,那么队列满的条件是(rear+1)%QueueSize == front(取模"%"的目的就是为了整合rear与front大小为一个问题)。比如图1,QueueSize = 6,front = 0, rear = 5,(5+1)%6 = 0, 所以此时队列满; 比如图2, QueueSize = 5, front = 2, rear = 1, (1+1)%5 = 2, 此时队列也为满。(这里的QueueSize = 5, 是因为整个数组里面总共有5个空间,但是只有4个空间才能装数据,也就是题目给的队列长度为k , 我们假设k = 4, 那么有效的空间个数为4, 另外1个空间是不能放数据的,它只能判断队列是否为满,但是不管空间能不能存放数据,每一个空间都要算上,因此队列的空间总数为5, QueueSize = 5)
④获取队头元素
为满的条件判断完毕,现在我们来获取队头元素,先判断一下队列是否为空,然后直接获取队头元素就行了。
⑤删除队头元素
如何删除队头元素呢? 就是让front变量往后走一步
以图2为例,k=4,队列的元素个数有4个,分别为 1, 2, 3 ,4, 有效空间数为4, 我想删除队头元素"1",让front变量向后走一步
然后我想删除队头元素"2",让front变量向后走一步
接着我想删除队头元素"3",front变量向后走一步
咦,奇怪,不是说直接让front变量向后走一步就行了吗? 哈哈哈, 循环队列的实质是让下标回绕,若front指向下标为k的位置,那么front+1是指向下标为 k+1 但是如果指向 k+1, 就出现了越界, 所以我们必须让front指向下标为0的位置,怎么让front回来呢? 很简单,直接将front+1的结果对QueueSize取模就行了((front+1)%(k+1)), 此时front+1指向 k+1 的位置,(k+1)%(k+1) = 0,front指向下标为0的位置(font = (front+1)%(k+1))
⑥获取队尾元素
OK,我们了解了如何获取队头元素,那接下来怎么获取队尾元素呢? 你可能会说: 太简单了, rear指向最后元素的下一个位置,那么我直接让 rear = rear-1 不就行了吗?
哈哈哈哈,想法总是美好的,请看下面这幅图~
如果此时rear = rear-1, 那么rear-1指向下标为-1的位置,并没有指向下标为k的位置。那怎么办?我们这时就不能再取模了,而是将rear-1的结果加上总空间个数,公式为: rear-1 + (k+1), rear-1的结果为-1, 总空间数k+1为5, -1+5 = 4, 获取下标为4的元素,恰好是队尾元素。因此,也可以这样记: rear + k,(正负1相互抵消)
但是又有新的问题产生了,那如果rear没有指向下标为0, rear-1不等于-1,但是仍然多走了 k+1, 我们怎么把它还原呢?
我们就将整体结果对总空间数目(k+1)取模就可以了,也就是把多走的(k+1)抹掉,任何一个比(k+1)小的值,加上(k+1),再模(k+1),结果不会有任何影响。公式为: (rear-1 + k+1)%(k+1); 比如,图3中,k = 4, 有4个有效的空间存储数据, rear的值为0, rear-1的结果为 -1, -1 + k + 1 == k , k %(k+1) == k, 最终rear找到了下标为4的位置; 图4中,k = 4, 有4个有效的空间存储数据, rear的值为4, rear-1的结果为3, 3+(k+1) == 8, 8 % (k+1) == 3,最终rear找到了下标为3的位置。
⑦释放循环队列
OK啦,基本操作都实现完毕,接下来咱们实现释放循环队列~ , 先把数组释放从内存中释放掉,再把在堆上malloc申请的结构体空间释放掉就可以啦!
好滴,这道题被我们解决啦,完整代码如下:
typedef struct {
int* arr; //动态数组
int front; //front指向第一个元素
int rear; //rear指向最后一个元素的下一个位置
int k; //元素个数
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj =(MyCircularQueue*) malloc(sizeof(MyCircularQueue));
//多开一个空间解决假溢出问题
obj->arr = malloc(sizeof(int)*(k+1));
obj->front = 0;
obj->rear = 0;
obj->k = k; //队列长度为k
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
//判断队列是否为空的条件: 看看front是不是和rear相等
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
//判断队列是否为满的条件: 看看是否 (rear+1)%(k+1) == front
return (obj->rear+1)%(obj->k+1) == obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//如果队列为满,则返回false
if(myCircularQueueIsFull(obj)){
return false;
}
//将新数据直接插入到rear指向的下标对应的位置
//rear指向下一个位置,要注意下标回绕
obj->arr[obj->rear] = value;
obj->rear = (obj->rear+1)%(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//如果队列为空,则返回false
if(myCircularQueueIsEmpty(obj)){
return false;
}
//让front指向下一个位置,注意下标回绕
obj->front = (obj->front+1)%(obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
//如果队列为空,返回-1
if(myCircularQueueIsEmpty(obj)){
return -1;
}
//直接将front指向的下标的位置的元素返回
int ret = obj->arr[obj->front];
return ret;
}
int myCircularQueueRear(MyCircularQueue* obj) {
//如果队列为空,返回-1
if(myCircularQueueIsEmpty(obj)){
return -1;
}
//返回rear当前位置的前一个位置
return obj->arr[(obj->rear-1 + obj->k+1)% (obj->k+1)];
}
void myCircularQueueFree(MyCircularQueue* obj) {
//将数组释放掉
//将结构体释放掉
free(obj->arr);
free(obj);
}
片尾
今天我们学习了一道OJ题---设计循环队列,里面用到了数组,也考察了队列的相关知识,希望看完这篇文章能对友友们有所帮助 ! ! !
求点赞收藏加关注 ! ! !
谢谢大家 ! ! !