【LeetCode】环形队列实现

目录

  • 前言
  • [1. 环形队列概念](#1. 环形队列概念)
  • [2. 循环队列实现设计](#2. 循环队列实现设计)
  • [3. 功能实现](#3. 功能实现)
    • [3.1 定义](#3.1 定义)
    • [3.2 初始化](#3.2 初始化)
    • [3.3 判断队列是否为空](#3.3 判断队列是否为空)
    • [3.4 判断队列是否为满](#3.4 判断队列是否为满)
    • [3.5 入栈](#3.5 入栈)
    • [3.6 出栈](#3.6 出栈)
    • [3.7 获取队头数据](#3.7 获取队头数据)
    • [3.8 获取队尾数据](#3.8 获取队尾数据)
    • [3.9 销毁](#3.9 销毁)
  • [4. 总结](#4. 总结)
  • [5. 完整通过代码](#5. 完整通过代码)

前言

之前我们学习环形链表相关问题,现在我们来看看环形队列有什么不同呢


循环队列题目链接

1. 环形队列概念

循环队列是一种线性数据结构,其操作依然是先进先出原则 并且队尾被连接在队首之后以形成一个循环 。它也被称为"环形缓冲器"。

最大的特点就是可以用利用这个队列之前用过的空间。

2. 循环队列实现设计

循环链表可以使用数组或链表实现

我这里用数组实现

如果需要存放是k个数据,我们就需要开辟k+1个数据空间,不然就无法判定空和满,导致数据插入和删除异常

需要两个变量来标识头(head)和尾(tail),出队front就向前移动,入队rear向前移动,但是这样会造成一个问题数组越界,循环就很好解决了这个问题,越界了就回到第一个数据位置。

  • 判空:head == tail
  • 盘满:head+1 == tail

3. 功能实现

3.1 定义

我们使用数组实现,需要一个数组,还有2个标志头(head)和尾(tail)的变量,和一个标志存放多少数据的变量k

c 复制代码
typedef struct {
    int* a;
    int head;
    int tail;
    int k;
} MyCircularQueue;

3.2 初始化

首先我们需要创建队列结构体,在给里面成员赋值,之前说了我们需要k+1数据的空间,防止不知道满了和空的情况,head和tail刚开始都是0,k还是实参传过来的,我们需要的是k+1数据的空间

c 复制代码
MyCircularQueue* myCircularQueueCreate(int k) {
    // 创建队列结构体
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    
    // 为队列里的数组创建k+1空间
    cq->a = (int*)malloc((k+1) * sizeof(int));
    cq->head = cq->tail = 0;
    cq->k = k;
    
    return cq;
}

3.3 判断队列是否为空

head和tail相等,就是空,不是就有数据

c 复制代码
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    // head和tail相等就是空
    return obj->head == obj->tail;
}

3.4 判断队列是否为满

因为多开了一个数据空间,所以可以判断队列是否为满,tail+1 == head,就满了,多开一个数据的空间,可以保证满了的时候,tail和head无论什么情况,都至少有一个空间的距离,但是tail可能会越界,我需要取余

a只要取余b, a % b,余数一定是 0------b - 1,所以tail取余就不会越界,正好也满足了越界回到第一个数据位置。

而且还需要注意优先级问题,tail和k都是先加1,在进行取余
(obj->tail+1) % (obj->k+1) == obj->head

c 复制代码
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    // tail+1取余k+1,等于head,就是满
    return (obj->tail+1) % (obj->k+1) == obj->head; 
}

3.5 入栈

  1. 满了的就不能入数据了
  2. 插入数据tail就会增加,可能会越界,同样需要取余
c 复制代码
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    // 队列满了,就不能插入数据
    if (myCircularQueueIsFull(obj))
        return false;

    obj->a[obj->tail] = value;
    obj->tail++;
    
    // 插入数据会导致tail下标越界,越界处理:越界了就回到开始位置
    obj->tail %= obj->k+1;
    return true;
}

3.6 出栈

  1. 队列为空不能出数据
  2. 出栈,head就会向前移动,同样也会有越界问题,也需要取余
c 复制代码
ool myCircularQueueDeQueue(MyCircularQueue* obj) {
    // 队列为空,不能删除数据
    if (myCircularQueueIsEmpty(obj))
        return false;

    obj->head++;
    // 删除数据会导致head下标越界,越界处理:越界了就回到开始位置
    obj->head %= obj->k+1;
    return true;
}

3.7 获取队头数据

队列为空不能取数据,返回数据的head位置的数据即可

c 复制代码
int myCircularQueueFront(MyCircularQueue* obj) {
     // 队列为空,不能取数据
    if (myCircularQueueIsEmpty(obj))
        return -1;

    // 返回头下标的数据
    return obj->a[obj->head];
}

3.8 获取队尾数据

队列为空不能取数据,由于是先插入数据,后++,我们要取tail数据,需要-1,这个有两个情况,需要特殊处理

  • tail不为0,直接取数组tail - 1位置即可
  • tail为0,我们减1,tail为-1就会越界,返回最后一个数据的下标就是k的位置,这里有两种解决方法1、if判断 2、取余
  1. if判断
c 复制代码
int myCircularQueueRear(MyCircularQueue* obj) {
    // 队列为空,不能取数据
    if (myCircularQueueIsEmpty(obj))
        return -1;

    // tail等于0会越界,取消返回最后一个数据的下标
    if (obj->tail == 0)
        return obj->a[obj->k];

    return obj->a[obj->tail - 1];
}
  1. 取余
    由于空间长短是k+1,下标是k,他们之间最少也会差1,所以当前位置tail+k,在取余k+1,刚好就返回了tail的位置的前一个数据位置
    tail+k = k+1
    tail - 1
c 复制代码
int myCircularQueueRear(MyCircularQueue* obj) {
    // 队列为空,不能取数据
    if (myCircularQueueIsEmpty(obj))
        return -1;

    // 由于空间长短是k+1,下标是k,他们之间最少也会差1
    // 所以当前位置tail+k,在取余k+1,刚好就返回了tail前一个数据位置
    // tail+k = k+1
    // tail - 1
    int tail = (obj->tail+obj->k) % (obj->k+1);
    return obj->a[tail];
}

3.9 销毁

同样也有两层结构,我们需要前销毁里层,在销毁外层

c 复制代码
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

4. 总结

循环队列用数组实现就需要多注意数组越界问题

5. 完整通过代码

c 复制代码
typedef struct {
    int* a;
    int head;	// 头
    int tail;	// 尾
    int k;		// 队列长度
} MyCircularQueue;

// 由于要先调用这两个函数,所以先声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

MyCircularQueue* myCircularQueueCreate(int k) {
    // 创建队列结构体
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    
    // 为队列里的数组创建k+1空间
    cq->a = (int*)malloc((k+1) * sizeof(int));
    cq->head = cq->tail = 0;
    cq->k = k;
    
    return cq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    // 队列满了,就不能插入数据
    if (myCircularQueueIsFull(obj))
        return false;

    obj->a[obj->tail] = value;
    obj->tail++;
    
    // 插入数据会导致tail下标越界,越界处理:越界了就回到开始位置
    obj->tail %= obj->k+1;
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    // 队列为空,不能删除数据
    if (myCircularQueueIsEmpty(obj))
        return false;

    obj->head++;
    // 删除数据会导致head下标越界,越界处理:越界了就回到开始位置
    obj->head %= obj->k+1;
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
     // 队列为空,不能取数据
    if (myCircularQueueIsEmpty(obj))
        return -1;

    // 返回头下标的数据
    return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    // 队列为空,不能取数据
    if (myCircularQueueIsEmpty(obj))
        return -1;

    // 由于空间长短是k+1,下标是k,他们之间最少也会差1
    // 所以当前位置tail+k,在取余k+1,真好就返回了前一个数据位置
    // tail+k = k+1
    // tail - 1

    // 取余法
    //int tail = (obj->tail+obj->k) % (obj->k+1);
    //return obj->a[tail];

     if (obj->tail == 0)
        return obj->a[obj->k];

    return obj->a[obj->tail - 1];
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    // head和tail相等就是空
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    // tail+1取余k+1,等于head,就是满
    return (obj->tail+1) % (obj->k+1) == obj->head; 
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    obj->head = obj->tail = obj->k = 0;
    free(obj);
}
相关推荐
东方芷兰22 分钟前
算法笔记 04 —— 算法初步(下)
c++·笔记·算法
JNU freshman24 分钟前
图论 之 迪斯科特拉算法求解最短路径
算法·图论
xinghuitunan40 分钟前
时间转换(acwing)c/c++/java/python
java·c语言·c++·python
青松@FasterAI1 小时前
【NLP算法面经】本科双非,头条+腾讯 NLP 详细面经(★附面题整理★)
人工智能·算法·自然语言处理
旅僧1 小时前
代码随想录-- 第一天图论 --- 岛屿的数量
算法·深度优先·图论
Emplace1 小时前
ABC381E题解
c++·算法
tekin1 小时前
Python 高级数据结构操作全解析:从理论到实践
数据结构·python·集合set·高级数据结构·集合操作·队列操作·堆操作
若兰幽竹2 小时前
【机器学习】衡量线性回归算法最好的指标:R Squared
算法·机器学习·线性回归
咸鱼过江2 小时前
观察者模式说明(C语言版本)
c语言·观察者模式
居然有人6542 小时前
23贪心算法
数据结构·算法·贪心算法