CC++ & Algorithm

queue队列适配器——先进先出的“排队”魔法

困难23
语言版本:通用
概述:队列是一种先进先出(FIFO,First In First Out)的数据结构,就像在食堂排队打饭——先到的人先打到饭。C++ STL中的queue容器适配器基于deque实现,提供了push(入队)、pop(出队)、front(队首)、back(队尾)等操作。Python中可以用collections.deque高效模拟队列。本文将带你轻松掌握队列的用法。

队列(queue)——先进先出的“排队”魔法

你有没有在学校食堂排过队?排在队伍最后面的人,要等前面所有人都打完饭才能轮到自己。这种“先来先服务”的规则,就是队列(queue)的核心思想:先进先出(FIFO,First In First Out)

队列在编程中就像一根“管道”:你从一端(队尾)放入数据,数据从另一端(队首)流出。你不能跳过中间的人,也不能插队。这种结构特别适合处理需要按顺序完成的任务,比如打印任务、聊天消息排序、游戏中的操作队列,甚至用来实现“广度优先搜索(BFS)”算法——你可以把BFS想象成沿着迷宫一层一层地找出口,每走完一层就把下一层的所有路口记下来,用队列来安排先到先走。

C++ 的 STL 提供了现成的 queue 容器适配器,几行代码就能实现一个队列。Python 中推荐用 collections.deque 来高效模拟队列。下面我们就一步步揭开队列的魔法。


队列的“公平规则”:先进先出(FIFO)

队列只允许在一端(队尾)添加元素,在另一端(队首)移除元素。想象一下你正在玩一个“传球游戏”:你从后面拿到一个球,只能从前面把球传给下一个人——球不能从中间漏掉,也不能从后面接回来。

生活里随处可见的队列

  • 食堂打饭:最先到的同学最先打到饭。
  • 打印机任务:你先提交的文档,打印机先打印出来。
  • 银行叫号机:先取号的人先被叫到窗口。
  • 手机聊天群:你发的消息会按发送时间先后显示在群里。
  • 游戏中的操作队列:你连续按了好几个技能键,游戏会按你按的顺序依次释放技能,不能乱序。

队列的接口非常简洁,只有队首队尾可以访问,不能像数组一样随便读取中间的元素。这就像队伍里你只能看到最前面和最后面的人,看不到中间的人在干嘛。


STL 中 queue 的用法(C++)

包含头文件与定义

使用 queue 前需要包含头文件 <queue>,然后定义一个队列对象,指定元素的类型。

#include <queue>        // 使用队列必须包含此头文件
using namespace std;

queue<int> q;            // 定义一个int类型的空队列,名字叫q
queue<string> name_queue; // 定义一个string类型的空队列,存放名字

常用成员函数

函数作用时间复杂度
push(x)将元素 x 放入队尾(入队)O(1)
pop()移除队首元素(出队),不返回被删除的元素O(1)
front()返回队首元素的引用(不删除)O(1)
back()返回队尾元素的引用(不删除)O(1)
empty()判断队列是否为空,空返回 true,否则 falseO(1)
size()返回队列中元素的个数O(1)

特别提醒pop() 只移除队首元素,不会把那个元素的值返回给你。如果你想先拿到队首再把它删掉,需要先调用 front() 把值存起来,再调用 pop()

int first = q.front();  // 拿到队首元素
q.pop();                // 再把它从队列里移除

完整示例代码(C++)

下面是一个完整的 C++ 程序,模拟了“早操排队”的过程:同学们按名字顺序入队,然后老师按顺序喊人出列做操。

#include <iostream>
#include <queue>        // 使用queue的头文件
#include <string>       // 使用string

using namespace std;

