广度优先搜索(BFS)与深度优先搜索(DFS)详解
广度优先搜索(BFS)和深度优先搜索(DFS)是图算法中两个基本的遍历方法。通过实例分析它们的工作原理和代码实现,可以更深入地理解它们的应用场景及优缺点。
1. 广度优先搜索(BFS)
原理:
广度优先搜索从起始节点开始,逐层遍历图中的所有节点,优先访问距离起始节点最近的节点。其核心思想是以一种层级结构进行遍历。
示例:
假设有一个无向图如下所示:
A
/ \
B C
/ \ \
D E F
从节点 A 开始进行广度优先搜索的顺序是:A -> B -> C -> D -> E -> F。
实现步骤:
- 初始化一个队列,将起始节点
A加入队列。 - 标记节点
A为已访问。 - 当队列不为空时,执行以下操作:
- 从队列中取出一个节点,访问该节点的所有未被访问过的邻居节点,并将这些邻居节点加入队列,同时标记为已访问。
- 重复步骤3,直到队列为空。
代码实现(Java):
import java.util.*;
public class BFS {
public void bfs(GraphNode startNode) {
// 初始化一个队列存储待访问的节点
Queue<GraphNode> queue = new LinkedList<>();
// 使用一个集合存储已访问的节点
Set<GraphNode> visited = new HashSet<>();
// 将起始节点加入队列并标记为已访问
queue.add(startNode);
visited.add(startNode);
// 当队列不为空时,持续遍历
while (!queue.isEmpty()) {
// 从队列中取出一个节点
GraphNode currentNode = queue.poll();
// 处理当前节点(这里简单打印节点值)
System.out.println("Visited node: " + currentNode.value);
// 遍历当前节点的所有邻居节点
for (GraphNode neighbor : currentNode.neighbors) {
// 如果邻居节点未被访问过,将其加入队列并标记为已访问
if (!visited.contains(neighbor)) {
queue.add(neighbor);
visited.add(neighbor);
}
}
}
}
// 图节点类的定义
class GraphNode {
int value; // 节点的值
List<GraphNode> neighbors; // 节点的邻居节点列表
GraphNode(int value) {
this.value = value;
neighbors = new ArrayList<>();
}
}
}
示例代码详细解释:
-
初始化队列和集合:
Queue<GraphNode> queue = new LinkedList<>();:使用LinkedList来实现队列。Set<GraphNode> visited = new HashSet<>();:使用HashSet来存储已访问的节点,避免重复访问。
-
将起始节点加入队列并标记为已访问:
queue.add(startNode);:将起始节点加入队列。visited.add(startNode);:将起始节点加入已访问集合。
-
当队列不为空时,持续遍历:
while (!queue.isEmpty()) {:当队列不为空时,继续遍历。GraphNode currentNode = queue.poll();:从队列中取出一个节点。System.out.println("Visited node: " + currentNode.value);:处理当前节点,这里简单打印节点值。
-
遍历当前节点的所有邻居节点:
for (GraphNode neighbor : currentNode.neighbors) {:遍历当前节点的所有邻居节点。if (!visited.contains(neighbor)) {:如果邻居节点未被访问过。queue.add(neighbor);:将邻居节点加入队列。visited.add(neighbor);:将邻居节点标记为已访问。
应用:
- 最短路径求解:在无权图中,BFS可以找到从起点到终点的最短路径。
- 层级遍历:可以用于二叉树的层序遍历。
优缺点:
- 优点:能找到无权图中的最短路径,适用于层级遍历。
- 缺点:在宽度较大的图中,BFS的空间复杂度较高,因为需要存储每一层的所有节点。
2. 深度优先搜索(DFS)
原理:
深度优先搜索从起始节点开始,沿着一条路径不断深入,直到不能继续为止,然后回溯到上一节点,继续探索其他路径。其核心思想是优先深入每一条路径。
示例:
假设有一个无向图如下所示:
A
/ \
B C
/ \ \
D E F
从节点 A 开始进行深度优先搜索的顺序可能是:A -> B -> D -> E -> C -> F。具体顺序可能因为邻接节点的访问顺序不同而有所不同。
实现步骤:
- 初始化一个栈,将起始节点
A加入栈。 - 标记节点
A为已访问。 - 当栈不为空时,执行以下操作:
- 从栈中取出一个节点,访问该节点的所有未被访问过的邻居节点,并将这些邻居节点加入栈,同时标记为已访问。
- 重复步骤3,直到栈为空。
代码实现(Java):
import java.util.*;
public class DFS {
public void dfs(GraphNode startNode) {
// 初始化一个栈存储待访问的节点
Stack<GraphNode> stack = new Stack<>();
// 使用一个集合存储已访问的节点
Set<GraphNode> visited = new HashSet<>();
// 将起始节点加入栈并标记为已访问
stack.push(startNode);
visited.add(startNode);
// 当栈不为空时,持续遍历
while (!stack.isEmpty()) {
// 从栈中取出一个节点
GraphNode currentNode = stack.pop();
// 处理当前节点(这里简单打印节点值)
System.out.println("Visited node: " + currentNode.value);
// 遍历当前节点的所有邻居节点
for (GraphNode neighbor : currentNode.neighbors) {
// 如果邻居节点未被访问过,将其加入栈并标记为已访问
if (!visited.contains(neighbor)) {
stack.push(neighbor);
visited.add(neighbor);
}
}
}
}
// 图节点类的定义
class GraphNode {
int value; // 节点的值
List<GraphNode> neighbors; // 节点的邻居节点列表
GraphNode(int value) {
this.value = value;
neighbors = new ArrayList<>();
}
}
}
示例代码详细解释:
-
初始化栈和集合:
Stack<GraphNode> stack = new Stack<>();:使用Stack来实现栈。Set<GraphNode> visited = new HashSet<>();:使用HashSet来存储已访问的节点,避免重复访问。
-
将起始节点加入栈并标记为已访问:
stack.push(startNode);:将起始节点加入栈。visited.add(startNode);:将起始节点加入已访问集合。
-
当栈不为空时,持续遍历:
while (!stack.isEmpty()) {:当栈不为空时,继续遍历。GraphNode currentNode = stack.pop();:从栈中取出一个节点。System.out.println("Visited node: " + currentNode.value);:处理当前节点,这里简单打印节点值。
-
遍历当前节点的所有邻居节点:
for (GraphNode neighbor : currentNode.neighbors) {:遍历当前节点的所有邻居节点。if (!visited.contains(neighbor)) {:如果邻居节点未被访问过。stack.push(neighbor);:将邻居节点加入栈。visited.add(neighbor);:将邻居节点标记为已访问。
应用:
- 路径搜索:在图中查找特定路径或解决迷宫问题。
- 拓扑排序:用于DAG(有向无环图)的拓扑排序。
- 连通性检查:判断图的连通性,查找连通分量。
优缺点:
- 优点:空间复杂度较低,在图的深度较大
而宽度较小时,性能较好。
- 缺点:可能会陷入深层路径,从而导致找到的路径不是最优解(尤其在有权图中)。
比较与选择
- **广度优先搜索(BFS)**适用于需要找到最短路径的问题,以及需要层级遍历的情况。
- **深度优先搜索(DFS)**适用于需要探索所有路径的问题,以及在图的深度较大而宽度较小时的情况。
在实际应用中,选择BFS还是DFS取决于具体问题的要求和图的结构特性。如果需要找到最短路径或者处理宽度较大的图,优先选择BFS;如果需要遍历整个图或者处理深度较大的图,优先选择DFS。
示例对比
以一个更复杂的图为例,进一步对比BFS和DFS的行为。假设我们有如下无向图:
A
/|\
B C D
/| |\
E F G H
从节点 A 开始,BFS 和 DFS 的遍历顺序如下:
- BFS:
A -> B -> C -> D -> E -> F -> G -> H - DFS:
A -> B -> E -> F -> C -> D -> G -> H
BFS详解:
- 初始化队列:
[A] - 访问
A,队列变为:[B, C, D] - 访问
B,队列变为:[C, D, E, F] - 访问
C,队列变为:[D, E, F] - 访问
D,队列变为:[E, F, G, H] - 依次访问
E, F, G, H,直到队列为空。
DFS详解:
- 初始化栈:
[A] - 访问
A,栈变为:[B, C, D] - 访问
B,栈变为:[E, F, C, D] - 访问
E,栈变为:[F, C, D] - 访问
F,栈变为:[C, D] - 访问
C,栈变为:[D] - 访问
D,栈变为:[G, H] - 依次访问
G, H,直到栈为空。
这两个算法在遍历顺序上的不同反映了它们各自的特性和适用场景。
954

被折叠的 条评论
为什么被折叠?



