每日一题---OJ题: 设计循环队列

片头

嗨! 小伙伴们,大家好! 今天我们来一起看看这道题目---设计循环队列,准备好了吗? 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题---设计循环队列,里面用到了数组,也考察了队列的相关知识,希望看完这篇文章能对友友们有所帮助 !   !   !

点赞收藏加关注 !   !   !

谢谢大家 !   !   !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值