深度优先搜索 (DFS) 原理与实现详解
本文详细介绍了深度优先搜索(DFS)算法的概念、步骤及两种实现方式(递归与迭代)。通过 Java 代码示例演示了图的邻接表构建与遍历逻辑,分析了回溯机制、访问标记的重要性及时间空间复杂度。内容涵盖路径查找、拓扑排序、连通性分析等应用场景,帮助读者理解图论基础与递归栈的应用。

本文详细介绍了深度优先搜索(DFS)算法的概念、步骤及两种实现方式(递归与迭代)。通过 Java 代码示例演示了图的邻接表构建与遍历逻辑,分析了回溯机制、访问标记的重要性及时间空间复杂度。内容涵盖路径查找、拓扑排序、连通性分析等应用场景,帮助读者理解图论基础与递归栈的应用。


深度优先搜索 (Depth-First Search, DFS) 是算法世界中重要的搜索策略。它像探险家在迷宫中探索,总是沿着一条路径走到尽头,才会返回尝试其他路径。本文将深入讲解 DFS 的原理、实现和应用。
深度优先搜索 (DFS) 是一种用于遍历或搜索树或图的算法。它的核心思想是:从起始节点开始,尽可能深入地探索一条路径,直到无法继续为止,然后回溯到前一个节点,继续探索其他未访问的路径。
DFS 的三个关键特点:
实现 DFS 的基本步骤如下:
重复步骤 2,直到栈为空