int main() {
    // 定义一个string类型的队列,用来存放同学的名字
    queue<string> student_queue;

    // 检查队列是否为空
    if (student_queue.empty()) {
        cout << "队伍里还没有人。" << endl;
    }

    // 同学们陆续入队(push)
    student_queue.push("小明");   // 小明排在队尾
    student_queue.push("小红");
    student_queue.push("小刚");
    student_queue.push("小丽");
    student_queue.push("大壮");   // 大壮是最后来的

    cout << "现在队伍里有 " << student_queue.size() << " 位同学。" << endl;
    cout << "队首(最先来的)是:" << student_queue.front() << endl;  // 小明
    cout << "队尾(最后到的)是:" << student_queue.back() << endl;   // 大壮

    // 开始做操:老师每次喊队首的同学出来,然后该同学离开队伍
    cout << "做操顺序:";
    while (!student_queue.empty()) {
        string current = student_queue.front(); // 拿到当前队首的同学名字
        cout << current << " ";                  // 喊名字
        student_queue.pop();                     // 该同学离开队伍
    }
    cout << endl;

    cout << "所有同学都做完操了,队伍空了?" 
         << (student_queue.empty() ? "是的" : "不是") << endl;

    return 0;
}

运行结果:

队伍里还没有人。
现在队伍里有 5 位同学。
队首(最先来的)是:小明
队尾(最后到的)是:大壮
做操顺序:小明 小红 小刚 小丽 大壮 
所有同学都做完操了,队伍空了?是的

底层实现说明

queue 默认基于 deque(双端队列)实现,也可以在定义时指定底层容器为 list。但不能用 vector,因为 vector 在头部删除元素时需要移动所有剩余元素,效率非常低(O(n))。而 deque 和 list 在两端添加删除都是 O(1) 的。

// 指定用list作为底层容器
queue<int, list<int>> q_with_list;

Python 中模拟队列(使用 deque)

Python 最推荐的队列实现是 collections.deque(双端队列),它支持左右两端的高效添加和删除。注意:不要用普通的 list 来模拟队列,因为 list.pop(0) 会移动所有剩余元素,时间复杂度 O(n),数据量大时会非常慢。

基本操作对应表

C++ queue 函数Python deque 方法说明
push(x)q.append(x)从右边(队尾)添加元素
pop()q.popleft()从左边(队首)移除元素并返回
front()q[0]访问队首元素(不删除)
back()q[-1]访问队尾元素(不删除)
empty()not q 或 len(q)==0判断是否为空
size()len(q)获取元素个数

完整示例代码(Python)

和 C++ 例子一样,模拟早操排队:

from collections import deque  # 从collections模块导入deque

def main():
    # 创建一个空队列,存放同学名字
    student_queue = deque()

    # 检查队列是否为空
    if not student_queue:
        print("队伍里还没有人。")

    # 同学们陆续入队(从右边添加)
    student_queue.append("小明")
    student_queue.append("小红")
    student_queue.append("小刚")
    student_queue.append("小丽")
    student_queue.append("大壮")

    print("现在队伍里有", len(student_queue), "位同学。")
    print("队首(最先来的)是:", student_queue[0])   # 小明
    print("队尾(最后到的)是:", student_queue[-1])  # 大壮

    # 做操:依次弹出队首(从左端弹出)
    print("做操顺序:", end="")
    while student_queue:                     # 只要队列非空
        current = student_queue.popleft()    # 弹出队首同学的名字
        print(current, end=" ")              # 打印名字
    print()

    print("所有同学都做完操了,队伍空了?", "是的" if not student_queue else "不是")

if __name__ == "__main__":
    main()

运行结果与 C++ 完全相同。

为什么不用 list?

如果你尝试用 list 模拟队列:

q = []
q.append(10)   # 入队:没问题
x = q.pop(0)   # 出队:会移动剩下的所有元素,很慢!

当队列里有成千上万个元素时,每次 pop(0) 都要移动后面所有元素,程序会变得非常慢。所以绝对不要用 list 当队列


新手常犯的错误

1. 空队列上调用 front() 或 back()

队列为空时,front()back() 的行为是未定义的(程序可能会崩溃)。所以使用前一定要判断是否为空

queue<int> q;
// 错误:q 是空的,直接访问 front() 会导致未定义行为
// int x = q.front(); 

// 正确做法:
if (!q.empty()) {
    cout << q.front() << endl;
}

