深度优先搜索(DFS):Java 实现与原理解析
深度优先搜索(Depth-First Search,简称 DFS)是一种用于遍历或搜索树或图的算法。它从起始节点出发,沿着图的一个分支一路向下探索,直到到达最深的节点(即没有未访问的邻居节点为止),然后回溯到上一个节点,继续探索其他未被访问的分支,直到所有节点都被访问过为止。
在本文中,我们将详细解析如何在 Java 中实现 DFS,并讨论相关的原理。
1. 深度优先搜索的基本原理
DFS 算法的核心思想是从图的一个节点出发,尽可能深入探索图的分支。当遇到死胡同时,回溯到最近的分支,继续搜索其他分支,直到所有节点都被遍历一遍。
我们可以使用递归或者显式栈来实现 DFS。在这篇文章中,我们将使用递归方式来实现 DFS。
2. 程序实现
下面是一个简单的 Java 实现,展示了如何用 DFS 对图进行遍历。假设图是由邻接矩阵表示的,这意味着每个节点通过一个二维数组来存储与其他节点的连接关系。
2.1 图的表示与输入
首先,我们定义图的大小(最大顶点数为 20)并接收边的输入。图的数据结构是一个二维数组 graph
,其中 graph[i][j] = 1
表示节点 i
与节点 j
有一条边,graph[i][j] = 0
表示没有边。
public static void main(String[] args) throws Exception {
int n = nextInt(); // 顶点数
graph = new int[n + 1][n + 1];
int m = nextInt(); // 边数
for (int i = 0; i < m; i++) {
int s = nextInt();
int e = nextInt();
graph[s][e] = 1;
}
}
在输入阶段,我们通过 nextInt()
方法接收数据。首先读取顶点数量 n
,然后读取边的数量 m
。对于每条边(由两个节点 s
和 e
定义),在邻接矩阵中更新对应的位置。
2.2 深度优先搜索的实现
DFS 的实现部分主要通过一个递归函数来完成。我们将维护一个 visited
数组来记录哪些节点已经被访问过,以防止重复遍历。
public static List<Integer> dfsByadList(int start) {
List<Integer> ans = new ArrayList<>();
boolean[] visited = new boolean[MAXN];
dfs(start, visited, ans);
return ans;
}
public static void dfs(int cur, boolean[] visited, List<Integer> ans) {
visited[cur] = true; // 标记当前节点为已访问
ans.add(cur); // 将当前节点加入结果列表
for (int i = 0; i < graph.length; i++) {
if (graph[cur][i] == 1 && !visited[i]) { // 如果存在边且节点未访问
dfs(i, visited, ans); // 递归访问该节点
}
}
}
2.3 递归过程解析
-
开始访问 :首先从指定的起始节点
start
开始调用dfs()
函数。每次递归调用时,都会将当前节点标记为已访问,并将其添加到结果列表ans
中。 -
递归探索邻居节点 :然后,程序遍历当前节点的所有邻居(即从
graph[cur]
的每一列查找是否有边)。如果存在边且该邻居节点尚未被访问,就递归调用dfs()
,进一步深入探索。 -
回溯:当某个节点的所有邻居都已经访问过时,递归会回溯到上一个节点,继续寻找下一个未访问的邻居。
-
结束遍历 :当所有节点都被访问过后,递归最终会结束,所有节点都将被依次加入到
ans
列表中,代表 DFS 遍历的顺序。
2.4 输出结果
DFS 完成后,我们将遍历的节点顺序打印出来:
for (int num : ans) {
out.print(num + " ");
}
out.println();
out.flush();
out.close();
br.close();
这个过程会打印出从起始节点开始的 DFS 遍历结果。
3. 深度优先搜索的时间复杂度
DFS 的时间复杂度为 O(V + E) ,其中 V
是图中节点的数量,E
是边的数量。每个节点和每条边最多会被访问一次,因此时间复杂度为 O(V + E)。
- V:每个节点最多被访问一次。
- E:每条边最多被访问一次。
4. 总结
DFS 是一种经典的图遍历算法,适用于搜索图中的路径、连通性检测等问题。我们在本文中通过 Java 代码展示了如何实现一个深度优先搜索,并解释了 DFS 的工作原理与流程。通过递归方式实现,DFS 对于图的搜索非常高效,尤其在需要深入到图的某一分支时尤为有用。
以下是完整的 Java 代码,包括深度优先搜索(DFS)算法的实现和图的输入输出处理:
import java.io.*;
import java.util.*;
public class Main {
public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static StreamTokenizer in = new StreamTokenizer(br);
public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static int MAXN = 20; // 最大节点数
static int[][] graph;
public static void main(String[] args) throws Exception {
// 读取顶点数
int n = nextInt();
graph = new int[n + 1][n + 1]; // 图的邻接矩阵
// 读取边数
int m = nextInt();
// 构建图的邻接矩阵
for (int i = 0; i < m; i++) {
int s = nextInt(); // 起点
int e = nextInt(); // 终点
graph[s][e] = 1; // 设置邻接矩阵
}
// 输出邻接矩阵
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
out.print(graph[i][j] + " ");
}
out.println();
}
// 从节点1开始DFS遍历
List<Integer> ans = dfsByadList(1);
// 输出DFS遍历结果
for (int num : ans) {
out.print(num + " ");
}
out.println();
// 刷新并关闭流
out.flush();
out.close();
br.close();
}
// 使用DFS遍历图,返回访问的节点顺序
public static List<Integer> dfsByadList(int start) {
List<Integer> ans = new ArrayList<>(); // 存储遍历结果
boolean[] visited = new boolean[MAXN]; // 记录节点是否被访问
dfs(start, visited, ans); // 从起点开始递归DFS
return ans;
}
// DFS递归函数
public static void dfs(int cur, boolean[] visited, List<Integer> ans) {
visited[cur] = true; // 标记当前节点已访问
ans.add(cur); // 将当前节点添加到结果列表中
for (int i = 0; i < graph.length; i++) {
// 如果当前节点和i节点之间有边,并且i节点未被访问过
if (graph[cur][i] == 1 && !visited[i]) {
dfs(i, visited, ans); // 递归访问邻居节点i
}
}
}
// 获取下一个整数输入
public static int nextInt() throws Exception {
in.nextToken();
return (int) in.nval;
}
}