pin_drop当前位置:知识文库 ❯ 图文

Python deque双端队列详解 - 滑动窗口与BFS实战教程

一、deque 概述

deque(Double-Ended Queue,双端队列)是 collections 模块中的高效队列实现,支持在两端快速添加和删除元素。与 list 相比,deque 在头部操作的时间复杂度为 O(1),而 list 为 O(n)。deque 还支持设置最大长度(maxlen),当元素超过限制时自动从另一端丢弃,非常适合实现滑动窗口、BFS 遍历、历史记录等场景。


二、语法与参数说明

基本语法

代码示例

from collections import deque

# 创建deque
dq = deque(iterable, maxlen=None)

# 常用操作
dq.append(x)        # 右端添加
dq.appendleft(x)    # 左端添加
dq.pop()            # 右端弹出
dq.popleft()        # 左端弹出

构造函数参数

参数 类型 默认值 说明
iterable 可迭代对象 None 初始化元素
maxlen int None 最大长度,超出时从对端丢弃

常用方法

方法 时间复杂度 说明
append(x) O(1) 右端添加元素
appendleft(x) O(1) 左端添加元素
pop() O(1) 右端弹出并返回元素
popleft() O(1) 左端弹出并返回元素
extend(iterable) O(k) 右端扩展多个元素
extendleft(iterable) O(k) 左端扩展多个元素(顺序反转)
rotate(n=1) O(k) 旋转n步(正数右移,负数左移)
clear() O(1) 清空所有元素

返回值说明

  • 构造函数:返回 deque 对象

  • pop()、popleft():返回弹出的元素

  • count():返回 int

  • rotate()、clear()、reverse():无返回值


三、代码示例

示例1:基本操作与性能对比

代码示例

from collections import deque
import time

# 基本操作
dq = deque([1, 2, 3])
dq.append(4)
dq.appendleft(0)
print(f"添加后: {dq}")

right = dq.pop()
left = dq.popleft()
print(f"弹出右端: {right}, 弹出左端: {left}")
print(f"弹出后: {dq}")

# 旋转操作
dq = deque([1, 2, 3, 4, 5])
dq.rotate(2)   # 右移2步
print(f"\n右移2步: {dq}")
dq.rotate(-2)  # 左移2步
print(f"左移2步: {dq}")

# 性能对比:头部插入
n = 100000

start = time.time()
lst = []
for i in range(n):
    lst.insert(0, i)
list_time = time.time() - start

start = time.time()
dq = deque()
for i in range(n):
    dq.appendleft(i)
deque_time = time.time() - start

print(f"\n头部插入{n}次:")
print(f"  list: {list_time:.3f}秒")
print(f"  deque: {deque_time:.3f}秒")
print(f"  速度比: {list_time/deque_time:.1f}x")

输出:

代码示例

添加后: deque([0, 1, 2, 3, 4])
弹出右端: 4, 弹出左端: 0
弹出后: deque([1, 2, 3])

右移2步: deque([4, 5, 1, 2, 3])
左移2步: deque([1, 2, 3, 4, 5])

头部插入100000次:
  list: 1.234秒
  deque: 0.008秒
  速度比: 154.2x

示例2:滑动窗口与 maxlen

代码示例

from collections import deque

# maxlen自动丢弃旧元素
history = deque(maxlen=5)
for i in range(10):
    history.append(f"操作{i+1}")

print(f"最近5条记录: {list(history)}")

# 滑动窗口最大值
def sliding_max(nums, k):
    """使用deque实现滑动窗口最大值"""
    result = []
    window = deque()  # 存储索引

    for i, num in enumerate(nums):
        # 移除窗口外的索引
        while window and window[0] <= i - k:
            window.popleft()

        # 移除比当前值小的索引(它们不可能是最大值)
        while window and nums[window[-1]] <= num:
            window.pop()

        window.append(i)

        if i >= k - 1:
            result.append(nums[window[0]])

    return result

nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
print(f"\n数组: {nums}")
print(f"窗口大小: {k}")
print(f"滑动窗口最大值: {sliding_max(nums, k)}")

输出:

代码示例

最近5条记录: ['操作6', '操作7', '操作8', '操作9', '操作10']

数组: [1, 3, -1, -3, 5, 3, 6, 7]
窗口大小: 3
滑动窗口最大值: [3, 3, 5, 5, 6, 7]

示例3:BFS 图遍历

代码示例

from collections import deque

def bfs(graph, start):
    """广度优先搜索"""
    visited = set()
    queue = deque([start])
    visited.add(start)
    order = []

    while queue:
        node = queue.popleft()
        order.append(node)

        for neighbor in graph.get(node, []):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

    return order

