深度优先搜索(DFS)和广度优先搜索(BFS)是图和树的两种重要遍历算法。
DFS是什么?
深度优先搜索是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。
Java 代码示例
import java.util.ArrayList;
import java.util.List;
// 图的节点类
class Node {
int val;
List<Node> neighbors;
public Node(int val) {
this.val = val;
this.neighbors = new ArrayList<>();
}
}
public class DFS {
// 标记节点是否被访问过
private boolean[] visited;
public void dfs(Node node) {
int n = 100; // 假设节点数量不超过 100
visited = new boolean[n];
dfsHelper(node);
}
private void dfsHelper(Node node) {
if (node == null || visited[node.val]) {
return;
}
// 标记当前节点为已访问
visited[node.val] = true;
System.out.print(node.val + " ");
// 递归访问相邻节点
for (Node neighbor : node.neighbors) {
dfsHelper(neighbor);
}
}
public static void main(String[] args) {
// 创建图
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node1.neighbors.add(node2);
node1.neighbors.add(node3);
node2.neighbors.add(node4);
node3.neighbors.add(node4);
DFS dfs = new DFS();
dfs.dfs(node1);
}
}
BFS是什么?
广度优先搜索是一种用于遍历或搜索树或图的算法。它从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。广度优先搜索使用队列来实现,先将根节点入队,然后循环取出队列中的节点,将其未访问的相邻节点入队,直到队列为空。
Java 代码示例
import java.util.*;
// 图的节点类
class Node {
int val;
List<Node> neighbors;
public Node(int val) {
this.val = val;
this.neighbors = new ArrayList<>();
}
}
public class BFS {
public void bfs(Node node) {
if (node == null) {
return;
}
// 标记节点是否被访问过
boolean[] visited = new boolean[100]; // 假设节点数量不超过 100
// 使用队列来实现 BFS
Queue<Node> queue = new LinkedList<>();
queue.offer(node);
visited[node.val] = true;
while (!queue.isEmpty()) {
Node current = queue.poll();
System.out.print(current.val + " ");
// 访问相邻节点
for (Node neighbor : current.neighbors) {
if (!visited[neighbor.val]) {
queue.offer(neighbor);
visited[neighbor.val] = true;
}
}
}
}
public static void main(String[] args) {
// 创建图
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node1.neighbors.add(node2);
node1.neighbors.add(node3);
node2.neighbors.add(node4);
node3.neighbors.add(node4);
BFS bfs = new BFS();
bfs.bfs(node1);
}
}
复杂度分析
- 时间复杂度:DFS 和 BFS 的时间复杂度均为 O(V+E),其中 V 是节点数,E 是边数。
- 空间复杂度:DFS 的空间复杂度为 O(V),主要是递归调用栈的空间;BFS 的空间复杂度也为 O(V),主要是队列的空间。
区别:
1.搜索策略
- DFS:沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点的所有子节点都被访问过后,搜索将回溯到上一个节点,继续探索其他未被访问的分支。可以理解为 “一条路走到黑”,直到无法继续才回头。
- BFS:从根节点开始,沿着树的宽度逐层遍历节点。即先访问离根节点最近的所有节点,再依次访问距离根节点更远的层次。可以看作是像水波纹一样逐层扩散的搜索方式。
2.数据结构
- DFS:通常使用递归或栈(Stack)来实现。递归调用本身就是基于系统栈来保存函数调用的上下文信息;手动实现时可以使用栈来模拟递归过程。
- BFS:使用队列(Queue)来实现。队列的先进先出(FIFO)特性保证了节点按照层次顺序被访问。
3.空间复杂度
- DFS:空间复杂度主要取决于递归调用栈的深度或栈中存储的节点数。在最坏情况下,树的深度为 O(V)(V 是节点数),因此空间复杂度为 O(V)。对于一些非常深的树,DFS 可能会导致栈溢出。
- BFS:空间复杂度主要取决于队列中存储的节点数。在最坏情况下,队列可能需要存储某一层的所有节点,对于完全二叉树,最后一层的节点数接近 V/2,所以空间复杂度也是 O(V)。但在某些情况下,BFS 所需的空间可能比 DFS 更大,尤其是树的宽度较大时。
4.搜索结果
- DFS:更适合寻找从起点到目标节点的一条路径,因为它会沿着一条路径一直深入,可能会较快地找到一条可行路径,但不一定是最短路径。
- BFS:由于是逐层遍历,当找到目标节点时,所经过的路径一定是最短路径(在无权图中)。
适用问题
DFS 适用问题
- 路径搜索:当需要找到从起点到终点的任意一条路径时,DFS 是一个不错的选择。例如,在迷宫问题中,找到一条从起点到终点的通路。
- 连通分量:在无向图中找出所有的连通分量。通过 DFS 可以遍历一个连通分量中的所有节点,然后再对未访问的节点进行新的 DFS 遍历,直到所有节点都被访问。
- 拓扑排序:在有向无环图(DAG)中进行拓扑排序。DFS 可以用于确定节点的完成时间,从而得到拓扑排序的结果。
- 回溯算法:许多回溯算法本质上就是 DFS 的应用,如八皇后问题、数独求解等。通过递归地尝试不同的选择,当发现当前选择无法得到有效解时,回溯到上一步继续尝试其他选择。
BFS 适用问题
- 最短路径问题:在无权图中,BFS 可以保证找到从起点到目标节点的最短路径。例如,在地图导航中寻找两点之间的最少步数路径。
- 层序遍历:需要按层遍历树或图的节点时,BFS 是首选算法。比如,在二叉树的层序遍历中,使用 BFS 可以方便地按层输出节点值。
- 广度优先的状态搜索:当问题的状态空间较大,且需要快速找到满足条件的最浅层次的解时,BFS 更合适。例如,在一些游戏中,通过不断扩展当前状态来寻找最优解。