【算法图解】DFS:一条路走到黑
DFS(深度优先搜索)像走迷宫:先走到底,走不通再回头。本文讲清递归/显式栈实现、visited、回溯关系、复杂度、典型题型(岛屿数量、路径搜索)与工程注意点。
16 分钟阅读
小明
【算法图解】DFS:一条路走到黑
DFS(Depth-First Search,深度优先搜索)像走迷宫:
- 先沿着一条路走到底
- 走不通就回退(backtrack)
- 换一条路继续
DFS 是很多题的基础骨架:
- 图遍历
- 连通块/岛屿数量
- 路径搜索
- 拓扑排序(配合颜色标记)
1. DFS 的两种写法:递归与显式栈
1.1 递归 DFS(最直观)
function dfs(graph, start) {
const visited = new Set()
const order = []
function visit(node) {
visited.add(node)
order.push(node)
for (const next of graph[node] ?? []) {
if (!visited.has(next)) visit(next)
}
}
visit(start)
return order
}
优点:代码短、表达清晰。
风险:递归深度过大可能爆栈。
1.2 显式栈 DFS(更工程)
function dfsIter(graph, start) {
const visited = new Set([start])
const stack = [start]
const order = []
while (stack.length) {
const node = stack.pop()
order.push(node)
const neighbors = graph[node] ?? []
for (let i = neighbors.length - 1; i >= 0; i--) {
const next = neighbors[i]
if (!visited.has(next)) {
visited.add(next)
stack.push(next)
}
}
}
return order
}
这里逆序压栈是为了让遍历顺序更接近递归版(可选)。
2. visited 的必要性:图里没有它会无限走
DFS 如果不标记 visited:
- 在图中遇到环会无限递归/无限循环
所以图 DFS 基本都要 visited。
3. DFS 与回溯的关系
很多人把 DFS 和回溯混在一起。
你可以这样理解:
- DFS 是一种遍历策略(先深后广)
- 回溯是在 DFS 过程中“试错 + 撤销选择”的技巧
当你需要在递归返回时撤销状态(例如路径、选择集),那就是回溯题。
4. 复杂度
- 时间:$O(V+E)$
- 空间:$O(V)$(visited + 栈/递归栈)
递归版空间上要注意:
- 递归深度在最坏情况下可以到 $O(V)$。
5. 典型题型
5.1 岛屿数量(二维网格 DFS)
把网格看成图:
- 每个格子是节点
- 上下左右是边
DFS 遇到陆地就淹掉(标记 visited),计数连通块。
5.2 路径搜索
- 找一条可行路径
- 或枚举所有路径
后者通常需要回溯(撤销路径)。
6. 面试回答模板
- DFS 深度优先,沿一条路走到底再回退。
- 可以用递归或显式栈实现。
- 图遍历需要 visited 防止环。
- 复杂度 $O(V+E)$。
总结
- DFS 是图遍历的基础。
- 递归直观,但要注意栈深。
- 回溯是 DFS 的“试错与撤销”增强版。