队列(Queue/Deque/PriorityQueue)

先进先出数据结构与优先队列 — Python实现与算法分析

数据结构分类: 线性数据结构 / 受限线性表

核心特性: 先进先出(FIFO, First-In-First-Out)

主要内容: 普通队列、循环队列、双端队列、优先队列、队列应用场景、复杂度分析

关键词: 队列, Queue, Deque, 优先队列, PriorityQueue, heapq, 循环队列, BFS

实现语言: Python 3

一、队列概述

队列(Queue)是一种先进先出(FIFO, First-In-First-Out)的线性数据结构。它的工作方式类似于现实生活中的排队——先到的人先获得服务,后到的人排在队尾等待。

队列的核心特性:

  • FIFO原则: 最先加入队列的元素最先被移除
  • 受限访问: 只能在队尾(rear)添加元素,在队首(front)移除元素
  • 基本操作: enqueue(入队)、dequeue(出队)、peek(查看队首)、is_empty(判空)、size(获取长度)
  • 时间复杂度: 理想情况下所有操作均为 O(1)

队列的基本操作定义

一个完整的队列接口包含以下核心方法:

  • enqueue(item) — 将元素添加到队尾
  • dequeue() — 移除并返回队首元素
  • peek() / front() — 返回队首元素但不移除
  • is_empty() — 检查队列是否为空
  • size() — 返回队列中元素的数量

"队列是一种受限制的线性表,限定只在表的一端进行插入操作、在另一端进行删除操作。允许插入的一端称为队尾(rear),允许删除的一端称为队首(front)。"

二、普通队列的实现

2.1 基于列表(数组)的实现

Python 中最简单的队列实现方式是使用内置的 list。使用 append() 实现入队操作(O(1)均摊),使用 pop(0) 实现出队操作(O(n),因为需要移动所有元素)。

class ListQueue: def __init__(self): self.items = [] def enqueue(self, item): """入队:在列表末尾添加元素,O(1)均摊""" self.items.append(item) def dequeue(self): """出队:移除并返回列表首部元素,O(n)""" if self.is_empty(): raise IndexError("队列为空") return self.items.pop(0) def peek(self): """查看队首元素""" if self.is_empty(): raise IndexError("队列为空") return self.items[0] def is_empty(self): return len(self.items) == 0 def size(self): return len(self.items) def __str__(self): return "Queue: front -> " + " -> ".join(str(x) for x in self.items) + " -> rear"

性能分析: 这种实现简单直观,但 dequeue() 操作的时间复杂度为 O(n),因为 pop(0) 需要将列表中的所有元素向前移动一位。对于大量数据,这会成为性能瓶颈。

2.2 基于链表(Linked List)的实现

使用双向链表可以实现 enqueue 和 dequeue 均为 O(1) 的队列。利用链表头结点作为队首、尾结点作为队尾。

class Node: def __init__(self, value): self.value = value self.next = None class LinkedListQueue: def __init__(self): self.front = None # 队首指针 self.rear = None # 队尾指针 self._size = 0 def enqueue(self, item): """入队:在链表尾部添加,O(1)""" node = Node(item) if self.rear is None: self.front = self.rear = node else: self.rear.next = node self.rear = node self._size += 1 def dequeue(self): """出队:移除链表头部节点,O(1)""" if self.is_empty(): raise IndexError("队列为空") value = self.front.value self.front = self.front.next if self.front is None: self.rear = None self._size -= 1 return value def peek(self): if self.is_empty(): raise IndexError("队列为空") return self.front.value def is_empty(self): return self._size == 0 def size(self): return self._size

性能分析: 链表实现下,入队和出队操作均为 O(1)。但每个节点需要额外存储指针(内存开销较大),且缓存局部性不如数组实现。

三、循环队列(Circular Queue)

循环队列是数组实现队列的一种优化方式。通过复用已出队元素的存储空间,避免了 pop(0) 带来的 O(n) 数据搬移开销。循环队列使用 head 指针(指向队首)和 tail 指针(指向队尾下一个位置)来管理队列。

