算法精讲——树(一):DFS 的奇妙探险之旅

140 阅读5分钟

算法精讲——树(一):DFS 的奇妙探险之旅🌳🚀

📅 2025 年 03 月 04 日 | 作者:无限大 | 标签:#算法 #树 #DFS


一、开篇小剧场 🎭

想象你是一位在茂密森林 🌲 里探险的冒险家,每次遇到分叉路口时,你都会选择最深的路径一路走到底,直到发现宝藏 💎 或死胡同才返回——这就是我们今天要讲的深度优先搜索(DFS) 的生动写照!


二、DFS 核心原理 🔍

1. 什么是 DFS(深度优先搜索)?

DFS(Depth-First Search)就像探险家的执着精神:

  • 策略不撞南墙不回头,优先探索最深的节点
  • 实现方式:递归 / 栈(Stack)
  • 时间复杂度:O(n)(每个节点访问一次)
核心要点:DFS 是策略,不同顺序遍历是方法

2. 二叉树 DFS 的三种方式 🤸

graph TD
    A((根节点)) --> B((左子树))
    A --> C((右子树))
    classDef green fill:#9f6,stroke:#333;
    classDef yellow fill:#ff6,stroke:#333;
    classDef blue fill:#69f,stroke:#333;
    class A green
    class B yellow
    class C blue
graph TD
    A((1)) --> B((2))
    A --> C((3))
    B --> D((4))
    B --> E((5))
    C --> F((6))

    classDef visited fill:#f96,stroke:#333;
    class A,B,D,E,C,F visited;

遍历结果速查表
遍历类型顺序口诀关键特征场景应用力扣真题
前序根→左→右 1 2 4 5 3 6👑 根 → 左 → 右根节点是第一个访问的结点快速克隆树结构144.前序遍历
中序左→根→右 4 2 5 1 3 6🤔 左 → 根 → 右根节点在中间分割左右子树二叉搜索树特性94.中序遍历
后序左→右→根 4 5 2 6 3 1🎁 左 → 右 → 根根节点是最后一个访问的结点删除树节点145.后序遍历

遍历流程详解

1. 前序遍历(DLR)

访问顺序:根 → 左 → 右

