Python队列(deque)
困难0排队不拥挤: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 不只能当普通队列,还能像栈一样从一端操作。比如用 appendleft 和 popleft 实现“左端队列”,或者用 append 和 pop 实现栈(后进先出)。另外,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实现(因为列表的append和pop都是 O(1))。 - 优先队列(Priority Queue):不按时间先后,按优先级排序,Python 用
heapq模块实现二叉堆。 - 广度优先搜索(BFS):在图或树的遍历中,经常用一个队列来存储“待访问的节点”,
deque是标准实现。 - 双端队列的其他应用:比如滑动窗口最大值、回文检测等。
掌握了队列,你就学会了一种高效管理“先后顺序”的方法。下次再写需要排队的程序,记得用 deque 哦!
例题精讲
在Python中,要使用deque高效地操作队列两端,正确的导入语句是?
关于deque的pop()和popleft()方法,下列描述正确的是?
使用deque的rotate(n)方法可以将队列中的元素向右循环移动n步(n为正整数)。
现有空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)使用deque模拟先进先出队列,循环处理任务直到队列为空。补全代码:\nfrom collections import deque\ntasks = deque(['task1','task2','task3'])\nwhile tasks:\n task = tasks.___()\n print(f'Processing {task}')