CC++ & Algorithm

Python队列(deque)

困难0
语言版本:C++
概述:队列就像排队买票,先进先出,用collections.deque可以高效地两头操作。

排队不拥挤:Python 队列(deque)完全指南

你有没有在奶茶店、食堂窗口或者游乐园排过队?先来的人先得到服务,后来的人排到后面——这就是“先进先出”(First In, First Out,简称 FIFO)的原则。在计算机程序里,很多任务也需要这样排队处理,比如打印机依次打印文档、游戏中的消息处理、操作系统管理进程等。Python 中处理队列的“神器”是 collections.deque,它不光能当队列用,还能两头操作,又快又方便。

为什么不能用列表当队列?

你可能想:用列表 list 实现队列很简单啊:用 append 在末尾添加(入队),用 pop(0) 从开头取出(出队)。比如:

# 用列表模拟队列(性能很差!)
queue = []                     # 空列表作为队列
queue.append('小明')           # 小明入队
queue.append('小红')           # 小红入队
first = queue.pop(0)           # 小明出队(从开头弹出)

这样写可以正常工作,但效率非常低。因为 pop(0) 删除第一个元素后,后面的所有元素都要向前移动一个位置。如果队列里有 10000 个元素,每出队一次就要移动 9999 个元素,程序会越来越慢。想象一下,如果排队买票的人每走一个人,后面所有人都要往前挪一步,那队伍得多慢!计算机里这种操作的叫“时间复杂度”,用列表模拟队列的出队操作是 O(n)(n 是元素个数),而 deque 的出队是 O(1)(即常数时间,和元素多少无关)。

所以,别用列表当队列,除非你的队列非常小(比如不到 100 个元素)。

认识 deque:两头都能进能出的高手

deque 是“double-ended queue”(双端队列)的缩写,它支持在两端高效地添加和删除元素。你既可以把它当普通队列(左端出、右端进),也可以当栈(一端进、一端出),甚至可以两头进两头出。

使用前需要先导入:

from collections import deque

队列的核心操作(以普通队列为例)

普通队列的规则是:从右端入队,从左端出队(当然你也可以反过来,只要保持一致即可)。常用操作:

操作代码说明
创建一个空队列q = deque()也可以传入可迭代对象,比如 deque([1,2,3])
入队(右端添加)q.append(item)相当于排队时新人站到最后
出队(左端删除)q.popleft()返回并删除队首元素
查看队首q[0]只看看不删除
查看队尾q[-1]最后一个人是谁?
判断是否为空len(q) == 0空队列长度为0
获取长度len(q)当前队伍里有多少人
清空队列q.clear()瞬间所有人离开

如果想把队列当双端队列用,还可以用 appendleft 在左端添加,pop 从右端删除。

生活中的例子:模拟银行叫号排队

银行营业厅里,顾客取号排队,柜员按顺序叫号。用 deque 实现非常自然:

from collections import deque

# 创建空队列
bank_queue = deque()                     # 银行排队队列

# 顾客取号入队
bank_queue.append('A001')                # 第一位顾客,号码 A001
bank_queue.append('A002')                # 第二位顾客
bank_queue.append('A003')                # 第三位顾客
print("当前排队:", list(bank_queue))     # ['A001', 'A002', 'A003']

# 柜员叫号(出队)
served = bank_queue.popleft()            # 服务 A001
print(f"请 {served} 到 1 号窗口")       # 请 A001 到 1 号窗口

# 又有新顾客取号
bank_queue.append('A004')                # 新顾客 A004 排在最后
print("当前排队:", list(bank_queue))     # ['A002', 'A003', 'A004']

# 继续叫号
served = bank_queue.popleft()            # 服务 A002
print(f"请 {served} 到 1 号窗口")       # 请 A002 到 1 号窗口

# 查看现在队首是谁(但不叫号)
print("下一位:", bank_queue[0])          # A003

写代码时容易犯的错误

错误1:忘记导入

# 错误:没有导入 deque
q = deque()  # NameError: name 'deque' is not defined
# 正确:
from collections import deque
q = deque()

错误2:用列表的 pop() 代替 popleft()

q = deque(['a', 'b', 'c'])
first = q.pop()    # 错误!pop() 是从右端删除,得到 'c',不是 'a'
# 正确应该是 q.popleft()

错误3:空队列时调用 popleft() 或访问索引

q = deque()
q.popleft()   # IndexError: pop from an empty deque
q[0]          # 同样会报错

