算法刷题系列(三十七):图论 BFS——在无权图中求最少边数
算法刷题系列(三十七):图论 BFS——在无权图中求最少边数
算法刷题系列(三十七):图论 BFS——在无权图中求最少边数
无权图中,从起点到某个顶点的最短路径,是经过边数最少的路径。广度优先搜索(BFS)从起点开始按距离一层层扩展,因此首次发现一个顶点时,得到的就是它的最少边数。
图用邻接表表示:graph[u] 列出从 u 可一步到达的邻居。无向边 {u,v} 要在两端都加入邻接表;有向边 u→v 只加入 graph[u]。
队列中的层次与多源搜索
distance[v] == -1 表示顶点尚未访问。将顶点入队时就写入距离,而不是出队后才标记;这样即使多个前驱都能到它,也只会入队一次。若有多个起点,把它们都以距离 0 入队,搜索便计算每个顶点到最近起点的最短边数。
Java 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
public class MultiSourceBfs {
public static int[] distances(List<List<Integer>> graph, int[] sources) {
int n = graph.size();
int[] distance = new int[n];
Arrays.fill(distance, -1);
Deque<Integer> queue = new ArrayDeque<>();
for (int source : sources) {
if (source < 0 || source >= n) {
throw new IllegalArgumentException("source out of range");
}
if (distance[source] == -1) {
distance[source] = 0;
queue.addLast(source);
}
}
while (!queue.isEmpty()) {
int node = queue.removeFirst();
for (int neighbor : graph.get(node)) {
if (neighbor < 0 || neighbor >= n) {
throw new IllegalArgumentException("neighbor out of range");
}
if (distance[neighbor] == -1) {
distance[neighbor] = distance[node] + 1;
queue.addLast(neighbor);
}
}
}
return distance;
}
}
Python 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from collections import deque
def bfs_distances(
graph: list[list[int]], sources: list[int]
) -> list[int]:
n = len(graph)
distance = [-1] * n
queue: deque[int] = deque()
for source in sources:
if not 0 <= source < n:
raise ValueError("source out of range")
if distance[source] == -1:
distance[source] = 0
queue.append(source)
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if not 0 <= neighbor < n:
raise ValueError("neighbor out of range")
if distance[neighbor] == -1:
distance[neighbor] = distance[node] + 1
queue.append(neighbor)
return distance
graph = [[1, 2], [0, 3], [0, 3], [1, 2], []]
assert bfs_distances(graph, [0, 4]) == [0, 1, 1, 2, 0]
assert bfs_distances(graph, []) == [-1, -1, -1, -1, -1]
正确性与复杂度
队列按先入先出的顺序处理顶点。所有距离为 d 的顶点先于距离为 d+1 的顶点出队;从距离 d 的顶点首次发现的邻居只需再走一条边,因此距离是 d+1。若存在更短路径,其前一层顶点会更早展开并先发现目标,与“首次发现时距离较大”矛盾。
邻接表下,每个顶点最多入队一次,每条边最多检查一次(无向边在表中出现两次),时间 O(V+E),额外空间 O(V)。邻接矩阵实现则通常要扫描整行,时间会变为 O(V²)。
边界与易错点
- 多个源点之间重复时应去重;否则虽不影响正确性,但会重复入队。
- 没有起点时所有距离保持
-1;不可达顶点也用-1表示。 - 无权图才按边数用 BFS 求最短路;若边权不相等,应考虑 Dijkstra 等算法。
- 需要恢复具体路径时,发现邻居时同时记录
parent[neighbor] = node。 - 图的方向由邻接表建图方式决定;把有向图误当无向图会引入不存在的路径。
BFS 的关键不变量是首次入队即确定最短边数。多源版本只改变初始化:把每个源都放进第零层。
本文由作者按照 CC BY 4.0 进行授权