数据结构——队列

队列是和栈成对出现的受限线性数据结构 ,也是计算机系统中高频使用的基础结构。如果说栈负责"函数调用、逻辑回溯",那队列就负责排队、缓冲、异步、任务调度

日常开发中的消息队列、线程池任务排队、网络IO缓冲、日志异步写入、服务器请求限流排队,底层全部依赖队列思想。本文从零吃透队列的底层原理、两种实现、循环队列优化、核心优缺点以及实战面试考点。

一、队列的核心定义与特性

1.1 什么是队列?

队列是一种**先进先出(FIFO,First In First Out)**的受限线性表。

通俗理解就是排队:先来的人先走,后来的人后走,绝对不允许插队、不允许从中间删除元素。

和栈刚好完全相反:

  • 栈:后进先出(LIFO)

  • 队列:先进先出(FIFO)

1.2 队列固定规则

  • 队尾(rear):只能从队尾插入元素(入队)

  • 队头(front):只能从队头删除元素(出队)

  • 不支持中间插入、中间删除、随机访问

1.3 核心操作(全部 O(1))

  • enqueue 入队:队尾添加元素

  • dequeue 出队:队头删除元素

  • front 取队头:查看队首元素,不删除

  • empty 判断空队列:判断队列是否无数据

二、队列的两种底层实现

队列和栈一样,属于逻辑结构 ,物理底层依托两种基础结构:顺序队列(数组)链式队列(链表)

2.1 顺序队列(普通数组实现)

利用连续数组存储数据,用两个标记变量控制头尾:

  • front:指向队头位置

  • rear:指向队尾下一个位置

存在致命问题:假溢出(伪溢出)

普通顺序队列最大的缺陷:数组前面出队空出来的空间永远无法复用

当 rear 走到数组末尾,就算前面有大量空位,也会判定队列满,无法继续入队,这就是假溢出

为了解决这个问题,工程中几乎不用普通顺序队列 ,全部使用优化后的循环队列

2.2 循环队列(顺序队列终极优化)

循环队列是面试最高频考点,核心思想:让数组头尾相连,利用取模运算复用前面的空闲空间

不再让 rear 一直往后走,而是通过 % capacity 实现环形循环。

循环队列核心公式
  • 队尾更新:rear = (rear + 1) % cap

  • 队头更新:front = (front + 1) % cap

如何判断空、满?(面试必背)

循环队列最大难点:front 和 rear 重合时,分不清是空还是满。工程标准解决方案:预留一个空闲位置

  • 队空front == rear

  • 队满(rear + 1) % cap == front

循环队列优点
  • 彻底解决假溢出,100% 利用数组空间

  • 无元素移动、效率极高、缓存友好

  • 操作系统、缓冲区、环形缓冲区普遍使用

2.3 链式队列(链表实现)

基于单链表实现,为了保证 O(1) 效率,维护两个指针:

  • front 指针:指向链表头(出队位置)

  • rear 指针:指向链表尾(入队位置)

操作逻辑
  • 入队:尾部新增节点,rear 后移,O(1)

  • 出队:头部删除节点,front 后移,O(1)

链式队列优缺点

✅ 优点:无固定容量、无溢出问题、动态扩容、适合数据量波动大的场景

❌ 缺点:缓存不友好、存在指针开销、频繁 new/delete 性能损耗

三、顺序队列 vs 链式队列(工程选型)

特性 循环顺序队列 链式队列
内存结构 连续内存,缓存友好 离散内存,缓存较差
容量限制 固定容量 无上限,动态扩容
性能 极高,无内存碎片 略低,频繁分配释放
适用场景 缓冲区、固定大小任务队列 大量动态任务、不确定数据量

四、特殊队列(进阶高频)

4.1 双向队列 deque

普通队列只能一头进、一头出;双向队列两头都能进、两头都能出

C++ STL deque 就是双向队列,支持:头尾插入、头尾删除,是栈和队列的结合体。

4.2 优先队列 priority_queue

不遵循先进先出,而是按照优先级出队

底层默认基于大根堆实现,常用于:任务优先级调度、TOPK 问题、事件排序。

五、队列在计算机底层的真实应用

队列是系统工程中使用最多的数据结构,远超栈和链表。

5.1 操作系统任务调度

操作系统就绪队列、阻塞队列、时间片轮转调度,全部依靠队列管理进程/线程。

5.2 网络 IO 模型

epoll 就绪队列、内核 socket 读写缓冲区、TCP 滑动窗口,底层都是环形队列。

5.3 异步缓冲队列

日志异步队列、消息队列、线程池任务队列,用来削峰、解耦、异步处理

5.4 限流与排队机制

服务器请求过多时,放入队列排队,防止瞬间打崩服务。

