C/C++ 数据结构(八)队列

本篇核心知识 :顺序循环队列、链式队列、队列基本操作、STL 容器适配器(queue/deque/priority_queue)、队列与栈 / 顺序表 / 链表对比、容器底层选型、课后实操要求


一、队列基础概述

概念

队列是受限线性表 ,遵循 先进先出(FIFO, First In First Out) 规则。数据只能从队尾 (rear) 插入(入队),从队头 (front) 删除(出队),两端操作严格区分,不允许在中间位置增删元素。

特性
  1. 操作规则:队尾入队、队头出队,先进入的数据优先被取出;

  2. 分类:按存储结构分为顺序队列(数组实现)链式队列(链表实现)

  3. 核心标识:队头指针front、队尾指针rear,用于定位首尾位置。

拓展

队列典型应用:消息排队、任务调度、广度优先搜索 (BFS)、网络请求缓冲。


二、顺序循环队列

概念

基于连续数组实现的顺序队列,为解决普通顺序队列 "假溢出" 问题,将数组首尾逻辑相连形成环形结构,称为循环队列。

1. 普通顺序队列缺陷(假溢出)

数组物理空间未用尽,但元素出队后,队头指针后移,前面空闲空间无法复用,看似队列已满,实际存在空闲区域,该现象为假溢出。循环队列通过指针循环折返,彻底解决此问题。

2. 循环队列核心设计约定

为区分队空队满 两种状态,主动预留一个空闲单元格

假设数组总容量为 MAXSIZE,实际最大存储元素个数 = MAXSIZE - 1

判空条件:front == rear(队头指针与队尾指针指向同一位置);

判满条件:(rear + 1) % MAXSIZE == front(队尾下一个位置是队头);

指针移动:入队 / 出队时,指针通过取模运算实现循环折返。

3. 成员定义与代码实现

复制代码
#include <iostream>
using namespace std;
​
// 定义循环队列最大容量(含一个预留空位)
#define MAXSIZE 7
​
// 顺序循环队列
class CirQueue{
private:
    int data[MAXSIZE];  // 连续数组存储元素
    int front;          // 队头指针:指向队首有效元素
    int rear;           // 队尾指针:指向队尾元素的下一个空位
public:
    // 构造函数:初始化队列为空
    CirQueue() : front(0), rear(0) {}
​
    // 判断队列是否为空
    bool isEmpty() const{
        return front == rear;
    }
​
    // 判断队列是否已满
    bool isFull() const{
        return (rear + 1) % MAXSIZE == front;
    }
​
    // 入队:队尾插入元素
    bool enQueue(int val){
        if (isFull())
            return false;  // 队列已满,入队失败
        data[rear] = val;
        rear = (rear + 1) % MAXSIZE; // 队尾指针循环后移
        return true;
    }
​
    // 出队:队头删除元素,用引用接收出队值
    bool deQueue(int& val){
        if (isEmpty())
            return false; // 队列为空,出队失败
        val = data[front];
        front = (front + 1) % MAXSIZE; // 队头指针循环后移
        return true;
    }
​
    // 获取队头元素(不删除)
    int getFront() const{
        if (!isEmpty())
            return data[front];
        return -1;
    }
};

特性补充

  1. 内存特点:数组空间静态分配,初始化后容量固定,无法动态扩容;

  2. 优势:随机访问、操作速度快;

  3. 劣势:容量固定,队列满后无法新增元素;

  4. 取模运算 % 是实现循环的核心,保证指针在 0 ~ MAXSIZE-1 范围内循环。


三、链式队列

概念

基于单链表实现的队列,节点动态申请内存,无固定容量限制,不会出现假溢出、队列满的问题。

特性
  1. 结构:由链表节点、队头指针front、队尾指针rear组成;

  2. 入队:在链表尾部新增节点,更新尾指针;

  3. 出队:删除链表头部节点,更新头指针;

  4. 优势:动态扩容,按需分配内存,适合数据量波动大的场景;

  5. 劣势:无随机访问,每个节点附带指针开销。

代码实现

复制代码
#include <iostream>
using namespace std;
​
// 链式队列节点
struct QNode
{
    int data;
    QNode* next;
    QNode(int val) : data(val), next(nullptr) {}
};
​
// 链式队列类
class LinkQueue
{
private:
    QNode* front;  // 队头指针
    QNode* rear;   // 队尾指针
public:
    // 1. 普通构造函数:空队列
    LinkQueue() : front(nullptr), rear(nullptr) {}
​
    // 2. 拷贝构造函数(深拷贝)
    LinkQueue(const LinkQueue& other)
    {
        front = nullptr;
        rear = nullptr;
        copyFrom(other);
    }
​
    // 3. 拷贝赋值运算符(深拷贝,防止自赋值)
    LinkQueue& operator=(const LinkQueue& other)
    {
        // 防止自赋值:自己 = 自己
        if (this == &other)
            return *this;
​
        // 先清空当前队列原有节点
        clear();
        // 再深拷贝目标队列
        copyFrom(other);
​
        return *this;
    }
​
    // 4. 析构函数
    ~LinkQueue()
    {
        clear();
    }
    