解决方法:操作前先检查是否为空 if q:if len(q) > 0

错误4:用索引访问不存在的元素

q = deque(['a'])
print(q[5])   # IndexError: deque index out of range

错误5:误用 deque 当作普通列表用切片

q = deque([1,2,3])
print(q[1:2])  # TypeError: sequence index must be integer, not 'slice'
# deque 不支持切片操作,如果要切片可以先转成列表:list(q)[1:2]

完整示例:模拟食堂打饭窗口

假设食堂有 3 个打饭窗口,但只有一个窗口开放,同学们排队打饭。用队列模拟,并统计每个同学的等待时间。

from collections import deque

# 所有同学按到达顺序排队
students = ['李华', '小明', '小红', '小刚', '小花', '大壮']
queue = deque()                           # 空队列

# 初始化:所有同学入队
for name in students:                     # 遍历学生列表
    queue.append(name)                    # 依次入队

print("初始排队顺序:", list(queue))

# 模拟打饭过程:每次叫一个人,直到队伍为空
time = 0                                  # 当前时间(分钟)
served_time = {}                          # 字典:记录每个人被服务的时间

while queue:                              # 当队列非空时
    student = queue.popleft()             # 队首同学出队(打饭)
    time += 2                             # 打饭需要 2 分钟
    served_time[student] = time           # 记录该同学被服务的时间
    print(f"{student} 在第 {time} 分钟打到饭")

print("\n每位同学的等待时间:")
for student, t in served_time.items():    # 遍历字典
    print(f"{student}: {t} 分钟")

运行结果:

初始排队顺序: ['李华', '小明', '小红', '小刚', '小花', '大壮']
李华 在第 2 分钟打到饭
小明 在第 4 分钟打到饭
小红 在第 6 分钟打到饭
小刚 在第 8 分钟打到饭
小花 在第 10 分钟打到饭
大壮 在第 12 分钟打到饭

每位同学的等待时间:
李华: 2 分钟
小明: 4 分钟
小红: 6 分钟
小刚: 8 分钟
小花: 10 分钟
大壮: 12 分钟

双端队列的更多玩法

deque 不只能当普通队列,还能像栈一样从一端操作。比如用 appendleftpopleft 实现“左端队列”,或者用 appendpop 实现栈(后进先出)。另外,deque 还支持 rotate 循环移动元素:

from collections import deque

q = deque([1, 2, 3, 4, 5])
q.rotate(2)               # 向右旋转2步,等价于每个元素向后移2位
print(list(q))            # [4, 5, 1, 2, 3]

q.rotate(-1)              # 向左旋转1步
print(list(q))            # [5, 1, 2, 3, 4]

这个功能在游戏循环、消息队列轮转中有用。

相关知识点指引

  • 栈(Stack):后进先出(LIFO),可以用 deque 实现,也可以用列表 list 实现(因为列表的 appendpop 都是 O(1))。
  • 优先队列(Priority Queue):不按时间先后,按优先级排序,Python 用 heapq 模块实现二叉堆。
  • 广度优先搜索(BFS):在图或树的遍历中,经常用一个队列来存储“待访问的节点”,deque 是标准实现。
  • 双端队列的其他应用:比如滑动窗口最大值、回文检测等。

掌握了队列,你就学会了一种高效管理“先后顺序”的方法。下次再写需要排队的程序,记得用 deque 哦!

例题精讲

1单选题

在Python中,要使用deque高效地操作队列两端,正确的导入语句是?

Afrom collections import deque
Bfrom deque import collections
Cimport deque
Dfrom collections import Deque
2单选题

关于deque的pop()和popleft()方法,下列描述正确的是?

Apop()移除并返回最左端元素
Bpopleft()移除并返回最右端元素
Cpop()默认移除并返回最右端元素
D当队列为空时调用popleft()程序不会出错
3判断题

使用deque的rotate(n)方法可以将队列中的元素向右循环移动n步(n为正整数)。

4填空题
现有空deque对象d,需要依次添加元素使得最终得到deque([1,2,3,4])(左侧为1,右侧为4)。请补全代码:\nfrom collections import deque\nd = deque()\nd.append(2)\nd.___(1)\nd.append(3)\nd.append(4)
5填空题
使用deque模拟先进先出队列,循环处理任务直到队列为空。补全代码:\nfrom collections import deque\ntasks = deque(['task1','task2','task3'])\nwhile tasks:\n    task = tasks.___()\n    print(f'Processing {task}')