循环队列核心原理

  • head 指针: 指向队首元素的位置
  • tail 指针: 指向下一个入队元素应该存放的位置
  • 判空条件: head == tail
  • 判满条件: (tail + 1) % capacity == head(牺牲一个存储单元以区分空和满)
  • 指针移动: 使用模运算 (pointer + 1) % capacity 实现循环
class CircularQueue: def __init__(self, capacity=10): self.items = [None] * (capacity + 1) # 多开一个位置区分空/满 self.capacity = capacity + 1 self.head = 0 # 队首指针 self.tail = 0 # 队尾指针(指向下一个插入位置) def enqueue(self, item): """入队:O(1)""" if self.is_full(): raise IndexError("队列已满") self.items[self.tail] = item self.tail = (self.tail + 1) % self.capacity def dequeue(self): """出队:O(1)""" if self.is_empty(): raise IndexError("队列为空") value = self.items[self.head] self.items[self.head] = None self.head = (self.head + 1) % self.capacity return value def peek(self): if self.is_empty(): raise IndexError("队列为空") return self.items[self.head] def is_empty(self): return self.head == self.tail def is_full(self): return (self.tail + 1) % self.capacity == self.head def size(self): return (self.tail - self.head + self.capacity) % self.capacity

循环队列设计要点:

  • 利用 (tail + 1) % capacity == head 判断队列是否已满,此时数组中有一个空位未使用
  • size() 的计算公式为 (tail - head + capacity) % capacity,确保模运算结果不为负
  • 所有操作均为 O(1),没有数据搬移开销
  • 适用于有界队列场景,如操作系统 I/O 缓冲区、网络数据包缓冲

使用示例:

# 循环队列使用演示 cq = CircularQueue(5) for i in range(5): cq.enqueue(i * 10) print(cq.dequeue()) # 输出: 0 print(cq.dequeue()) # 输出: 10 cq.enqueue(99) # 复用已释放的空间 print(cq.peek()) # 输出: 20 print(cq.size()) # 输出: 4

四、双端队列(Deque)

双端队列(Deque, Double-Ended Queue)是一种可以在两端进行插入和删除操作的线性数据结构。与普通队列不同,Deque 没有严格的 FIFO 限制,允许在队首和队尾两端进行 O(1) 的添加和移除操作。

4.1 Python 的 collections.deque

Python 标准库提供了 collections.deque,它是用双向链表(准确地说,是双向的块状链表)实现的,支持两端的 O(1) 操作。

from collections import deque # 创建双端队列 d = deque(['a', 'b', 'c']) # 两端添加元素 d.append('d') # 右端添加: deque(['a','b','c','d']) d.appendleft('x') # 左端添加: deque(['x','a','b','c','d']) # 两端弹出元素 right = d.pop() # 右端弹出: 'd', deque变为['x','a','b','c'] left = d.popleft() # 左端弹出: 'x', deque变为['a','b','c'] # 其他常用操作 d.extend(['e', 'f']) # 右端扩展 d.extendleft(['m', 'n']) # 左端扩展(注意顺序反转) d.rotate(1) # 向右循环旋转1步 d.rotate(-2) # 向左循环旋转2步 print(d[0]) # O(1) 访问首部 print(d[-1]) # O(1) 访问尾部

collections.deque vs list 性能对比

操作dequelist
右端添加(append)O(1)O(1)均摊
右端弹出(pop)O(1)O(1)
左端添加(appendleft)O(1)O(n)
左端弹出(popleft)O(1)O(n)
索引访问O(n)O(1)
中间插入O(n)O(n)

4.2 用 deque 实现栈和队列

利用 deque 可以轻松实现栈(Stack)和队列(Queue)两种数据结构:

from collections import deque class Stack: """基于 deque 实现栈(后进先出)""" def __init__(self): self._data = deque() def push(self, item): self._data.append(item) def pop(self): return self._data.pop() def peek(self): return self._data[-1] def is_empty(self): return len(self._data) == 0 class Queue: """基于 deque 实现队列(先进先出)""" def __init__(self): self._data = deque() def enqueue(self, item): self._data.append(item) def dequeue(self): return self._data.popleft() def peek(self): return self._data[0] def is_empty(self): return len(self._data) == 0

五、优先队列(PriorityQueue)