5.5 广度优先搜索 BFS

二叉树层序遍历、图 BFS 遍历,算法底层完全依赖队列。

六、线性表四大结构终极对比(面试必背)

结构 规则 访问方式 核心用途
顺序表 自由读写 随机访问 O(1) 存储、查询多
链表 自由读写 顺序访问 O(n) 频繁增删
后进先出 仅栈顶访问 函数调用、回溯、括号匹配
队列 先进先出 仅头尾访问 排队、缓冲、任务调度、BFS

普通顺序队列 代码实现(数组版)

底层基于静态数组实现,可直观体现假溢出问题,帮助理解队列底层缺陷。

cpp 复制代码
#include <iostream>
using namespace std;

// 最大队列容量
const int MAX_SIZE = 5;

// 普通顺序队列
class ArrayQueue {
private:
    int data[MAX_SIZE];
    int front;  // 队头:指向队首元素
    int rear;   // 队尾:指向队尾元素的下一个位置

public:
    // 构造函数:初始化空队列
    ArrayQueue() {
        front = 0;
        rear = 0;
    }

    // 判断队列是否为空
    bool empty() {
        return front == rear;
    }

    // 判断队列是否满
    bool full() {
        return rear == MAX_SIZE;
    }

    // 入队操作
    bool enqueue(int val) {
        if (full()) {
            cout << "队列已满,入队失败" << endl;
            return false;
        }
        data[rear++] = val;
        return true;
    }

    // 出队操作
    bool dequeue() {
        if (empty()) {
            cout << "队列为空,出队失败" << endl;
            return false;
        }
        front++;
        return true;
    }

    // 获取队头元素
    int getFront() {
        if (empty()) return -1;
        return data[front];
    }

    // 遍历打印队列
    void print() {
        if (empty()) {
            cout << "队列为空" << endl;
            return;
        }
        cout << "队列元素:";
        for (int i = front; i < rear; i++) {
            cout << data[i] << " ";
        }
        cout << endl;
    }
};

// 测试代码
int main() {
    ArrayQueue q;
    q.enqueue(1);
    q.enqueue(2);
    q.enqueue(3);
    q.print();

    q.dequeue();
    q.print();

    // 此处会触发假溢出:数组前方有空位,但rear到达末尾无法入队
    q.enqueue(4);
    q.enqueue(5);
    q.enqueue(6); // 队列已满,入队失败
    return 0;
}

核心缺陷:出队后数组前方产生空闲空间,但无法复用,出现假溢出,仅适合入门演示,工程不使用。

循环队列 代码实现(面试重点)

解决普通顺序队列假溢出问题,通过取模运算实现环形复用空间,采用业界标准:预留一个空位区分空/满状态,面试手撕最优版本。

cpp 复制代码
#include <iostream>
using namespace std;

const int MAX_SIZE = 5;

class CircleQueue {
private:
    int data[MAX_SIZE];
    int front;  // 队头指针
    int rear;   // 队尾指针

public:
    CircleQueue() {
        front = 0;
        rear = 0;
    }

    // 判断空队列
    bool empty() {
        return front == rear;
    }

    // 判断队列满(预留一个空位)
    bool full() {
        return (rear + 1) % MAX_SIZE == front;
    }

    // 入队
    bool enqueue(int val) {
        if (full()) {
            cout << "循环队列已满" << endl;
            return false;
        }
        data[rear] = val;
        rear = (rear + 1) % MAX_SIZE;
        return true;
    }

    // 出队
    bool dequeue() {
        if (empty()) {
            cout << "循环队列为空" << endl;
            return false;
        }
        front = (front + 1) % MAX_SIZE;
        return true;
    }

    // 获取队头元素
    int getFront() {
        if (empty()) return -1;
        return data[front];
    }

    // 遍历打印
    void print() {
        if (empty()) {
            cout << "循环队列为空" << endl;
            return;
        }
        cout << "循环队列元素:";
        int i = front;
        while (i != rear) {
            cout << data[i] << " ";
            i = (i + 1) % MAX_SIZE;
        }
        cout << endl;
    }
};

// 测试代码
int main() {
    CircleQueue q;
    q.enqueue(10);
    q.enqueue(20);
    q.enqueue(30);
    q.print();

    q.dequeue();
    q.print();

    // 环形复用空闲空间,解决假溢出
    q.enqueue(40);
    q.enqueue(50);
    q.print();
    return 0;
}

核心考点复盘

  • 队空:front == rear

  • 队满:(rear + 1) % MAX_SIZE == front

  • 头尾指针更新:通过取模实现环形循环

链式队列 代码实现(链表版)

基于单链表实现,无固定容量、无溢出问题,动态扩容,适合数据量不确定的场景,所有操作时间复杂度 O(1)。

