队列 (Queue)

概念

队列是一种线性数据结构,遵循先进先出 (FIFO - First-In, First-Out) 的原则。这意味着最早进入队列的元素将最先被移除。可以将其想象成现实生活中的排队队伍,先到的人先接受服务。

队列的基本操作包括:

  • 入队 (Enqueue):在队列的尾部添加一个元素。
  • 出队 (Dequeue):从队列的头部移除一个元素。
  • 查看队首元素 (Peek/Front):获取队列头部的元素,但不移除它。
  • 判断队列是否为空 (isEmpty):检查队列中是否没有任何元素。
  • 获取队列大小 (Size):获取队列中元素的数量。

Python 实现

Python 中有多种方式可以实现队列:

1. 使用 collections.deque (推荐)

collections.deque (双端队列) 是专门为在两端快速添加和弹出元素而设计的,因此非常适合实现队列和栈。

from collections import deque

# 创建一个空队列
my_queue = deque()

# 入队 (在右端添加)
my_queue.append('A')
my_queue.append('B')
my_queue.append('C')
print(f"Queue after enqueues: {my_queue}") # deque(['A', 'B', 'C'])

# 出队 (从左端移除)
if my_queue: # 检查队列是否为空
    front_element = my_queue.popleft()
    print(f"Dequeued element: {front_element}") # 'A'
    print(f"Queue after dequeue: {my_queue}")   # deque(['B', 'C'])
else:
    print("Queue is empty, cannot dequeue.")

# 查看队首元素
if my_queue:
    peek_element = my_queue[0]
    print(f"Front element (peek): {peek_element}") # 'B'
else:
    print("Queue is empty, cannot peek.")

# 判断队列是否为空
is_empty = not my_queue # 或者 len(my_queue) == 0
print(f"Is queue empty? {is_empty}") # False

# 获取队列大小
queue_size = len(my_queue)
print(f"Queue size: {queue_size}") # 2

# 继续出队直到队列为空
while my_queue:
    print(f"Dequeued: {my_queue.popleft()}")

print(f"Queue after all dequeues: {my_queue}") # deque([])
print(f"Is queue empty now? {not my_queue}")    # True

deque 的优点

  • append()popleft() 操作的时间复杂度都是 O(1),非常高效。
  • 线程安全(对于单个操作)。

2. 使用列表 (List) (效率较低)

虽然可以使用 Python 的列表来实现队列,但这通常不是最高效的方法,因为在列表的开头插入或删除元素(对应出队操作)的时间复杂度是 O(n),其中 n 是列表的长度。这是因为列表中的所有后续元素都需要移动。

# 创建一个空列表作为队列
list_queue = []

# 入队 (在列表末尾添加 - O(1) on average)
list_queue.append(10)
list_queue.append(20)
list_queue.append(30)
print(f"List queue after enqueues: {list_queue}") # [10, 20, 30]

# 出队 (从列表开头移除 - O(n))
if list_queue:
    front_element = list_queue.pop(0)
    print(f"Dequeued element: {front_element}") # 10
    print(f"List queue after dequeue: {list_queue}")   # [20, 30]
else:
    print("List queue is empty.")

# 查看队首元素
if list_queue:
    peek_element = list_queue[0]
    print(f"Front element (peek): {peek_element}") # 20
else:
    print("List queue is empty.")

# 判断队列是否为空
is_empty = not list_queue
print(f"Is list queue empty? {is_empty}") # False

# 获取队列大小
queue_size = len(list_queue)
print(f"List queue size: {queue_size}") # 2

对于频繁的出队操作,使用列表实现的队列性能会比较差。

3. 使用 queue.Queue (用于多线程)

Python 的 queue 模块提供了一个 Queue 类,它是线程安全的,主要用于在多线程环境中生产者和消费者之间传递数据。

import queue

# 创建一个线程安全的队列
thread_safe_queue = queue.Queue()
# q = queue.Queue(maxsize=5) # 可以指定最大容量,如果队列满了,put会阻塞

# 入队
thread_safe_queue.put('Task 1')
thread_safe_queue.put('Task 2')
thread_safe_queue.put('Task 3')
print(f"Thread-safe queue size: {thread_safe_queue.qsize()}")

# 出队
if not thread_safe_queue.empty():
    item = thread_safe_queue.get() # 如果队列为空,get()会阻塞直到有元素
    print(f"Dequeued item: {item}") # 'Task 1'
    # thread_safe_queue.task_done() # 用于生产者-消费者模式,通知任务已完成
else:
    print("Thread-safe queue is empty.")

# 查看队首元素 (Queue 对象没有直接的 peek 方法,需要先 get 再 put回去,或者不消费)
# 通常在多线程场景下,不建议这样做,因为它不是原子操作。
# 如果确实需要,可以这样做(但不推荐用于并发场景的常规操作):
# if not thread_safe_queue.empty():
#     peek_item = thread_safe_queue.queue[0] # 访问内部 deque
#     print(f"Peek item: {peek_item}")

# 判断队列是否为空
print(f"Is thread-safe queue empty? {thread_safe_queue.empty()}")

# 获取队列大小
print(f"Thread-safe queue size: {thread_safe_queue.qsize()}")

# 清空队列
while not thread_safe_queue.empty():
    thread_safe_queue.get_nowait() # get_nowait() 如果为空立即抛出 Empty 异常

print(f"Thread-safe queue empty after clearing? {thread_safe_queue.empty()}")

queue.Queue 适用于并发编程,如果只是在单线程中使用队列数据结构,collections.deque 是更好的选择。

基本操作总结 (以 collections.deque 为例)

  • 入队 (Enqueue): my_queue.append(item)
  • 出队 (Dequeue): item = my_queue.popleft() (需要先检查队列是否为空)
  • 查看队首元素 (Peek): front_item = my_queue[0] (需要先检查队列是否为空)
  • 判断队列是否为空: is_empty = not my_queueis_empty = len(my_queue) == 0
  • 获取队列大小: size = len(my_queue)

应用场景

队列在计算机科学中有广泛的应用,例如:

  • 任务调度:操作系统中的进程调度,打印机任务队列。
  • 广度优先搜索 (BFS):图和树的遍历算法。
  • 数据缓冲:在数据生产者和消费者速率不同时,用作缓冲区,如网络数据包的接收和处理。
  • 消息队列:在分布式系统中用于组件间的异步通信。
  • 模拟现实世界中的排队系统

选择合适的队列实现取决于具体需求,对于通用单线程场景,collections.deque 因其性能和易用性而成为首选。