【算法图解】BFS:层层推进的搜索
BFS(广度优先搜索)像水波纹一样一层层扩散,天然适合最短路径(无权图)与层级遍历。本文讲清队列实现、visited 的必要性、复杂度、常见题型与工程化写法。
16 分钟阅读
小明
【算法图解】BFS:层层推进的搜索
BFS(Breadth-First Search,广度优先搜索)的感觉像“水波纹”:
- 从起点出发
- 先把距离为 1 的点都访问完
- 再访问距离为 2 的点
- 直到覆盖全图或找到目标
这使得 BFS 天然适合:
- 无权图的最短路径
- 树的层序遍历
- 最少步数/最少操作次数
1. BFS 的核心:队列
BFS 的数据结构是队列:
- 先入先出
这正好对应“按层扩散”。
2. 最小可运行模板(图)
图的表示方式很多,这里用邻接表:
const graph = {
A: ['B', 'C'],
B: ['D'],
C: ['D'],
D: [],
}
BFS:
function bfs(graph, start) {
const queue = [start]
const visited = new Set([start])
const order = []
while (queue.length) {
const node = queue.shift()
order.push(node)
for (const next of graph[node] ?? []) {
if (!visited.has(next)) {
visited.add(next)
queue.push(next)
}
}
}
return order
}
console.log(bfs(graph, 'A'))
(注意:shift() 在 JS 数组上是 $O(n)$,工程里更推荐用指针或双端队列。)
3. 为什么必须有 visited?
在图里如果没有 visited:
- 你会在环上无限循环
所以 BFS/DFS 在图中几乎总是需要 visited。
在树中通常不需要,因为树没有环。
4. BFS 求最短路径(无权图)
BFS 的关键性质:
- 第一次到达某个节点时,走过的边数是最少的
实现最短路径,你需要:
dist:距离(步数)prev:前驱,用来还原路径
function shortestPath(graph, start, target) {
const queue = [start]
const visited = new Set([start])
const prev = new Map()
while (queue.length) {
const node = queue.shift()
if (node === target) break
for (const next of graph[node] ?? []) {
if (!visited.has(next)) {
visited.add(next)
prev.set(next, node)
queue.push(next)
}
}
}
if (!visited.has(target)) return null
const path = []
for (let cur = target; cur != null; cur = prev.get(cur)) {
path.push(cur)
if (cur === start) break
}
path.reverse()
return path
}
5. 复杂度
- 时间:$O(V+E)$(每个点入队一次,每条边最多看一次)
- 空间:$O(V)$(visited、队列、prev)
6. 典型题型
- 二叉树层序遍历
- 最少步数到达终点(迷宫、棋盘)
- 单词接龙(状态图)
- 拓扑层级(有向无环图)
做这类题的“套路”是:
- 把状态看成节点
- 把一次操作看成边
- BFS 找最短步数
总结
- BFS 用队列实现“按层扩散”。
- 在图里必须 visited。
- BFS 是无权图最短路径的天然解。