Menu
CoddyTech

广度优先搜索(BFS)

最后更新

广度优先搜索逐层探索一个图。它从源节点出发,先访问所有直接相邻的节点,再访问这些节点尚未访问的邻居,依此类推,像一圈圈距离递增的波纹一样向外扩展。点击上方的“播放”,观察它如何从起点一层一层地向外展开。

BFS 使用先进先出的队列,正是队列保证了逐层访问的顺序。因为它按跳数从近到远到达各个节点,BFS 能在无权图中找到最短路径(边数最少)。它对每个节点和每条边各访问一次,所以运行时间是 O(V + E)。

时间和空间复杂度

指标复杂度说明
时间O(V + E)每个顶点和每条边各访问一次
空间O(V)队列加已访问集合,最坏情况包含所有节点
遍历方式逐层最近的节点优先,一圈一圈向外
最短路径是(无权图)以最少的边到达每个节点

分步说明

步骤发生了什么
1把源节点加入队列。
2取出队首节点,并标记为已访问。
3查看它的每个邻居。
4把所有尚未访问、也不在队列中的邻居加入队列。
5重复,直到队列为空。

示例演算

从节点 0 开始遍历这个图,它的边为 0-1、0-2、1-3、2-3、2-4:

步骤已访问队列(待访问)
开始{}[0]
出队 0{0}[1, 2]
出队 1{0, 1}[2, 3]
出队 2{0, 1, 2}[3, 4]
出队 3{0, 1, 2, 3}[4]
出队 4{0, 1, 2, 3, 4}[](完成)

什么时候使用 BFS

适合使用的情况不适合使用的情况
需要在无权图中找最短路径边带有权重,应改用 Dijkstra 算法
目标很可能离源节点很近图非常宽,队列可能要存放庞大的待访问节点
希望按层次顺序探索图只需到达任意一个节点,这时 DFS 占用的内存更少
要找连通分量或跳数最少的路线需要拓扑排序或环检测,DFS 更合适

BFS 与 DFS 对比

两者都以 O(V + E) 遍历所有节点,但访问顺序和所用的数据结构不同。打开深度优先搜索的可视化,把两者放在一起对比。

方面BFSDFS
数据结构队列(FIFO)栈或递归
顺序逐层(最近的优先)沿一条分支深入,再回溯
最短路径(无权图)是,边数最少否,无法保证
在宽图上的内存高,待访问节点可能很多低,每次只保存一条路径
最适合最少跳数、连通分量环检测、拓扑排序、回溯

Breadth-First Search 代码

简洁、可运行的 Breadth-First Search 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。

Breadth-First Search 的 Python 代码

Python
1from collections import deque2
3
4def bfs(graph, start):5    visited = {start}6    queue = deque([start])7    order = []8    while queue:9        node = queue.popleft()10        order.append(node)11        for neighbor in graph[node]:12            if neighbor not in visited:13                visited.add(neighbor)  # mark on enqueue, not dequeue14                queue.append(neighbor)15    return order16
17
18graph = {19    "A": ["B", "C"],20    "B": ["D", "E"],21    "C": ["F"],22    "D": [],23    "E": ["F"],24    "F": [],25}26
27print("BFS order:", " -> ".join(bfs(graph, "A")))
在 Python 在线编辑器中运行这段代码

广度优先搜索常见问题

BFS 的时间复杂度是多少?
BFS 的运行时间是 O(V + E),其中 V 是顶点数,E 是边数,因为它对每个顶点访问一次,对每条边检查一次。队列和已访问集合占用 O(V) 的空间。
BFS 能找到最短路径吗?
在无权图中可以。因为 BFS 按离源节点的跳数从近到远到达各个节点,它第一次到达某个节点时,走的就是边数最少的路径。对于带权图,你需要改用 Dijkstra 算法。
BFS 和 DFS 有什么区别?
BFS 使用队列,逐层探索(最近的优先);而 DFS 使用栈,先沿一条分支深入到底再回溯。BFS 能找到无权图中的最短路径;DFS 在宽图上占用的内存更少,适合环检测和拓扑排序。
什么时候该用 BFS 而不是 Dijkstra 算法?
当每条边的代价都相同时,用 BFS,因为它不需要优先队列,就能在 O(V + E) 时间内找到边数最少的路径。当边的权重各不相同时,才需要 Dijkstra 算法;在带权图上直接运行 BFS,得到的是跳数最少的路径,而不是代价最低的路径。
为什么 BFS 需要已访问集合?

图中可能有环,如果没有已访问集合,BFS 会反复把同一个节点加入队列,陷入无限循环。在节点第一次入队时(而不是出队时)就标记它,还能防止同一个节点被不同的邻居重复加入队列。

应该在入队时还是出队时标记节点为已访问?

在入队时标记。如果等到出队时才标记,一个节点在被处理之前,可能会被不同的邻居多次加入队列,浪费内存和时间。入队时标记能保证每个节点恰好进入队列一次。

Coddy 编程语言插图

在 Coddy 掌握算法

开始