优先队列(Priority Queue)是一种特殊的队列,每个元素都有一个优先级属性。出队操作总是返回当前队列中优先级最高的元素(或最低的,取决于实现)。优先队列的底层实现通常是二叉堆(Binary Heap)

5.1 二叉堆(Binary Heap)原理

二叉堆的关键性质

  • 完全二叉树: 除最后一层外,其他层节点是满的,最后一层节点靠左排列
  • 堆序性: 最小堆中每个父节点的值 <= 所有子节点的值;最大堆中每个父节点的值 >= 所有子节点的值
  • 数组存储: 一般使用数组实现,根节点在索引0,任意节点 i 的左子节点在 2*i+1,右子节点在 2*i+2,父节点在 (i-1)//2
  • 核心操作: heapify(建堆,O(n))、push(插入,O(log n))、pop(取出最值,O(log n))

5.2 Python heapq 模块

Python 标准库的 heapq 模块提供了最小堆(Min-Heap)的实现。所有操作基于列表进行原地调整。

import heapq # 创建堆 heap = [] numbers = [5, 3, 8, 1, 6, 9, 2, 7, 4] for num in numbers: heapq.heappush(heap, num) # 每步插入后,堆保持最小堆性质 # 弹出最小值 smallest = heapq.heappop(heap) # 返回 1 # 查看最小值(不弹出) print(heap[0]) # 堆顶元素即最小值 # 从列表直接构建堆 — O(n) arr = [9, 7, 5, 3, 1, 8, 6, 4, 2] heapq.heapify(arr) # 原地建堆 # 同时 push 和 pop result = heapq.heappushpop(heap, 0) # 先 push 0,再 pop 最小值 result = heapq.heapreplace(heap, 10) # 先 pop 最小值,再 push 10 # 获取最大的 N 个元素 / 最小的 N 个元素 largest = heapq.nlargest(3, [1, 5, 3, 9, 7, 2]) # [9, 7, 5] smallest = heapq.nsmallest(3, [1, 5, 3, 9, 7, 2]) # [1, 2, 3]

5.3 自定义优先级

当需要根据复杂规则确定优先级时,可以在元组中组合优先级值,或者实现自定义类的 __lt__ 方法。

import heapq from dataclasses import dataclass, field # 方式一:使用元组(优先级, 数据) tasks = [] heapq.heappush(tasks, (3, "写报告")) heapq.heappush(tasks, (1, "处理紧急bug")) heapq.heappush(tasks, (2, "代码审查")) priority, task = heapq.heappop(tasks) print(task) # 输出: "处理紧急bug"(优先级最高,数字最小) # 方式二:自定义对象实现 __lt__ @dataclass class Patient: name: str severity: int # 病情严重程度(数字越大越严重) arrival_order: int = field(compare=False) def __lt__(self, other): # 病情更严重的优先级更高(最大堆效果) if self.severity != other.severity: return self.severity > other.severity # 同等病情时,先到的优先级高 return self.arrival_order < other.arrival_order # 模拟急诊室分诊 pq = [] heapq.heappush(pq, Patient("张三", 5, 1)) heapq.heappush(pq, Patient("李四", 9, 2)) # 更严重,先处理 heapq.heappush(pq, Patient("王五", 5, 3)) while pq: p = heapq.heappop(pq) print(f"处理患者: {p.name}, 严重程度: {p.severity}") # 输出顺序: 李四(9) -> 张三(5) -> 王五(5)

5.4 使用 queue.PriorityQueue

Python 标准库也提供了线程安全的 queue.PriorityQueue 类,其底层也是基于 heapq 实现的。

from queue import PriorityQueue pq = PriorityQueue() pq.put((2, "中等优先级")) pq.put((1, "高优先级")) pq.put((3, "低优先级")) while not pq.empty(): priority, item = pq.get() print(f"优先级 {priority}: {item}") # 输出: # 优先级 1: 高优先级 # 优先级 2: 中等优先级 # 优先级 3: 低优先级

heapq vs PriorityQueue 对比

  • heapq: 非线程安全,性能更高,适合单线程场景
  • queue.PriorityQueue: 线程安全(内部使用锁),适合多线程场景
  • queue.PriorityQueue: 提供了 put()/get() 方法的阻塞机制,支持超时
  • queue.PriorityQueue: 适合生产者-消费者模式

