DFS
DFS
深度优先搜索(DFS,Depth-First Search)是一种遍历或搜索树/图结构的算法,其核心思想是“一条路走到底”,通过递归或栈实现。DFS 在解决 组合问题、排列问题、路径问题、连通性问题 时非常高效。以下是 DFS 的通用模板、回溯、剪枝、记忆化搜索的详细解析。
一、DFS 通用模板
递归实现模板
public void dfs(参数列表) {
// 终止条件
if (终止条件满足) {
记录结果或返回;
return;
}
// 遍历所有可能的子节点或选择
for (所有可能的子节点或选择) {
if (子节点或选择合法) {
标记当前选择(如设置访问状态);
dfs(新的参数); // 递归深入下一层
撤销当前选择(回溯); // 恢复状态
}
}
}
栈实现模板(非递归)
public void dfsWithStack(起始节点) {
Stack<节点类型> stack = new Stack<>();
stack.push(起始节点);
Set<节点类型> visited = new HashSet<>(); // 记录已访问节点
while (!stack.isEmpty()) {
节点类型 node = stack.pop();
if (node 是目标节点) {
处理结果;
continue;
}
// 遍历相邻节点
for (节点类型 neighbor : node的相邻节点列表) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
stack.push(neighbor);
}
}
}
}
二、回溯(Backtracking)
回溯是 DFS 的一种应用形式,核心是 尝试所有可能的选择,并在发现当前路径无法得到解时回退(撤销选择)。
经典问题:全排列
public class Permutations {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(nums, new ArrayList<>(), res);
return res;
}
private void backtrack(int[] nums, List<Integer> path, List<List<Integer>> res) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path)); // 找到一种排列
return;
}
for (int num : nums) {
if (path.contains(num)) continue; // 跳过已选元素
path.add(num); // 选择当前元素
backtrack(nums, path, res);
path.remove(path.size() - 1); // 撤销选择(回溯)
}
}
}
关键点:通过 path.add() 和 path.remove() 实现选择与撤销。
三、剪枝(Pruning)
剪枝是通过 提前终止无效递归分支 来优化 DFS 效率的技术。
示例:组合总和(剪枝优化)
public class CombinationSum {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates); // 排序便于剪枝
List<List<Integer>> res = new ArrayList<>();
backtrack(candidates, target, 0, new ArrayList<>(), res);
return res;
}
private void backtrack(int[] nums, int remain, int start, List<Integer> path, List<List<Integer>> res) {
if (remain < 0) return; // 剪枝:剩余值小于0,无需继续
if (remain == 0) {
res.add(new ArrayList<>(path));
return;
}
for (int i = start; i < nums.length; i++) {
if (nums[i] > remain) break; // 剪枝:排序后后续元素都大于remain
path.add(nums[i]);
backtrack(nums, remain - nums[i], i, path, res); // 允许重复选择
path.remove(path.size() - 1);
}
}
}
剪枝点:
- 排序后提前终止循环(
nums[i] > remain)。 - 递归前检查
remain < 0。
四、记忆化搜索(Memoization)
记忆化搜索通过 缓存中间计算结果 避免重复递归,常用于优化重复子问题。
示例:斐波那契数列
public class Fibonacci {
private Map<Integer, Integer> memo = new HashMap<>();
public int fib(int n) {
if (n <= 1) return n;
if (memo.containsKey(n)) {
return memo.get(n); // 直接返回缓存结果
}
int res = fib(n - 1) + fib(n - 2);
memo.put(n, res); // 缓存结果
return res;
}
}
示例:矩阵中的最长递增路径(DFS + 记忆化)
public class LongestIncreasingPath {
private int[][] dirs = {{0,1}, {0,-1}, {1,0}, {-1,0}};
private int[][] memo; // 记忆化缓存
public int longestIncreasingPath(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
memo = new int[m][n];
int maxLen = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
maxLen = Math.max(maxLen, dfs(matrix, i, j));
}
}
return maxLen;
}
private int dfs(int[][] matrix, int i, int j) {
if (memo[i][j] != 0) return memo[i][j]; // 命中缓存
int max = 1;
for (int[] dir : dirs) {
int x = i + dir[0], y = j + dir[1];
if (x < 0 || y < 0 || x >= matrix.length || y >= matrix[0].length) continue;
if (matrix[x][y] <= matrix[i][j]) continue;
max = Math.max(max, 1 + dfs(matrix, x, y));
}
memo[i][j] = max; // 缓存结果
return max;
}
}
五、DFS 的典型应用场景
| 问题类型 | 示例 | 关键技术 |
|---|---|---|
| 组合/排列问题 | 全排列、子集、组合总和 | 回溯、剪枝 |
| 路径问题 | 迷宫问题、矩阵中的最长递增路径 | 记忆化搜索 |
| 连通性问题 | 岛屿数量、图的连通分量 | 标记访问状态 |
| 决策问题 | 八皇后、数独 | 回溯、剪枝 |
六、总结
| 技术 | 核心思想 | 适用场景 |
|---|---|---|
| 通用模板 | 递归或栈实现,遍历所有可能路径 | 所有 DFS 问题 |
| 回溯 | 撤销选择,尝试其他可能性 | 组合、排列、决策问题 |
| 剪枝 | 提前终止无效递归分支 | 优化时间复杂度 |
| 记忆化搜索 | 缓存中间结果,避免重复计算 | 存在重复子问题(如动态规划) |
实战建议:
- 明确递归终止条件:避免无限递归。
- 合理设计状态标记与恢复:确保回溯正确性。
- 优先考虑剪枝:显著提升算法效率。
- 记忆化搜索与动态规划结合:解决复杂重复子问题。

浙公网安备 33010602011771号