2. 忘记 pop() 只删除不返回

很多新手以为 pop() 会返回被删除的元素,于是写出这样的代码:

int x = q.pop();  // 错误!pop() 返回类型是 void

正确做法:

int x = q.front();
q.pop();

3. 混淆队首和队尾

队列是“先进先出”,所以 front() 是最先添加的元素,back() 是最后添加的。不要和 stack(栈)搞混了,栈是“后进先出”,用 top() 访问栈顶。

4. 在遍历时修改队列

如果一边用 front()pop() 遍历,一边又往队列里 push 新元素,循环条件(!q.empty())会永远为真,导致无限循环。务必小心:如果需要一边处理一边添加新任务,要控制好循环次数或用计数器。

// 危险示例(可能无限循环)
while (!q.empty()) {
    int x = q.front();
    q.pop();
    if (满足某种条件) {
        q.push(x + 1);  // 又添加了新元素,队列永远不为空
    }
}

queue 的经典应用:广度优先搜索(BFS)

队列最出名的一个用途就是实现 广度优先搜索(BFS)。例如,你要在一个迷宫里找从起点到终点的最短路径,可以这样想:

  1. 从起点出发,把起点放入队列。
  2. 每次从队首取出一个格子,看看它四周(上、下、左、右)有没有可以走的格子,把这些新格子放入队尾。
  3. 重复第2步,直到找到终点或队列为空。

因为队列的“先进先出”特性,我们会先探索离起点最近的格子,再探索稍远的,一层一层往外扩,第一次到达终点的路径一定是最短的。这就是“广度优先”的含义。

如果你对 BFS 感兴趣,可以继续学习:图论基础树的层序遍历迷宫最短路径 等知识点。


相关知识点指引

  • 栈(stack):队列的“好兄弟”,后进先出(LIFO),常用在括号匹配、深度优先搜索(DFS)中。
  • 双端队列(deque):queue 的底层实现之一,两端都可以添加和删除,比 queue 更灵活。
  • 优先队列(priority_queue):不再是普通的“先来先服务”,而是按照优先级大小出队,常用于任务调度、堆排序。
  • 容器适配器:queue 和 stack 都是容器适配器,它们基于底层容器(如 deque、list)提供了特定的接口限制。理解适配器的设计模式有助于你写出更灵活的代码。

掌握队列,你就拥有了处理“按顺序排队”问题的利器。不管是写游戏、做算法题,还是处理现实中的任务调度,队列总能派上用场。现在你可以试试自己写一个小程序:模拟银行叫号服务,或者用队列实现一个简单的消息聊天系统。祝你在编程世界里排队愉快!?

例题精讲

1单选题

关于STL中的queue容器适配器,下列说法正确的是?

Aqueue是顺序容器,支持随机访问
Bqueue默认使用deque作为底层容器
Cqueue可以用vector作为底层容器,因为vector支持push_front
Dqueue的front()和back()操作的时间复杂度为O(n)
2单选题

以下代码中,若q是queue<int>类型,执行q.push(1); q.push(2); q.push(3); int x = q.front(); q.pop(); 则x的值是?

A1
B2
C3
D未定义行为
3单选题

关于queue的成员函数,哪个说法是错误的?

Aempty()返回bool值,检查队列是否为空
Bsize()返回队列中元素个数
Cback()返回队列中最后一个元素,但不删除
Dpop()返回并删除队头元素
4判断题

queue容器适配器允许使用list作为底层容器,但必须保证list支持push_back和pop_front操作。

5填空题
以下代码实现将队列q中所有元素复制到另一个队列copy中,保持顺序。请填空:
#include <queue>
#include <iostream>
int main() {
    std::queue<int> q;
    q.push(1); q.push(2); q.push(3);
    std::queue<int> copy;
    for (std::queue<int>::size_type i = 0; i < q.size(); ++i) {
        int val = q.front();
        copy.push(val);
        q.pop();
        q.push(___);
    }
    // 现在copy中为1,2,3,q恢复原顺序
    return 0;
}