def shortest_path(graph, start, end):
    """最短路径(BFS)"""
    if start == end:
        return [start]

    visited = {start}
    queue = deque([(start, [start])])

    while queue:
        node, path = queue.popleft()

        for neighbor in graph.get(node, []):
            if neighbor == end:
                return path + [neighbor]
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))

    return None

# 构建图
graph = {
    "A": ["B", "C"],
    "B": ["A", "D", "E"],
    "C": ["A", "F"],
    "D": ["B"],
    "E": ["B", "F"],
    "F": ["C", "E"],
}

print("BFS遍历顺序:", bfs(graph, "A"))
print("A到F最短路径:", shortest_path(graph, "A", "F"))

输出:

代码示例

BFS遍历顺序: ['A', 'B', 'C', 'D', 'E', 'F']
A到F最短路径: ['A', 'C', 'F']

四、实际应用场景

  • 滑动窗口:使用 maxlen 实现固定大小的滑动窗口,如移动平均、最近N条记录

  • BFS 遍历:广度优先搜索的标准数据结构,保证按层级遍历

  • 历史记录:实现撤销/重做功能,或浏览器的前进/后退历史


五、注意事项

注意1deque 的随机访问(dq[i])时间复杂度为 O(n),比 list 的 O(1) 慢得多。如果需要频繁随机访问,应使用 list

注意2extendleft() 会将元素逆序添加到左端,因为每次 appendleft 都将新元素放在最前面。

注意3:设置 maxlen 后,当 deque 满时,从一端添加元素会自动从另一端丢弃,此操作不会引发异常。

注意4deque 不支持 sort() 方法,如需排序应先转为 list

提示rotate(n) 方法可以高效实现循环移位。正数 n 表示右移(末尾元素移到开头),负数 n 表示左移。


六、与 list 对比

特性 deque list queue.Queue asyncio.Queue
两端操作 O(1) 左端O(n) O(1) O(1)
随机访问 O(n) O(1) 不支持 不支持
线程安全
maxlen 支持 不支持 不支持 支持
旋转操作 支持 不支持 不支持 不支持
适用场景 算法/数据结构 通用 多线程 异步

七、小结

  • 双端高效deque 是双端队列,两端操作均为 O(1),适合频繁在头部增删元素的场景

  • maxlen 特性maxlen 参数实现自动丢弃的固定大小队列,适合滑动窗口和历史记录

  • 旋转与扩展rotate() 方法实现高效循环移位,extendleft() 注意元素逆序

  • 随机访问限制:随机访问为 O(n),不适合需要频繁索引访问的场景


八、练习题

练习1

使用 deque 实现一个回文检测器,从字符串两端同时比较字符。

练习2

使用 dequemaxlen 实现一个最近文件列表,最多保存 10 个最近打开的文件路径,重复打开时移到最前。

练习3

使用 deque 实现 BFS 解决迷宫最短路径问题,输入二维矩阵表示迷宫(0为通路,1为墙壁),输出从起点到终点的最短路径长度。

常见问题

deque 和 list 有什么区别,什么时候用 deque?

deque 在两端添加和删除元素都是 O(1) 时间复杂度,而 list 在头部操作是 O(n)。当你需要频繁在队列头部进行插入或删除操作时(如实现队列、滑动窗口、BFS),应该使用 deque。如果只是普通的顺序存储和尾部操作,list 就足够了。

maxlen 参数是如何工作的?

当设置了 maxlen 后,deque 会自动保持固定长度。从右端添加元素时,如果已满,左端最旧的元素会被自动丢弃;从左端添加时,右端最旧的元素会被丢弃。这个过程不会抛出异常,非常适合实现最近N条记录、移动平均等场景。

extendleft() 为什么会逆序?

extendleft([a, b, c]) 等价于依次执行 appendleft(a)、appendleft(b)、appendleft(c)。由于每次 appendleft 都将元素放在最前面,所以最终顺序是 [c, b, a]。如果希望保持原顺序,可以先将序列反转:dq.extendleft(reversed([a, b, c]))。

deque 是线程安全的吗?

deque 本身不是线程安全的。append() 和 popleft() 等单个操作是原子性的,但组合操作(如先检查再弹出)不是线程安全的。如果需要在多线程环境中使用,应该使用 queue.Queue 类,它是专门设计用于线程安全的队列。

如何实现一个固定大小的滑动窗口?

使用 maxlen 参数创建 deque 即可:window = deque(maxlen=k)。每次 append 新元素时,如果窗口已满,最旧的元素会自动被丢弃。配合其他逻辑可以实现移动平均、滑动最大值/最小值等经典算法。

标签: deque 双端队列 collections BFS 滑动窗口 Python教程

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Python OrderedDict有序字典详解 - move_to_end与LRU缓存实现 下一篇: Python namedtuple命名元组详解 - 替代字典的轻量方案

poll相关推荐