C++ 环形队列 从原理到实例演示

前言:大家用过队列吗?普通队列的性能瓶颈是什么呢? "假溢出" 和频繁内存移动的问题成为了普通队列绕不开的性能瓶颈。而环形队列凭借 "空间复用 + O (1) 读写" 的特性,成为解决这一问题的最优解 。无论是中断服务函数(ISR)中的数据缓存,还是网络 IO 的数据包处理,几乎都能看到它的身影。今天,我们从原理到实例,解读一下C++环形队列。

目录

一、什么是环形队列

二、如何实现环形

三、难点

四、实例演示


一、什么是环形队列

环形队列 ,又称循环队列,它是一种++线性数据结构++ 。它基于普通的队列(先进先出,FIFO),用**数组模拟环状结构,**在存储结构上表现就像一个"环形"。

为什么需要它?

来看看普通顺序队列的缺陷:

它存在两个指针,队头(front)与队尾指针(rear)。队头指针始终指向队头元素,队尾指针始终指向队尾元素的下一位置。

当rear等于队列总长时,说明队列已经 "满" 了。此时若再执行入队操作,便会出现队满 "溢出"。然而,由于在此之前可能已经执行了若干次出队操作,因而队列的前面部分可能还有很多闲置的空间,所以这种溢出并非是真的没有可用的存储空间,故这种溢出现象称之为 "假溢出"。

而环形队列通过将数组的首尾相连,解决了假溢出问题。当尾指针移动到数组末尾时,如果数组前端有空位,它可以"绕"回头,重新利用之前的内存空间,从而极大地提高了内存利用率

二、如何实现环形

环形队列通常基于一段连续的内存空间(如数组或 std::vector)实现。实现"环形"的关键在于指针(索引)的移动方式

我们不能通过简单地让指针 +1来实现,因为当指针指向数组末尾时,再加一就越界了。这里,我们需要利用取模运算(%)

指针移动公式:

bash 复制代码
next_index = (current_index + 1) % capacity

这样,当 current_index 达到 capacity - 1 时,下一位置会自动计算为 0,从而实现循环。

例如:

当rear在索引为4的位置的时候,若再次入队,则 rear = (4+1)%5 = 0,自动回到了索引0的位置,实现了内存重新利用。

三、难点

环形队列实现中最棘手的问题是:判空与判满

对于普通队列,当队头指针front == 队尾指针rear时,代表队列为空。然而,对于环形队列,front == rear的时候,不一定表示队空,还有可能表示队满。

常见的解决方案:

  1. 牺牲一个存储单元
  • 队列为空:front == rear
  • 队列为满:(rear + 1) % capacity == front
  • 这意味着如果数组大小为 N,最多只能存储 N-1 个元素。
  1. 增加一个计数器
  • 增加一个整数变量 count 记录当前元素个数。
  • 队列为空:count == 0
  • 队列为满:count == capacity
  • 这种方法逻辑清晰,且能充分利用数组空间。

推荐大家使用第二种方式。

四、实例演示

下面我们采用增加计数器的方法,通过模板实现通用环形队列。

cpp 复制代码
#include <iostream>
#include <vector>
#include <stdexcept>

template <typename T>
class CircularQueue {
private:
    std::vector<T> data;
    int front;     // 头指针,指向队首元素
    int rear;      // 尾指针,指向队尾元素的下一个位置
    int capacity;  // 容量
    int count;     // 当前元素数量

public:
    // 构造函数
    CircularQueue(int cap) : capacity(cap), front(0), rear(0), count(0) {
        if (cap <= 0) throw std::invalid_argument("Capacity must be positive");
        data.resize(capacity);
    }

    // 入队
    bool enqueue(const T& value) {
        if (isFull()) {
            std::cout << "Queue is Full! Cannot enqueue " << value << std::endl;
            return false;
        }
        data[rear] = value;
        rear = (rear + 1) % capacity; // 循环移动
        count++;
        return true;
    }

    // 出队
    bool dequeue(T& value) {
        if (isEmpty()) {
            std::cout << "Queue is Empty! Cannot dequeue." << std::endl;
            return false;
        }
        value = data[front];
        front = (front + 1) % capacity; // 循环移动
        count--;
        return true;
    }

    // 获取队首元素
    T& peek() {
        if (isEmpty()) throw std::runtime_error("Queue is empty");
        return data[front];
    }

    bool isEmpty() const {
        return count == 0;
    }

    bool isFull() const {
        return count == capacity;
    }

    int size() const {
        return count;
    }
};

int main() {
    // 1. 创建容量为 5 的环形队列
    CircularQueue<int> cq(5);

    // 2. 测试入队
    std::cout << "--- Enqueue Operations ---" << std::endl;
    for (int i = 1; i <= 6; ++i) {
        cq.enqueue(i * 10);
    }

    // 3. 测试出队
    std::cout << "\n--- Dequeue Operations ---" << std::endl;
    int val;
    for (int i = 0; i < 3; ++i) {
        if (cq.dequeue(val)) {
            std::cout << "Dequeued: " << val << std::endl;
        }
    }

    // 4. 测试循环利用空间
    std::cout << "\n--- Enqueue Again (Circular Reuse) ---" << std::endl;
    cq.enqueue(60);
    cq.enqueue(70);
    cq.enqueue(80); // 此时队列应再次变满

    // 5. 打印最终状态
    std::cout << "\n--- Final Queue Status ---" << std::endl;
    std::cout << "Current Size: " << cq.size() << std::endl;
    while (!cq.isEmpty()) {
        cq.dequeue(val);
        std::cout << "Remaining Element: " << val << std::endl;
    }

    return 0;
}

代码运行输出:

bash 复制代码
--- Enqueue Operations ---
Queue is Full! Cannot enqueue 60

--- Dequeue Operations ---
Dequeued: 10
Dequeued: 20
Dequeued: 30

--- Enqueue Again (Circular Reuse) ---
(空间被循环利用,成功入队 60, 70, 80)

--- Final Queue Status ---
Current Size: 5
Remaining Element: 40
Remaining Element: 50
Remaining Element: 60
Remaining Element: 70
Remaining Element: 80
相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495645 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
fqbqrr6 小时前
2607C++,soui与安卓
c++·soui
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a1117769 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao9 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
fqbqrr9 小时前
2607C++,使用微软detours勾挂工具
c++