DFS 有两种常见的实现方式:
递归实现利用系统栈隐式地管理节点的访问顺序,代码简洁易懂:
public void dfsRecursive(Graph graph, int vertex, boolean[] visited) {
// 标记当前节点为已访问
visited[vertex] = true;
System.out.print(vertex + " ");
// 递归访问所有未访问的邻接节点
for (int neighbor : graph.getNeighbors(vertex)) {
if (!visited[neighbor]) {
dfsRecursive(graph, neighbor, visited);
}
}
}
迭代实现使用显式的栈来管理节点的访问顺序:
public void dfsIterative(Graph graph, int startVertex) {
boolean[] visited = new boolean[graph.getVertexCount()];
Stack<Integer> stack = new Stack<>();
// 将起始节点标记为已访问并入栈
visited[startVertex] = true;
stack.push(startVertex);
System.out.print(startVertex + " ");
while (!stack.isEmpty()) {
// 获取栈顶元素(但不移除)
int currentVertex = stack.peek();
// 查找未访问的邻接节点
int nextVertex = findUnvisitedNeighbor(graph, currentVertex, visited);
if (nextVertex != -1) {
// 找到未访问的邻接节点,标记为已访问并入栈
visited[nextVertex] = true;
stack.push(nextVertex);
System.out.print(nextVertex + " ");
} else {
// 没有未访问的邻接节点,回溯(弹出栈顶元素)
stack.pop();
}
}
}
private int findUnvisitedNeighbor(Graph graph, int vertex, boolean[] visited) {
List<Integer> neighbors = graph.getNeighbors(vertex);
for (int neighbor : neighbors) {
if (!visited[neighbor]) {
return neighbor;
}
}
return -1; // 没有未访问的邻接节点
}
DFS 在实际编程中有广泛的应用:
DFS 中最难理解的部分是回溯过程。当算法探索到一条路径的尽头时,需要回溯到前一个节点,继续探索其他路径。这个过程可以通过栈的操作来理解:
想象你在探索一个迷宫,每当你走到一个分岔路口,你就在地图上标记当前位置。当你走到死胡同时,你需要回到上一个有未探索路径的分岔路口继续探索。这就是回溯的过程!
在图中执行 DFS 时,如果存在环路且不正确处理已访问节点,可能会导致无限循环。因此,使用 visited 数组来标记已访问的节点是至关重要的。
例如,在一个简单的环 A→B→C→A 中,如果不标记已访问节点,算法会无限循环:A→B→C→A→B→C→…
递归实现 DFS 时,对于非常深的图或树,可能会导致栈溢出(StackOverflowError)。在这种情况下,应该考虑使用迭代实现。
迭代实现使用显式的栈,可以更好地控制内存使用,避免栈溢出问题。
让我们详细分析一下 DFS 的核心代码实现。以下是一个完整的 Java 实现示例,包含了递归和迭代两种方式:
import java.util.*;
// 图的邻接表表示
class Graph {
private int vertexCount;
private List<List<Integer>> adjacencyList;
public Graph(int vertexCount) {
this.vertexCount = vertexCount;
adjacencyList = new ArrayList<>(vertexCount);
for (int i = 0; i < vertexCount; i++) {
adjacencyList.add(new ArrayList<>());
}
}
public void addEdge(int source, int destination) {
adjacencyList.get(source).add(destination);
// 对于无向图,还需要添加反向边
// adjacencyList.get(destination).add(source);
}
public List<Integer> getNeighbors(int vertex) {
return adjacencyList.get(vertex);
}
public int getVertexCount() {
return vertexCount;
}
}
public class DFSExample {
// 递归实现 DFS
public void dfsRecursive(Graph graph, int vertex, boolean[] visited) {
// 标记当前节点为已访问
visited[vertex] = ;
System.out.print(vertex + );
( neighbor : graph.getNeighbors(vertex)) {
(!visited[neighbor]) {
dfsRecursive(graph, neighbor, visited);
}
}
}
{
[] visited = [graph.getVertexCount()];
Stack<Integer> stack = <>();
visited[startVertex] = ;
stack.push(startVertex);
System.out.print(startVertex + );
(!stack.isEmpty()) {
stack.peek();
findUnvisitedNeighbor(graph, currentVertex, visited);
(nextVertex != -) {
visited[nextVertex] = ;
stack.push(nextVertex);
System.out.print(nextVertex + );
} {
stack.pop();
}
}
}
{
List<Integer> neighbors = graph.getNeighbors(vertex);
( neighbor : neighbors) {
(!visited[neighbor]) {
neighbor;
}
}
-;
}
{
();
graph.addEdge(, );
graph.addEdge(, );
graph.addEdge(, );
graph.addEdge(, );
graph.addEdge(, );
graph.addEdge(, );
();
System.out.println();
[] visited = [graph.getVertexCount()];
dfsExample.dfsRecursive(graph, , visited);
System.out.println();
dfsExample.dfsIterative(graph, );
}
}
以示例中的图为例,从节点 A(0) 开始的 DFS 遍历过程如下:
最终的遍历顺序是:A B D E C F G
学习 DFS 有助于培养解决复杂问题的算法思维。通过理解 DFS 的工作原理,你将学会如何将大问题分解为小问题,并通过回溯策略找到解决方案。这种思维方式对于解决各种编程挑战非常有价值!
DFS 是递归和栈的经典应用场景。通过学习 DFS,你可以深入理解递归的工作原理,以及如何使用栈来模拟递归过程。这些知识对于理解其他高级算法和数据结构至关重要。
DFS 是图论中最基本的算法之一。掌握 DFS 后,你将更容易理解其他图算法,如广度优先搜索 (BFS)、最短路径算法、最小生成树等。这为学习更复杂的图论知识奠定了基础。
实现 DFS 需要良好的代码组织能力,特别是在处理复杂的图结构和状态管理时。这有助于提高你的代码质量和可维护性。
DFS 在实际编程中有广泛的应用,从游戏开发到网络分析。掌握 DFS 后,你将能够解决许多实际问题,如迷宫寻路、网页爬虫、社交网络分析等。
本文深入探讨了深度优先搜索 (DFS) 这个强大的算法工具。我们学习了 DFS 的基本概念、实现方式、应用场景以及一些重要的注意事项。
DFS 就像是一位勇敢的探险家,总是选择一条路径走到尽头,然后回头尝试其他路径。这种'一条路走到黑'的策略在解决许多问题时非常有效,特别是在需要找到所有可能解或检查图的连通性时。
记住,DFS 有两种实现方式:递归和迭代。递归实现简洁优雅,但可能面临栈溢出的风险;迭代实现使用显式栈,更加灵活和安全。根据具体问题和约束条件,选择合适的实现方式非常重要。
在实际应用中,DFS 可以帮助我们解决各种问题,从简单的图遍历到复杂的路径查找、拓扑排序和连通性分析。掌握 DFS 不仅能提高你的算法能力,还能培养解决复杂问题的思维方式。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online