Python中BFS必须用collections.deque,因其popleft()和append()均为O(1),而list.pop(0)是O(n),节点超2000时性能差距可达5倍以上;需用set存visited、parent字典记录路径、邻接表存图,并在入队前检查是否已访问。

Python里用collections.deque写BFS最稳
不用list.pop(0)模拟队列,它每次删头是O(n)操作,图稍大就卡住。真BFS必须用deque——内部是双向链表,popleft()和append()都是O(1)。
常见错误现象:list当队列跑稀疏图不明显,但节点超2000个后,耗时可能差5倍以上;更隐蔽的是,某些测试用例会故意加长路径,让list.pop(0)退化成纯暴力遍历。
- 初始化队列:用
deque([start_node]),别写deque().append(start_node) - 每轮取节点:必须用
queue.popleft(),不是pop()(那是栈) - 避免重复访问:用
set存visited,别用list in查,后者是O(n)
路径还原得靠parent字典,不是靠队列顺序
BFS本身只保证第一次到达某节点的距离最短,但队列里不存路径信息。想返回具体路径,必须在访问邻居时记录“谁带它来的”。
使用场景:找最短路径字符串、输出节点序列、做导航类逻辑。如果只要判断连通性或最短距离,parent可省略。
- 初始化:
parent = {start: None},别漏掉起点 - 更新时机:只在
neighbor not in visited时设parent[neighbor] = current - 还原路径:从终点往回跳
parent,用while循环拼列表,最后reverse(),别用递归(深图易栈溢出)
graph用邻接表还是邻接矩阵?看数据规模和稀疏度
邻接矩阵(二维list或numpy数组)查边快O(1),但建表O(n²),内存吃紧;邻接表(dict套list或set)省内存、遍历快,适合绝大多数图问题。
性能影响:1000节点以下两者差别不大;上万节点且边数不到节点平方的1%,邻接表快3倍以上,内存少90%。
- 推荐结构:
graph = {node: [neighbor1, neighbor2, ...]},邻居用list就行,除非要频繁删边 - 注意键存在性:用
graph.get(node, [])代替graph[node],防KeyError - 无向图记得双向加边:
graph[u].append(v)和graph[v].append(u)
遇到MemoryError或超时,先检查是否漏了visited判重
这是BFS最常踩的坑——没在入队前检查是否已访问,导致同一节点反复进队,队列爆炸式增长。小图可能只是慢,大图直接崩。
错误现象:len(queue)在几轮后突然飙到百万级;visited集合大小远小于预期节点数;CPU跑满但没进展。
- 判重位置必须在
for neighbor in graph[current]:循环内,且在append前 - 别把
visited.add(neighbor)写在popleft()之后——那已经晚了,当前节点的邻居可能已被重复加入多次 - 调试技巧:打印
len(queue)和len(visited),若前者长期大于后者10倍,基本就是漏判重
deque、路径有没有记parent、visited有没有在正确位置拦住重复节点。