设计循环队列

这篇我们说一下队列里的循环队列,然后做一下LeetCode里的题。
设计循环队列_第1张图片
环形队列可以使用数组实现,也可以使用循环链表实现。
设计循环队列_第2张图片
循环队列的特点:
1.符合先进先出
2.空间大小是固定的

然后,我们来看一下空的循环队列,和满的循环队列是什么样子:

设计循环队列_第3张图片
为什么会这样设计:
原因:为了避免空和满混洗,无法区分。在设计循环队列时,无论使用数组还是链表实现,都要多开一个空间,也就意味着,要是一个存k个数据的循环队列,要开k+1个空间。
也就是说:
front==rear 就为空
front=rear+1 就为满

然后,我们来看一下循环队列的使用流程:
假设我们要插入3个数据:
设计循环队列_第4张图片
此时,循环队列就为满的,不能在插入了。

当我们想要删除数据,我们只需要front++一下就行:
设计循环队列_第5张图片
此时,循环队列就是空,当我们在插入数据:
设计循环队列_第6张图片
此时,循环队列就为满,不能插入了。

这就是循环队列的一个基本流程,那么我们是用数组实现还是链表实现呢?
答案是:我们用数组来实现。

我们根据LeetCode里的题来实现:
OJ链接
设计循环队列_第7张图片
首先,我们先把循环队列的结构弄出来:

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不在边界
设计循环队列_第8张图片
这样,我们直接插到rear处,然后rear++。
第二种情况:rear在边界
设计循环队列_第9张图片
这种情况,插入到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不在边界
设计循环队列_第10张图片
这种情况,我们直接让front++

第二种情况:front在边界
设计循环队列_第11张图片
这种情况,就是让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];

}

第六,我们要取循环队列队尾元素:
取队尾元素,我们要分情况讨论:
第一种情况:
设计循环队列_第12张图片
因为rear是指向数据元素的下一个位置,所以最后一个元素就是rear-1

第二种情况:
设计循环队列_第13张图片
这里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;
}

第八,我们要判断循环队列是否为满:
第一种情况:
设计循环队列_第14张图片
此时,rear+1==front就为满,前面已经说过。

第二种情况:
设计循环队列_第15张图片
此时,front0和reark也为满

代码如下:

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);
}

到这里,循环队列的基本接口函数就完成了,这个以后我们会在生产者消费者模型里用到。如果大家觉得这篇文章有帮助,大家可以多加支持一下,谢谢大家。
设计循环队列_第16张图片

你可能感兴趣的:(题目练习,链表,leetcode,数据结构)