六、队列的典型应用

6.1 广度优先搜索(BFS)

队列是 BFS 算法的核心数据结构。无论在图还是树中,BFS 都使用队列来逐层遍历节点。

from collections import deque def bfs_graph(graph, start): """ 图的广度优先遍历 graph: 邻接表表示的图 {node: [neighbors]} start: 起始节点 """ visited = set() queue = deque([start]) visited.add(start) result = [] while queue: node = queue.popleft() result.append(node) for neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) return result # 示例:二叉树层序遍历 class TreeNode: def __init__(self, val=0): self.val = val self.left = None self.right = None def level_order(root): """二叉树层序遍历(BFS)""" if not root: return [] result = [] queue = deque([root]) while queue: level_size = len(queue) level = [] for _ in range(level_size): node = queue.popleft() level.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) result.append(level) return result

6.2 任务调度(Task Scheduling)

操作系统中的任务调度广泛使用队列。普通队列用于轮询调度(Round Robin),优先队列用于优先级调度。

import heapq import time class TaskScheduler: """基于优先级的任务调度器""" def __init__(self): self._queue = [] # 优先队列 self._counter = 0 # 相同优先级时保证 FIFO def add_task(self, name, priority=0): """添加任务,priority 越小优先级越高""" heapq.heappush(self._queue, (priority, self._counter, name)) self._counter += 1 def run_next(self): """执行下一个优先级最高的任务""" if not self._queue: return _, _, name = heapq.heappop(self._queue) print(f"[{time.strftime('%H:%M:%S')}] 执行: {name}") def run_all(self): """按优先级执行所有任务""" while self._queue: self.run_next() # 模拟 CPU 进程调度 scheduler = TaskScheduler() scheduler.add_task("系统时钟中断", priority=1) scheduler.add_task("用户程序 - 浏览器", priority=5) scheduler.add_task("磁盘 I/O 完成", priority=2) scheduler.add_task("用户程序 - 编辑器", priority=5) scheduler.add_task("网络数据包到达", priority=1) scheduler.run_all() # 执行顺序: 系统时钟中断 -> 网络数据包到达 -> 磁盘 I/O 完成 -> 浏览器 -> 编辑器

6.3 生产者-消费者模式

队列是解决生产者-消费者问题的经典方案,queue.Queue 提供了线程安全的实现。

from queue import Queue import threading import time import random def producer(q, name): """生产者:向队列中放入数据""" for i in range(5): item = f"{name}-产品-{i}" q.put(item) print(f"[生产者 {name}] 生产: {item}") time.sleep(random.uniform(0.1, 0.5)) def consumer(q, name): """消费者:从队列中取出数据并处理""" while True: item = q.get() print(f"[消费者 {name}] 消费: {item}") time.sleep(random.uniform(0.2, 0.6)) q.task_done() # 创建线程安全队列,最大容量为 10 q = Queue(maxsize=10) # 启动 2 个生产者和 3 个消费者 for i in range(2): t = threading.Thread(target=producer, args=(q, chr(65 + i))) t.daemon = True t.start() for i in range(3): t = threading.Thread(target=consumer, args=(q, chr(88 + i))) t.daemon = True t.start() # 等待所有生产者完成 print("主线程等待所有任务完成...") q.join() print("所有任务处理完毕!") # queue.Queue 的关键特性: # - put(): 如果队列已满则阻塞,直到有空位 # - get(): 如果队列为空则阻塞,直到有数据 # - task_done(): 通知队列一个任务已完成 # - join(): 阻塞直到所有任务都被处理完毕

6.4 消息队列(Message Queue)

在分布式系统和微服务架构中,消息队列是核心组件。虽然生产环境通常使用 RabbitMQ、Kafka、Redis 等专业中间件,但其核心原理仍然是队列数据结构。

消息队列的典型使用场景

  • 异步处理: 将耗时操作(如发送邮件、生成报表)放入消息队列,快速返回响应
  • 流量削峰: 高并发请求先进入消息队列,后端服务按处理能力消费,防止系统过载
  • 系统解耦: 生产者和消费者不需要直接通信,通过消息队列作为中间层
  • 日志收集: 多个服务将日志写入消息队列,日志处理系统集中消费
