Python如何实现广度优先搜索BFS_基于collections.deque构建队列

为什么不用list而要用deque实现BFS队列

因为list.pop(0)是O(n)操作,每次从头部弹出都会移动后面所有元素;而deque.popleft()是O(1),BFS频繁出入队时性能差距会指数级放大。实测10万节点图上,list版本可能卡顿数秒,deque几乎瞬时完成。

常见错误是写成queue = []; queue.pop(0),尤其新手从循环队列概念直接套用列表索引,结果在稍大规模数据下超时或被判定为算法低效。

  • 必须从collections导入:from collections import deque
  • 初始化写法是queue = deque([start_node]),不是deque(start_node)(后者会把节点拆成字符/元素)
  • 入队统一用append(),出队统一用popleft()——别混用pop(),否则变成DFS行为

deque版BFS的标准骨架怎么写

核心就三步:初始化队列、循环取点、对未访问邻居入队。关键在于访问标记必须在入队时做,而不是出队时做,否则同一节点可能被重复加入多次。

from collections import deque

def bfs(graph, start): visited = set([start]) queue = deque([start])

while queue:
    node = queue.popleft()
    print(node)  # 处理当前节点

    for neighbor in graph.get(node, []):
        if neighbor not in visited:
            visited.add(neighbor)
            queue.append(neighbor)  # 入队即标记,防重入

注意:graph通常为dict,键是节点,值是邻接节点列表;若用邻接矩阵,遍历逻辑要改为索引扫描,但deque用法不变。

遇到层级遍历(如二叉树层序)怎么改

BFS天然适合分层,关键是每次循环前记录当前队列长度,作为本层节点数。这不是额外开数组,而是利用len(queue)的O(1)特性。

Python 3.14.3

微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。

  • 不要在循环中动态查len(queue),必须在每轮开头固定一次:for _ in range(len(queue)):
  • 如果需要返回每层结果,用level_nodes = []收集本轮node,再result.append(level_nodes)
  • 图结构做层级遍历时,仍需严格维持“入队即标记”,否则跨层重复访问会污染层级边界

示例片段:

while queue:
    level_size = len(queue)
    current_level = []
    for _ in range(level_size):
        node = queue.popleft()
        current_level.append(node)
        for n in graph[node]:
            if n not in visited:
                visited.add(n)
                queue.append(n)

实际调试时deque容易被忽略的细节

最常踩的坑是误以为deque支持下标随机访问——它确实支持q[0]q[-1],但这是O(n)操作(内部是双向链表),千万别在BFS主循环里写queue[0]代替popleft(),否则整个算法退化成O(n²)。

  • 检查是否意外调用了queue.pop()(右侧出队)导致顺序错乱
  • 多线程场景下deque不安全,但纯BFS单线程无需加锁
  • 内存上,dequelist略高一点,但换来的性能提升远超这点开销,不必纠结
  • 如果用deque(maxlen=N)做了限制,BFS中途可能丢节点——除非你明确要做滑动窗口式搜索,否则别设maxlen

真正难的从来不是写出BFS,而是当图变大、逻辑嵌套加深、还要兼容多种输入格式时,依然守住“入队即标记”和“只用popleft/append”这两条底线。

相关推荐:

如何实现Python中线程安全的单例模式_使用元类metaclass或Lock加锁

因为__new__不是原子操作:线程A判断_instance为None后,未执行super().__new__前,线程B也判为None,导致重复创建;GIL不保护跨语句逻辑,需用双重检查锁定(DCL)加threading.Lock确保线程安全。 为什么直接用 __new__ 实现单例在多线程下会失效 因为 __new__ 本身不是原子操作:线程 A 判断 _instance 为 None 后,还没...

Python如何实现图的深度优先遍历_基于栈结构或递归函数实现

