@TOC
作为一名程序员,你是否遇到过需要在复杂的图结构中寻找路径、检测环,或者进行树遍历的问题?深度优先搜索(Depth-First Search, DFS)作为一种经典的图遍历算法,能够轻松应对这些场景。在 CSDN 社区中,技术文章的受欢迎程度往往取决于内容的实用性、代码的可读性以及图文结合的讲解方式。因此,本文将为你带来一篇深入浅出、图文并茂、代码详尽的 DFS 指南,涵盖原理、Java 实现、应用场景和实战示例,确保你不仅理解 DFS,还能立刻上手应用!
深度优先搜索(DFS)是一种用于遍历或搜索树或图的算法。它的核心思想是:从一个起始节点开始,沿着一条路径尽可能深入地探索,直到无法继续前进时,再回溯到上一个分叉点,尝试其他路径。用一句话概括:DFS 是一种“先走到尽头再回头”的搜索策略。
与广度优先搜索(BFS)“层层扩展”的方式不同,DFS 更像是一个勇敢的探险家,优先深入某一条路,直到碰壁才返回。这种特性使得 DFS 在某些问题(如路径查找、环检测)中特别高效。
为了让你直观理解 DFS 的执行过程,我们以一个简单的图为例:
图结构:
0
/ \
1---2
|
3
访问顺序:0 -> 1 -> 2 -> 3
下图展示了 DFS 的过程(灰色表示已访问):
初始状态 访问 0 访问 1 访问 2 访问 3
0 0* 0* 0* 0*
/ \ / \ / \ / \ / \
1---2 1---2 1*--2 1*--2* 1*--2*
| | | | |
3 3 3 3* 3*
这种“一条路走到黑”的方式,正是 DFS 的精髓。
DFS 在实际开发中用途广泛,以下是几个典型场景:
在 Java 中,DFS 通常通过递归或显式栈实现。这里我们以邻接表表示图,并用递归方式实现 DFS,因为它代码简洁且符合直觉。
以下是一个基于邻接表的 DFS 实现,包含详细注释:
import java.util.*;
public class DFSGraph {
private int V; // 图的节点数
private LinkedList<Integer>[] adj; // 邻接表表示图
// 构造函数,初始化图
public DFSGraph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; i++) {
adj[i] = new LinkedList<>(); // 为每个节点初始化邻接表
}
}
// 添加边(无向图)
public void addEdge(int v, int w) {
adj[v].add(w); // v -> w
adj[w].add(v); // w -> v(无向图需添加双向边)
}
// DFS 核心递归方法
private void dfsUtil(int v, boolean[] visited) {
visited[v] = true; // 标记当前节点为已访问
System.out.print(v + " "); // 访问节点(这里打印)
// 遍历当前节点的所有邻接节点
for (int neighbor : adj[v]) {
if (!visited[neighbor]) { // 如果邻接节点未被访问
dfsUtil(neighbor, visited); // 递归访问
}
}
}
// DFS 入口方法
public void DFS(int start) {
boolean[] visited = new boolean[V]; // 记录访问状态
dfsUtil(start, visited); // 从起始节点开始 DFS
}
// 测试代码
public static void main(String[] args) {
DFSGraph graph = new DFSGraph(5); // 创建一个 5 个节点的图
// 添加边
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 4);
graph.addEdge(3, 4);
System.out.println("从节点 0 开始的 DFS 遍历:");
graph.DFS(0);
}
}
从节点 0 开始的 DFS 遍历:
0 1 3 4 2
LinkedList[] adj
作为邻接表,adj[i]
存储节点 i
的所有邻接节点。V
表示节点总数。addEdge
方法为无向图添加双向边。dfsUtil
是递归核心,标记并访问当前节点,然后递归处理未访问的邻接节点。DFS
方法初始化 visited
数组并启动遍历。visited
数组的空间。让我们通过一个迷宫问题展示 DFS 的应用。假设有一个 4x4 的迷宫,0 表示通路,1 表示墙,目标是从 (0,0) 到 (3,3) 找一条路径。
0 1 0 0
0 1 0 1
0 0 0 0
1 1 0 0
public class MazeSolver {
static int[][] maze = {
{0, 1, 0, 0},
{0, 1, 0, 1},
{0, 0, 0, 0},
{1, 1, 0, 0}
};
static int N = 4;
static int[][] path = new int[N][N]; // 记录路径
// 四个方向:上、右、下、左
static int[] dx = {-1, 0, 1, 0};
static int[] dy = {0, 1, 0, -1};
public static boolean solveMaze(int x, int y) {
// 到达终点 (3,3)
if (x == N - 1 && y == N - 1) {
path[x][y] = 1;
return true;
}
// 检查当前位置是否合法
if (isSafe(x, y)) {
path[x][y] = 1; // 标记为路径的一部分
// 尝试四个方向
for (int i = 0; i < 4; i++) {
int nextX = x + dx[i];
int nextY = y + dy[i];
if (solveMaze(nextX, nextY)) {
return true;
}
}
// 回溯:如果当前路径不通,撤销标记
path[x][y] = 0;
}
return false;
}
// 检查坐标是否有效
public static boolean isSafe(int x, int y) {
return x >= 0 && x < N && y >= 0 && y < N && maze[x][y] == 0;
}
public static void main(String[] args) {
if (solveMaze(0, 0)) {
System.out.println("找到路径:");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
System.out.print(path[i][j] + " ");
}
System.out.println();
}
} else {
System.out.println("无解");
}
}
}
找到路径:
1 0 0 0
1 0 0 0
1 1 1 1
0 0 0 1
path
数组标记走过的位置,成功到达 (3,3) 时返回路径。通过这篇文章,你应该已经掌握了 DFS 的原理、Java 实现以及实战应用。无论是图遍历还是迷宫求解,DFS 都展现了其简洁而强大的能力。为了加深理解,不妨试试以下问题:
如何用 DFS 检测图中的环?
如果用栈而非递归实现 DFS,会是什么样?
欢迎在评论区分享你的代码或疑问,一起探讨 DFS 的更多玩法!如果觉得这篇博客对你有帮助,记得点赞和收藏哦!