from queue import Queue import threading import time # 简单的异步任务处理系统(模拟消息队列) task_queue = Queue() # 后台工作线程 def worker(): while True: task = task_queue.get() print(f"[Worker] 处理任务: {task}") time.sleep(1) # 模拟任务处理时间 task_queue.task_done() threading.Thread(target=worker, daemon=True).start() # 模拟 Web 请求提交异步任务 def handle_request(req_id): print(f"[Web] 收到请求 #{req_id},已加入队列") task_queue.put(f"请求 #{req_id} 的处理任务") for i in range(5): handle_request(i) task_queue.join() print("所有请求处理完成")

七、时间复杂度分析

下表总结了各队列实现的核心操作时间复杂度:

实现方式enqueue / pushdequeue / pop peekspace
list(列表模拟) O(1) 均摊 O(n) O(1) O(n)
链表实现 O(1) O(1) O(1) O(n) *
循环队列 O(1) O(1) O(1) O(n)
collections.deque O(1) O(1) O(1) O(n) *
heapq(优先队列) O(log n) O(log n) O(1) O(n)
queue.PriorityQueue O(log n) O(log n) O(n)

* 链表和 deque 的额外空间开销来自指针/节点元数据。

选择建议:

  • 普通 FIFO 队列: 首选 collections.deque,两端操作均为 O(1)
  • 有界队列 / 缓冲区: 使用循环队列,固定内存占用,无动态扩容开销
  • 优先级调度: 使用 heapq(单线程)或 queue.PriorityQueue(多线程)
  • 线程间通信: 使用 queue.Queue(线程安全,支持阻塞)
  • 算法题 / 竞赛: 使用 collections.deque,性能优越且 API 丰富

八、核心要点总结

九、进一步思考

队列作为一种基础数据结构,其思想已经渗透到计算机科学的各个领域。深入理解队列及其变体,不仅是掌握数据结构的需要,更是理解系统设计的起点。

扩展方向

  • 阻塞队列(Blocking Queue): Java 的 ArrayBlockingQueue、LinkedBlockingQueue,Python 的 queue.Queue 都是阻塞队列的实现,常用于线程池和连接池
  • 延迟队列(Delay Queue): 元素在指定延迟时间后才能被取出,常用于定时任务和过期缓存清理
  • 并发无锁队列(Lock-Free Queue): 使用 CAS(Compare-And-Swap)指令实现的高性能并发队列,如 Python 的 multiprocessing.Queue
  • 单调队列(Monotonic Queue): 队列中的元素保持单调递增或递减,常用于解决滑动窗口最值问题(如 LeetCode 239 滑动窗口最大值)
  • 双端队列与滑动窗口: 结合双端队列可以 O(n) 时间内解决滑动窗口最大值/最小值问题

LeetCode 经典题目推荐

  • 普通队列: 225. 用队列实现栈、232. 用栈实现队列
  • 双端队列: 239. 滑动窗口最大值、862. 和至少为 K 的最短子数组
  • 优先队列: 23. 合并 K 个升序链表、215. 数组中的第 K 个最大元素、347. 前 K 个高频元素
  • BFS: 102. 二叉树的层序遍历、200. 岛屿数量、752. 打开转盘锁
  • 生产者-消费者: 1188. 设计有限阻塞队列(多线程)

实际工程中的应用

  1. Web 服务器: Nginx 的事件队列、请求队列使用队列管理连接
  2. 数据库: MySQL 的 redo log buffer、binlog 传输使用队列机制
  3. 操作系统: CPU 调度队列(就绪队列、等待队列)、I/O 请求队列
  4. 网络: 数据包缓冲队列、TCP 的滑动窗口机制
  5. 异步框架: Python asyncio 的事件循环使用队列管理协程调度
  6. 分布式系统: Kafka 的分区日志本质上是一个持久化的队列

"队列不仅仅是数据结构课上的一种抽象数据类型。理解队列的设计哲学——解耦、缓冲、异步——对于构建健壮的软件系统至关重要。FIFO 的原则看似简单,但当它与优先级、阻塞、超时、持久化等特性结合时,就演化出了现代系统中不可或缺的中间件基础设施。"