Menu
CoddyTech

深度优先搜索(DFS)

最后更新

深度优先搜索在探索图时,会沿着每条分支尽可能深入,然后再回溯。它从源节点出发,访问一个邻居,再访问这个邻居的邻居,依此类推,直到走进死胡同(一个没有未访问邻居的节点),这时它回溯并尝试下一条分支。点击上方的“播放”,观察它如何沿一条路径深入到底,再退回来。

DFS 天然适合用栈来表达:可以是显式的栈(如这里的动画),也可以是递归时的调用栈。它对每个节点和每条边各访问一次,所以对一个有 V 个顶点、E 条边的图,运行时间是 O(V + E)。

时间和空间复杂度

指标复杂度说明
时间O(V + E)每个顶点和每条边各访问一次
空间O(V)栈加已访问集合,最坏情况包含所有节点
遍历方式深度优先先把一条分支走到底,再走其他分支
数据结构栈(或递归)后进先出的顺序决定了深度优先的行为

分步说明

步骤发生了什么
1把源节点压入栈。
2弹出一个节点;如果它已被访问,就跳过。
3把它标记为已访问,并记录到达它的那条边。
4把它所有未访问的邻居压入栈。
5重复,直到栈为空。

示例演算

在图 A → [B, C]、B → [D]、C → [E] 上从 A 开始迭代式 DFS,邻居按列出的顺序入栈(后进先出的栈会先弹出最后压入的元素):

步骤栈(右侧为栈顶)已访问操作
1[A]{}压入源节点 A。
2[B, C]{A}弹出 A,标记为已访问,依次压入邻居 B 和 C。
3[B, E]{A, C}弹出 C,标记为已访问,压入邻居 E。
4[B]{A, C, E}弹出 E,标记为已访问,它没有邻居。
5[D]{A, C, E, B}弹出 B,标记为已访问,压入邻居 D。
6[]{A, C, E, B, D}弹出 D,标记为已访问,栈已空,结束。

什么时候使用 DFS

适合使用的情况不适合使用的情况
需要做环检测、拓扑排序,或者找连通分量。需要在无权图中找最短路径,BFS 能做到,DFS 不能。
图又宽又浅,栈占用的内存远少于存放大量待访问节点的队列。图非常深并且用了递归,调用栈可能会溢出。
想穷举所有路径,比如走迷宫或回溯搜索。需要按离源节点的距离顺序发现节点。
要检查可达性,或者判断两个节点之间是否存在路径。必须保证找到的路径边数最少。

Depth-First Search 代码

简洁、可运行的 Depth-First Search 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。

Depth-First Search 的 Python 代码

Python
1def dfs(graph, node, visited):2    visited.add(node)3    order = [node]4    for neighbor in graph[node]:5        if neighbor not in visited:6            order.extend(dfs(graph, neighbor, visited))7    return order8
9
10graph = {11    "A": ["B", "C"],12    "B": ["D", "E"],13    "C": ["F"],14    "D": [],15    "E": ["F"],16    "F": [],17}18
19order = dfs(graph, "A", set())20print("DFS order:", " -> ".join(order))
在 Python 在线编辑器中运行这段代码

深度优先搜索常见问题

DFS 的时间复杂度是多少?
DFS 的运行时间是 O(V + E),其中 V 是顶点数,E 是边数,因为它对每个顶点访问一次,对每条边查看一次。栈和已访问集合占用 O(V) 的空间。
DFS 和 BFS 有什么区别?
DFS 使用栈,先沿一条分支深入到底再回溯;而 BFS 使用队列,逐层探索,最近的节点优先。BFS 能在无权图中找到最短路径,DFS 不能,但 DFS 在宽图上占用的内存更少,也天然适合环检测和拓扑排序这类任务。
DFS 是递归的还是迭代的?

两种都可以。递归版本隐式地使用程序的调用栈,写起来非常简洁;迭代版本使用显式的栈(如这里的动画),在大图上可以避免深层递归导致的栈溢出。

什么时候该用 DFS 而不是 BFS?

当你需要探索完整的路径时用 DFS:环检测、拓扑排序、连通分量,或者走迷宫这类回溯搜索。它在宽图上占用的内存也更少,因为栈一次只保存一条分支上的待访问节点。如果需要无权图中的最短路径,或者需要按离源节点的距离顺序得到节点,就改用 BFS。

DFS 能找到最短路径吗?
不可靠。DFS 返回它碰巧找到的第一条路径,而这往往不是边数最少的那条,因为它一心向深处走,而不是均匀地向外扩展。无权图的最短路径请用 BFS,带权图请用 Dijkstra 算法。
为什么 DFS 需要已访问集合?
没有已访问集合,DFS 会重复访问可以通过多条路径到达的节点,在有环的图中还会陷入无限循环。在弹出节点时标记已访问(并跳过已访问的节点),能保证每个节点只处理一次,运行时间保持在 O(V + E)。一个常见的错误是只在弹出时标记已访问,却仍然压入重复节点:这样结果是对的,但栈里会留下过时的条目,必须跳过。
Coddy 编程语言插图

在 Coddy 掌握算法

开始