步骤分解

  1. 访问根节点 1
  2. 递归遍历左子树(节点 2 的左子树 → 4
  3. 递归遍历右子树(节点 2 的右子树 → 5
  4. 递归遍历根节点的右子树(节点 36

路径图

1 → 2 → 4 → 5 → 3 → 6

2. 中序遍历(LDR)

访问顺序:左 → 根 → 右

步骤分解

  1. 递归遍历左子树(节点 2 的左子树 → 4
  2. 访问根节点 2
  3. 递归遍历右子树(节点 2 的右子树 → 5
  4. 访问根节点 1
  5. 递归遍历右子树(节点 3 的左子树为空 → 访问 3 → 访问 6

路径图

4 → 2 → 5 → 1 → 3 → 6

3. 后序遍历(LRD)

访问顺序:左 → 右 → 根

步骤分解

  1. 递归遍历左子树(节点 2 的左子树 → 4
  2. 递归遍历右子树(节点 2 的右子树 → 5
  3. 访问根节点 2
  4. 递归遍历右子树(节点 3 的右子树 → 6 → 访问 3
  5. 访问根节点 1

路径图

4 → 5 → 2 → 6 → 3 → 1

三、解题思路具体分析 🔥

1. DFS 问题识别雷达 🕵️

遇到以下特征时优先考虑 DFS:

  • 需要遍历所有可能路径
  • 问题可分解为子树问题
  • 需要回溯操作(如路径记录)
  • 求极值/存在性问题

2. 四步拆解法 💡

flowchart TD
    A[问题抽象] --> B[状态定义]
    B --> C[终止条件]
    C --> D[状态转移]
   
```js

#### 步骤详解表

| 步骤 | 操作要点                   | 示例问题              |
| ---- | -------------------------- | --------------------- |
| 1    | 将问题转化为树/图结构      | 路径总和 → 路径遍历  |
| 2    | 明确当前节点状态           | 当前路径和+剩余目标值 |
| 3    | 设置递归终止基线           | 叶子节点判断          |
| 4    | 定义向子节点的状态转移方式 | 选择左/右子树         |

---

## 四、通用解题模板 🛠️

### 递归版模板

```java
public ReturnType dfs(TreeNode node, 附加参数) {
    // 🚩1. 终止条件
    if (node == null) return baseCase;
    if (满足特定条件) return 结果值;

    // ✨2. 处理当前节点(前序位置)
    处理逻辑;

    // 🌳3. 递归子节点
    ReturnType left = dfs(node.left, 更新参数);
    ReturnType right = dfs(node.right, 更新参数);

    // 🎯4. 后序处理(可选)
    return 合并结果(left, right);
}

迭代版模板

public ReturnType dfsIterative(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        // 💡注意入栈顺序:前序->右左入栈,中序->特殊处理
        if (节点需处理) {
            处理逻辑;
        }
        // 按遍历顺序反向压栈
        if (node.right != null) stack.push(node.right);
        if (node.left != null) stack.push(node.left);
    }
    return 结果;
}
模板选择指南
场景推荐模板原因
简单路径问题递归代码简洁直观
复杂状态管理迭代避免栈溢出
需要回溯操作递归+全局变量方便状态回退
严格深度优先迭代显式控制栈操作

五、代码实战演练 ⚔️

案例1:路径总和(力扣112

// ✅递归模板的完美实践
public boolean hasPathSum(TreeNode root, int targetSum) {
    // 🚩终止条件1:空节点
    if (root == null) return false;

    // 🚩终止条件2:叶子节点
    if (root.left == null && root.right == null) {
        return targetSum == root.val; // ✨结果判断
    }

    // 🌳递归子节点(更新剩余目标值)
    return hasPathSum(root.left, targetSum - root.val)
        || hasPathSum(root.right, targetSum - root.val);
}

案例2:二叉树的中序遍历(力扣94

// ✅迭代模板的典型应用
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    TreeNode curr = root;

    // 🎮显式控制遍历流程
    while (curr != null || !stack.isEmpty()) {
        // 🌳左探针直达最深处
        while (curr != null) {
            stack.push(curr);
            curr = curr.left;
        }
        curr = stack.pop();
        res.add(curr.val); // ✨访问节点
        curr = curr.right; // ➡️转向右子树
    }
    return res;
}
图例分析

diagram.png

六、避坑指南 ⚠️

常见错误对照表

错误现象错误原因解决方案
栈溢出(StackOverflow)递归深度超过系统限制改用迭代+手动维护栈
路径结果重复未及时回溯状态添加 path.removeLast()
空指针异常未判断节点是否为null添加 if(node==null)检查
死循环图中未标记已访问节点使用 visited集合记录

七、知识宇宙扩展 🪐

DFS 变种应用表

变种类型应用场景经典题目
记忆化 DFS重叠子问题优化70.爬楼梯
双向 DFS超大搜索空间优化127.单词接龙
剪枝 DFS组合类问题优化39.组合总和
flowchart LR
    Start[开始] --> Condition{是否满足条件?}
    Condition -- 是 --> Action[处理节点]
    Condition -- 否 --> Back[回溯]
    Action --> Next[访问子节点]
    Next --> Condition

八、今日小结 📌

  • 🧭 DFS 是纵向搜索的典型代表
  • 🛠 掌握递归与迭代两种实现方式
  • 🎮 通过树类问题理解 DFS 的精髓

九、下期剧透 🔮

明日主题 :BFS 层序遍历的魔法——像水波纹一样扫描整棵树!

亮点预告

  • 🌀 队列(Queue)的妙用技巧
  • 🎯 最短路径问题的破解之道
  • 💡 双向 BFS 优化秘籍

🌟 课后作业 :用 DFS 实现二叉树的镜像翻转,把你的代码截图发到评论区吧!