cpp 复制代码
#include <iostream>
using namespace std;

// 链表节点
struct QueueNode {
    int val;
    QueueNode* next;
    QueueNode(int v) : val(v), next(nullptr) {}
};

// 链式队列
class LinkQueue {
private:
    QueueNode* front; // 队头指针
    QueueNode* rear;  // 队尾指针

public:
    // 初始化空队列
    LinkQueue() {
        front = nullptr;
        rear = nullptr;
    }

    // 判断空队列
    bool empty() {
        return front == nullptr;
    }

    // 入队(尾部插入)
    void enqueue(int val) {
        QueueNode* newNode = new QueueNode(val);
        // 空队列初始化头尾指针
        if (empty()) {
            front = newNode;
            rear = newNode;
        } else {
            rear->next = newNode;
            rear = newNode;
        }
    }

    // 出队(头部删除)
    bool dequeue() {
        if (empty()) {
            cout << "链式队列为空,出队失败" << endl;
            return false;
        }
        // 暂存队头节点
        QueueNode* temp = front;
        front = front->next;
        delete temp; // 释放内存,避免泄漏

        // 如果删完后队列为空,重置尾指针
        if (front == nullptr) {
            rear = nullptr;
        }
        return true;
    }

    // 获取队头元素
    int getFront() {
        if (empty()) return -1;
        return front->val;
    }

    // 遍历打印队列
    void print() {
        if (empty()) {
            cout << "链式队列为空" << endl;
            return;
        }
        cout << "链式队列元素:";
        QueueNode* cur = front;
        while (cur != nullptr) {
            cout << cur->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }

    // 析构函数:释放所有节点内存
    ~LinkQueue() {
        while (!empty()) {
            dequeue();
        }
    }
};

// 测试代码
int main() {
    LinkQueue q;
    q.enqueue(100);
    q.enqueue(200);
    q.enqueue(300);
    q.print();

    q.dequeue();
    q.print();

    q.enqueue(400);
    q.print();
    return 0;
}

三种队列代码核心总结

  1. 普通顺序队列:代码最简单,存在假溢出,仅用于入门理解,不实战使用。

  2. 循环队列:面试必手撕,解决空间浪费,缓存友好,操作系统缓冲区、网络IO底层常用。

  3. 链式队列:动态扩容无上限,无需处理取模运算,适合任务量波动大的场景,需手动释放内存。

常见面试手撕问题

  1. 为什么循环队列要预留一个空位? 答:为了区分队空和队满状态,避免 front==rear 状态歧义。

  2. 链式队列为什么维护头尾指针? 答:尾指针避免每次入队遍历链表尾部,保证入队操作 O(1)。

  3. 顺序队列和链式队列怎么选型? 答:固定数据量、追求高性能选循环顺序队列;数据量未知、动态增减选链式队列。

七、全文总结

  1. 队列是**先进先出(FIFO)**的受限线性表,队尾入队、队头出队,所有操作均为 O(1)。

  2. 普通顺序队列存在假溢出 问题,工程中统一使用循环队列解决空间浪费问题。

  3. 循环队列判空:front == rear;判满:(rear+1)%cap == front,通过预留空位解决歧义。

  4. 链式队列基于链表实现,无容量限制,适合动态数据场景;顺序循环队列缓存友好、性能更高。

  5. 队列核心应用:操作系统调度、网络缓冲区、异步队列、BFS 算法、消息中间件、线程池任务排队。

相关推荐
专注API从业者7 小时前
用 Open Claw + 淘宝商品接口,快速实现电商商品监控与智能选品(附完整代码)
大数据·前端·数据结构·数据库
Shadow(⊙o⊙)7 小时前
前缀和:和可被K整除的子数组(normal)
数据结构·c++·算法
世纪末的小黑7 小时前
【LeetCode自用】LeetCode自用记录贴,题目一:两数之和
数据结构·算法·leetcode
努力努力再努力wz7 小时前
【Redis入门系列】:Redis 内部编码机制与 String 深度解析:SDS 底层实现、三种编码与核心命令详解
c语言·开发语言·数据结构·数据库·c++·redis·缓存
Brilliantwxx7 小时前
【C++】 认识STL set与map(基础接口+题目OJ运用)
开发语言·数据结构·c++·笔记·算法
海清河晏1118 小时前
数据结构 | 循环队列
数据结构·c++·visual studio
暴力求解8 小时前
数据结构---二叉树及堆的实现
数据结构·算法·二叉树
超梦dasgg8 小时前
并查集(Union-Find)详解 + Java 完整实现
java·数据结构·算法·图搜索
AbandonForce8 小时前
从入门到入土:二分查找算法
数据结构·算法