深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法从根节点开始,沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。
DFS由Charles Pierre Trémaux在19世纪设计,是图论中经典的基础算法之一。
在图或树数据结构中系统地访问所有节点,确保每个节点恰好被访问一次。
基于图论和递归的数学概念。
从起始节点开始,沿着一条路径尽可能深入地访问节点,直到不能再深入为止,然后回溯到上一个节点,继续探索其他未访问的路径,直到所有节点都被访问过。
- 从起始节点开始,标记该节点为已访问;
- 对于当前节点的每个未访问过的邻接节点,递归地执行深度优先搜索;
- 如果当前节点没有未访问的邻接节点,则回溯到上一个节点;
- 重复步骤2-3,直到所有节点都被访问。
算法 DFS(graph, startVertex)
输入:图graph,起始顶点startVertex
输出:节点的访问顺序
算法 DFS_Recursive(vertex)
标记vertex为已访问
访问vertex
对于vertex的每个邻接节点neighbor
如果neighbor未被访问
DFS_Recursive(neighbor)
算法 DFS_Iterative(graph, startVertex)
创建一个栈stack
标记startVertex为已访问
将startVertex压入stack
while stack不为空
vertex = 从stack弹出元素
访问vertex
对于vertex的每个邻接节点neighbor
如果neighbor未被访问
标记neighbor为已访问
将neighbor压入stack
# 递归实现
def dfs_recursive(graph, vertex, visited=None):
if visited is None:
visited = set()
visited.add(vertex)
print(vertex, end=' ')
for neighbor in graph[vertex]:
if neighbor not in visited:
dfs_recursive(graph, neighbor, visited)
return visited
# 迭代实现
def dfs_iterative(graph, start):
visited = set()
stack = [start]
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
print(vertex, end=' ')
# 将邻接节点加入栈中(逆序添加以保证访问顺序)
for neighbor in reversed(graph[vertex]):
if neighbor not in visited:
stack.append(neighbor)
return visitedimport java.util.*;
// 递归实现
public static void dfsRecursive(Map<Integer, List<Integer>> graph, int vertex, Set<Integer> visited) {
visited.add(vertex);
System.out.print(vertex + " ");
for (int neighbor : graph.getOrDefault(vertex, new ArrayList<>())) {
if (!visited.contains(neighbor)) {
dfsRecursive(graph, neighbor, visited);
}
}
}
// 迭代实现
public static void dfsIterative(Map<Integer, List<Integer>> graph, int start) {
Set<Integer> visited = new HashSet<>();
Stack<Integer> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
int vertex = stack.pop();
if (!visited.contains(vertex)) {
visited.add(vertex);
System.out.print(vertex + " ");
// 将邻接节点加入栈中(逆序添加以保证访问顺序)
List<Integer> neighbors = graph.getOrDefault(vertex, new ArrayList<>());
for (int i = neighbors.size() - 1; i >= 0; i--) {
int neighbor = neighbors.get(i);
if (!visited.contains(neighbor)) {
stack.push(neighbor);
}
}
}
}
}- 邻接表表示:O(V + E),其中V是顶点数,E是边数
- 邻接矩阵表示:O(V²)
- 递归版本:O(V) - 递归调用栈的深度
- 迭代版本:O(V) - 显式栈的空间
在邻接表表示中,每个顶点和每条边都只会被访问常数次,因此时间复杂度为O(V + E)。
图的邻接表表示:
0: [1, 2]
1: [0, 3, 4]
2: [0]
3: [1]
4: [1]
从节点0开始DFS遍历,可能的访问顺序:0 -> 1 -> 3 -> 4 -> 2
- 处理大型稀疏图
- 处理有环图
- 在树结构中的应用
- 空图
- 只有一个节点的图
- 不连通的图
- 有向图和无向图
可以通过动画演示DFS如何沿着一条路径深入,遇到死胡同时如何回溯,以及如何访问所有可达节点。
- 内存需求相对较低(相对于BFS)
- 可以用于检测图中的环
- 可以用于拓扑排序
- 可以用于寻找强连通分量
- 实现相对简单
- 可能会陷入很深的路径,导致栈溢出
- 不一定能找到最短路径
- 对于宽度很大的图,可能效率不高
相比于广度优先搜索(BFS),DFS使用较少的内存,但BFS能找到最短路径。
- 检测图中的环
- 拓扑排序
- 寻找强连通分量
- 解决迷宫问题
- 路径查找问题
- 网络爬虫
- 图论算法
- 网络分析
- 人工智能中的状态空间搜索
- 编译器设计中的数据流分析
- 分析DFS和BFS在不同图结构下的性能差异。
- 证明DFS可以用于检测无向图中的环。
- 实现DFS算法,处理各种边界条件。
- 使用DFS检测有向图中是否存在环。
- 实现使用DFS进行拓扑排序。
设计一个简单的网络爬虫,使用DFS策略遍历网站链接,并提取相关信息。
- 图遍历算法的优化研究
- 在并行计算中的应用
- 《算法导论》中的图算法章节
- 《图论算法》相关书籍
- Tarjan算法相关资料
- 广度优先搜索(BFS)
- Dijkstra算法
- A*搜索算法
- Tarjan强连通分量算法