递归DFS栈溢出风险高因CPython默认递归深度仅1000,长链或环易触发RecursionError;隐式栈不可控,闭包和帧对象加剧内存开销;图应优先用显式栈实现。 递归实现DFS时,为什么栈溢出风险比预期高? Python默认递归深度限制是1000层,图中存在长链或环状结构时极易触发RecursionError。这不是算法错,而是CPython解释器对递归调用的硬性约束。 用sys.setr...

Python项目中多进程消费同一个RabbitMQ队列怎么保障数据一致性?

RabbitMQ本身保证单条消息只投递一次给一个消费者,数据一致性问题源于业务逻辑并发执行导致的竞态,而非消息重复投递;basic_ack仅确认消息接收,不保障业务执行唯一性,故无法解决数据一致性问题。 多进程消费同一个RabbitMQ队列时,数据一致性问题不来自RabbitMQ本身(它天然保证单条消息只投递一次给一个消费者),而来自业务逻辑并发执行导致的竞态——比如多个进程同时读写同一张数据库表...

Python怎么使用RabbitMQ的延迟队列?基于Pika库的实现

RabbitMQ延迟队列需通过TTL+死信交换机(DLX)组合实现,精度秒级、延迟时间publish时固定为字符串毫秒值,且必须完整配置x-dead-letter-exchange与x-dead-letter-routing-key,否则消息丢弃。 延迟队列不是RabbitMQ原生功能,得靠TTL+死信交换机组合实现 rabbitmq本身没有x-delayed-message插件以外的内置延迟队列...

Pandas 中基于条件将数据填充到空矩阵:构建地理流动流量的二维汇总表

本文介绍如何使用 pandas 的 pivot_table 和 categorical 配合 fill_value 参数,将原始流动数据(含 origin-destination 地理编码和流量值)高效转换为指定地理单元集合的 n×n 流量矩阵,缺失组合自动补零。 本文介绍如何使用 pandas 的 pivot_table 和 categorical 配合 fill_value 参数,将原始流动数...

Python 大数据清洗中利用 RabbitMQ 实现多租户多队列的数据流向精细化控制

RabbitMQ的vhost不宜直接作租户隔离单位,因其不支持运行时动态增删、管理成本高、连接数易指数级上涨;应改用消息头tenant_id或routing key前缀实现逻辑隔离,并结合DLX+TTL实现可控重试。 为什么 RabbitMQ 的 vhost 不能直接当租户隔离单位用 因为 vhost 是权限和资源隔离边界,但不支持运行时动态创建/销毁,且管理成本高——每次新增租户都要调用 PUT...

Python如何实现一致性哈希算法_解决分布式缓存数据分配不均

一致性哈希通过固定哈希空间(如2^32)构建环形结构,节点和数据映射其上并顺时针就近分配;增删节点仅影响邻近区间,大幅减少数据迁移;引入虚拟节点可缓解物理节点分布不均问题,提升负载均衡性。 直接用 hash_ring 库最省事,但真要自己写、调参或排查分布不均问题,必须理解虚拟节点数量、哈希函数输出范围、环结构更新时机这三个关键点。 为什么简单取模(hash(key) % N)在节点变动时会崩 节...

Python Django中如何实现点赞收藏的计数功能_使用F表达式避免竞争条件

直接obj.likes += 1在并发下丢数据,是因为该操作分“读-改-写”三步:先查出旧值(如99),Python内存中加1得100,再保存;若两个请求同时执行,均基于99计算并写入100,最终结果为100而非102,造成更新丢失。 为什么直接 obj.likes += 1 在并发下会丢数据 多个用户几乎同时点赞时,Django 默认先查出当前 likes 值(比如 99),各自加 1 得到 1...

Python中Scikit-learn如何实现半监督学习_利用LabelPropagation

LabelPropagation要求数据具备局部平滑性,特征须标准化,未标记样本标签必须为-1,且需至少两个已标记类别;拟合用transduction_获取全部预测,不提供置信度,对噪声敏感。 LabelPropagation 在 scikit-learn 中的适用前提 LabelPropagation 不是万能半监督工具,它要求数据天然具备“局部平滑性”——即相似样本大概率共享标签。如果特征空间...