循环队列详解

文章目录

  • 循环队列
  • 代码实现

循环队列

循环队列的元素个数固定,一端进另一端出,普通队列我们是选择用链表实现的,循环队列这里我们选择用数组实现。
为什么会选择数组实现而不是链表呢?
为了满足队列先进先出的特点,我们要保证可以很容易的找到第一个元素的位置和最后一个元素的位置,用单链表我们是可以用两个指针来定位第一个元素和最后一个元素的位置,可以很方便的进行插入,删除,但是我们还要对队列进行判空和判满操作,那么此时就会遇到一个问题,当队头指针和队尾指针相等时代表着空还是满呢?此时我们可以空出来一个位置不存放数据,当尾指针rear的next为头指针front的时候我们就认为它已经满了,rear是在push完之后变为指向它的下一个节点的位置,此时我们让rear指向的位置不再存储数据,用双链表实现同样面临着上述问题。总之到最后都要多出来一个位置,不会让它真的满。但是它的难点在于取尾不好取,因为此时rear已经指向了最后一个数据的下一个节点。那么考虑让rear指向最后一个数据呢?那么此时就不容易初始化了,空的时候指向第一个位置,增加一个元素还是让它指向第一位置,此时rear就不会移动了。
链表实现过程:
初始化fornt和rear都指向第一个节点,此时没有元素
循环队列详解_第1张图片
当push一个元素之后,rear此时指向的是最后一个数据的下一个节点。
循环队列详解_第2张图片
当rear的next为front时表示满了,rear此时指向的这个节点不存储数据
循环队列详解_第3张图片
这种情况也是满的
循环队列详解_第4张图片
那么用数组实现呢?
数组要想实现循环就需要借助下标来实现,通过取模操作使得最后一个元素的下一个下标为第一个元素的下标。
我们可以选择开辟比规定循环队列存放的数据个数多一个空间,用来区分队列满和空的两种状态。
循环队列详解_第5张图片
利用数据在数组中的下标来判断队列是否为满,当rear的下一个为front的时候表示满,size表示实际能存储的数据个数,这里的size = 5。
重点:如何判断rear的下一个就是front?
next = (rear + 1) % (size +1);
这里就是(5 + 1) % (5 + 1)= 0;
同理当删除操作是front的下一个就是(front - 1 + size + 1)% (size + 1);
被除数为什么要加上size + 1呢?
例如上面的front - 1之后就是 - 1,我们要保证下标值在0~size之间。对于%被除数加上与除数相等的数结果不会发生变化,如:5 % 6 = 5和 (5 + 6) % 6 = 5结果是相等的。
找队列的最后一个值就是(rear - 1 + size + 1)% (size + 1);
对于减的操作我们都要加上除数的值避免被除数变成负数。所以这里我们也加上了除数的值。
如:
循环队列详解_第6张图片

当不断删除,front不断向后走当front和rear的值相等时,循环队列就为空了。
多开辟一个空间就是更好地区分为了使队满和队空两种情况

代码实现

力扣题目
链接:https://leetcode.cn/problems/design-circular-queue/

#define _CRT_SECURE_NO_WARNINGS 1

#include 
#include 
#include 

typedef struct {
    int* a;
    // int* front;//指向第一个数据
    // int* rear;//指向最后一个数据
    int front;//第一个数据的下标
    int rear;//最后一个数据下标
    int size;//循环队列的大小
} MyCircularQueue;

//创建队列并初始化
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* CQ;
    CQ = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    assert(CQ);
    //这里我们选择多开辟一个空间,以利于判断循环队列是否满了
    //不多开辟空间,rear的下一个和front相等不也是能判断是否满了吗?为什么不能这样做?
    //因为rear的值不是最后一个元素下标,而是最后一个元素的下一个位置的下标
    //那让rear值时最后一个不就行了吗?
    //那rear的初始值就是-1,那你此时有怎么去判断队列为空吗?
    //当进进出出元素之后front和rear相等时,队列可能为空也可能满了。
    CQ->a = (int*)malloc(sizeof(int) * (k + 1));
    assert(CQ->a);
    CQ->front = 0;
    CQ->rear = 0;
    //多开辟了一个空间这里为什么还是k?k的值代表什么?
    CQ->size = k;
    return CQ;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    //判断循环队列是否满了 当当前队尾的下一个就是队头时则表示满了
    //求出当前队尾的下一个位置
    int next = (obj->rear + 1) % (obj->size + 1);
    // if(next == obj->front)
    // {
    //     return 
    // }
    return next == obj->front;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    // if(obj->rear == obj->front)
    // {
    //     return true;
    // }
    // return false;
    return obj->rear == obj->front;
}

//元素进队列
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    //判断是否满了
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    // obj->a[obj->rear] = value;
    // obj->rear++;
    obj->a[obj->rear++] = value;
    obj->rear = obj->rear % (obj->size + 1);
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    //front可能为0
    //obj->front = (--obj->front + obj->size) % (obj->size + 1);
    obj->front = (++obj->front) % (obj->size + 1);
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //return obj->front;
    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //获取队尾元素的位置
    //return obj->rear;
    //return obj->a[obj->rear];
   //为什么队尾元素也要计算直接像上面那样返回不就好了吗?//因为rear指向的是队尾元素的下一个坐标
    return obj->a[(obj->rear - 1 + obj->size + 1) % (obj->size + 1)];
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    obj->a = NULL;
    obj->front = 0;
    obj->rear = 0;
    obj->size = 0;
}

你可能感兴趣的:(数据结构,链表,数据结构)