    // 清空队列(复用析构逻辑)
    void clear()
    {
        while (front != nullptr)
        {
            QNode* temp = front;
            front = front->next;
            delete temp;
        }
        rear = nullptr;
    }
​
    // 深拷贝另一个队列的所有节点
    void copyFrom(const LinkQueue& other)
    {
        // 原队列为空,直接返回
        if (other.front == nullptr)
        {
            front = rear = nullptr;
            return;
        }
​
        // 先拷贝队头节点
        front = new QNode(other.front->data);
        QNode* cur = front;
        QNode* src = other.front->next;
​
        // 依次拷贝后续所有节点
        while (src != nullptr)
        {
            cur->next = new QNode(src->data);
            cur = cur->next;
            src = src->next;
        }
        // 尾指针指向最后一个节点
        rear = cur;
    }
​
    // 判断队列是否为空
    bool isEmpty() const{
        return front == nullptr;
    }
​
    // 入队:尾部添加节点
    void enQueue(int val){
        QNode* newNode = new QNode(val);
        if (isEmpty()){
            // 空队列:头尾指针都指向新节点
            front = newNode;
            rear = newNode;
        }
        else{
            rear->next = newNode;
            rear = newNode; // 更新尾指针
        }
    }
​
    // 出队:删除队头节点
    bool deQueue(int& val){
        if (isEmpty())
            return false;
        QNode* temp = front;
        val = temp->data;
        front = front->next;
        // 若删完最后一个节点,尾指针置空
        if (front == nullptr)
            rear = nullptr;
        delete temp;
        return true;
    }
​
    // 获取队头元素
    int getFront() const{
        if (!isEmpty())
            return front->data;
        return -1;
    }
};

关键易错点

  1. 出队时如果删除的是最后一个节点,必须将rear置空,避免野指针;

  2. 链式队列必须自定义析构函数,逐个释放堆节点。


四、队列分类对比

对比项 顺序循环队列 链式队列
底层结构 静态数组,内存连续 动态链表,内存离散
容量 固定,无法动态扩容 无上限,按需申请内存
溢出问题 存在队满,预留空位判状态 无队满、无假溢出
访问效率 高(数组直接访问) 较低(只能遍历)
内存开销 无指针额外开销 每个节点附带指针开销
适用场景 数据量固定、追求高速访问 数据量动态变化、不确定大小

五、STL 容器适配器(队列相关)

概念

容器适配器:不独立实现存储结构,基于已有基础容器封装出新容器,仅对外暴露专属接口,是 C++ STL 的核心设计思想。队列、栈、优先队列均为典型容器适配器。

5.1 std::queue 普通队列

概念

STL 标准队列,默认底层容器为deque,严格遵循先进先出。

常用成员函数

push(val):队尾入队;

pop():队头出队(无返回值);

front():获取队头元素;

back():获取队尾元素;

empty():判断是否为空;

size():获取元素个数。

代码示例
复制代码
#include <iostream>
#include <queue>
using namespace std;
​
int main(){
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);
​
    while (!q.empty()){
        cout << q.front() << " ";
        q.pop();
    }
    return 0;
}

5.2 std::deque 双向队列

概念

双端队列,支持头部、尾部两端高效插入 / 删除 ,是queue/stack默认底层容器。

特性
  1. 区别于vectorvector头部增删效率极低,deque两端操作效率一致;

  2. 底层:分段连续内存,并非单一整块数组;

  3. 常用接口:push_back/push_frontpop_back/pop_front

5.3 std::priority_queue 优先队列

概念

外观是队列,不遵循先进先出,内部自动按照优先级排序,每次出队的是优先级最高的元素(默认大顶堆)。

特性
  1. 底层默认基于 ** 堆(heap)** 实现;

  2. 应用场景:任务优先级调度、TOP-K 问题、排序优化;

  3. 接口与普通queue基本一致。

代码示例
复制代码
#include <iostream>
#include <queue>
using namespace std;
​
int main(){
    // 默认大顶堆:数值越大优先级越高
    priority_queue<int> pq;
    pq.push(3);
    pq.push(1);
    pq.push(5);
​
    while (!pq.empty()){
        cout << pq.top() << " ";
        pq.pop();
    }
    // 输出:5 3 1
    return 0;
}

拓展:容器适配器自定义底层

STL 队列可手动指定底层容器,支持deque/list

复制代码
// 指定以list作为queue底层容器
queue<int, list<int>> q;

六、队列与栈、顺序表、链表综合对比

  1. 栈 VS 队列

    栈:先进后出,仅在同一端增删;

    队列:先进先出,两端区分队头 / 队尾。

  2. vector VS deque

    vector:连续数组,尾部操作快,头部增删需要挪动元素,效率差;

    deque:分段内存,头尾增删效率都很高。

  3. 原生手写队列 VS STL 队列

    手写:理解底层原理,面试手写代码必考;

    STL:工程开发直接使用,无需管理内存,开发效率高。