【队列】循环队列(Circular Queue)详解

文章目录

一、循环队列简介

在实际开发中,队列是一种常用的数据结构,而循环队列(Circular Queue)则一般是一种基于数组实现的队列(也可使用循环链表)。与传统的 FIFO 队列相比,循环队列通过将数组首尾相连形成一个 "环",能够更高效地利用内存空间。

循环队列的主要思想是:当队尾指针到达数组末端时,如果数组前面还有空余空间,就可以从数组头部重新利用这些空间进行入队操作。也就是说,数组的末端和头部通过逻辑上的连接,形成一个环状结构,从而避免了顺序队列中由于出队操作而导致的空间浪费问题。

如下图就是一个典型的循环队列,其中的 front 表示头指针,指向队头。rear 则表示尾指针,指向队尾元素的下一个位置。

二、循环队列的判空和判满

在循环队列中,frontrear 都是可以循环移动的,当队空时,front == rear 成立;当队满时,front == rear 也成立。因此显然不能只凭 front == rear 来判断队空还是队满。

为了解决这个问题,在循环队列中约定:少用一个元素空间,当队尾标识的 rear在队头标识front 的上一个位置时,队列为满。此时,判断队空和队满的条件分别如下:

队空时:front == rear

队满时:(rear + 1) % MAXSIZE == front

其中,MAXSIZE 是队列容量的大小

两种情况下队列中指针的状态如下图所示:

既然少一个元素空间,这就意味着,如果要存储的数据个数最大为 k,那么你需要开辟的循环队列的大小应为 k+1

三、循环队列的实现

我们来以具体的一道题目来实现循环队列的各种操作

leetcode 622. 设计循环队列

c 复制代码
typedef struct {
    int* a;
    int front;  // 头"指针"指向队头数据
    int tail;  // 尾"指针"指向队尾的下一个位置
    int k;  // 一会儿开辟的队列大小为 k+1
} MyCircularQueue;

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);  // 前面先实现的函数要用到这两个接口,所以事先声明一下

// 循环队列的初始化
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    cq->a = (int*)malloc(sizeof(int)*(k+1));
    cq->front = 0;  // 初始化数据
    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);  
    // tail 自增了之后可能超出循环队列的大小范围所以要取模
    // 模的是循环队列的大小 k+1

    return true;
}

// 出队
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)) return false;  // 为空就不能再删
    obj->front = (obj->front+1)%(obj->k+1);  // 和前面是一样的原理,注意同样是加一再取模
    return true;
}

// 获取队头元素
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)) return -1;
    return obj->a[obj->front];
}

// 获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)) return -1;
    if(obj->tail == 0) return obj->a[obj->k];  // 跨越了一个循环的情况
    else return obj->a[obj->tail-1];
}

// 判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->tail;
}

// 判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->tail+1) % (obj->k+1) == obj->front;  
    // tail的后一个是front说明满了,但是有可能tail+1跨过了一个循环。所以要取模
}

// 释放
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);  // 注意这里要先释放结构体内的数组!!!不然会可能内存泄漏
    free(obj);  
}
相关推荐
是隼人35 分钟前
buuctf-pwn [HarekazeCTF2019]baby_rop2(64位ret2libc)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
青 春 记 忆1 小时前
LeetCode 350. 两个数组的交集 II|Python 解法详解
python·算法·leetcode
wuyk5552 小时前
107.FreeRTOS 链表深度解析:从原理到面试满分答案
c语言·开发语言·数据结构·stm32·单片机·链表·面试
-dzk-2 小时前
【链表】LC 138.随机链表的复制
数据结构·链表
洋不写bug3 小时前
链表补充练习,双链表的模拟实现
java·数据结构·链表·双链表·底层实现
白狐_7984 小时前
408 数据结构|外部排序:流程与 k 路归并
数据结构·算法
AAA代码批发商4 小时前
Days 39 Linux C 开发之 SQLite 数据库完整学习笔记
linux·c语言·数据库
圣保罗的大教堂5 小时前
leetcode 3903. 最小稳定下标 I 简单
leetcode
卢锡荣6 小时前
单芯掌控多口互联|乐得瑞 LDR6020 PD3.1 多通道 Type‑C 控制 SOC 芯片
c语言·开发语言
wuminyu6 小时前
JVM锁膨胀与Futex源码解析
java·linux·c语言·jvm·c++