diff --git a/docs/algorithm/binary-tree-traversal.md b/docs/algorithm/binary-tree-traversal.md new file mode 100644 index 0000000..df46d41 --- /dev/null +++ b/docs/algorithm/binary-tree-traversal.md @@ -0,0 +1,246 @@ +# 二叉树的遍历 + +!!! note "💡 一句话概述" + 二叉树遍历是按照特定规则走访每个节点恰好一次,核心分为深度优先(前/中/后序)和广度优先(层序)两大类。 + +--- + +## 🔑 核心概念 + +1. **前序遍历(Pre-order)**:根 → 左 → 右,常用于序列化/复制树结构 +2. **中序遍历(In-order)**:左 → 根 → 右,BST 中序即有序序列 +3. **后序遍历(Post-order)**:左 → 右 → 根,常用于释放资源、计算目录大小 +4. **层序遍历(Level-order / BFS)**:逐层从左到右,借助队列实现 + +--- + +## 📝 详细说明 + +### 遍历方式对比 + +| 遍历方式 | 访问顺序 | 典型应用 | 实现方式 | +|----------|----------|----------|----------| +| 前序 | 根→左→右 | 序列化、复制树 | 递归 / 栈 | +| 中序 | 左→根→右 | BST 排序输出 | 递归 / 栈 | +| 后序 | 左→右→根 | 释放节点、表达式求值 | 递归 / 栈 | +| 层序 | 逐层从左到右 | 最短路径、按层处理 | 队列 | + +### 递归 vs 迭代 + +递归写法简洁直观,但存在栈溢出风险(树深度过大时);迭代写法用显式栈/队列模拟调用栈,更安全也更可控。面试中通常要求两种都能写。 + +### 统一迭代模板 + +三种 DFS 遍历的迭代写法可以用「标记法」统一:入栈时按相反顺序压入节点和 null 标记,遇到 null 时才真正访问节点,通过调整压入顺序即可切换前/中/后序。 + +--- + +## 💻 代码示例 + +### 节点定义 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right +``` + +### 递归写法 + +```python +def preorder(root): + if not root: + return [] + return [root.val] + preorder(root.left) + preorder(root.right) + +def inorder(root): + if not root: + return [] + return inorder(root.left) + [root.val] + inorder(root.right) + +def postorder(root): + if not root: + return [] + return postorder(root.left) + postorder(root.right) + [root.val] +``` + +### 迭代写法 + +```python +def preorder_iter(root): + """前序迭代:栈,先右后左压入""" + if not root: + return [] + stack, res = [root], [] + while stack: + node = stack.pop() + res.append(node.val) + if node.right: + stack.append(node.right) + if node.left: + stack.append(node.left) + return res + +def inorder_iter(root): + """中序迭代:一路向左入栈,弹出后转向右子树""" + stack, res = [], [] + cur = root + while cur or stack: + while cur: + stack.append(cur) + cur = cur.left + cur = stack.pop() + res.append(cur.val) + cur = cur.right + return res + +def postorder_iter(root): + """后序迭代:前序(根左右)翻转 → 根右左 → 反转得左右根""" + if not root: + return [] + stack, res = [root], [] + while stack: + node = stack.pop() + res.append(node.val) + if node.left: + stack.append(node.left) + if node.right: + stack.append(node.right) + return res[::-1] +``` + +### 层序遍历(BFS) + +```python +from collections import deque + +def levelorder(root): + if not root: + return [] + q, res = deque([root]), [] + while q: + level = [] + for _ in range(len(q)): + node = q.popleft() + level.append(node.val) + if node.left: + q.append(node.left) + if node.right: + q.append(node.right) + res.append(level) + return res +``` + +### 统一迭代模板(标记法) + +```python +def inorder_unified(root): + """统一模板:切换压入顺序即可实现前/中/后序""" + if not root: + return [] + stack, res = [root], [] + while stack: + node = stack.pop() + if node is None: + # null 标记,下一个就是真正要访问的节点 + val_node = stack.pop() + res.append(val_node.val) + else: + # 中序:左 → 根 → 右,入栈顺序相反:右 → 根(含标记) → 左 + if node.right: + stack.append(node.right) + stack.append(node) # 节点本身 + stack.append(None) # null 标记 + if node.left: + stack.append(node.left) + return res +``` + +--- + +## ⚠️ 常见陷阱 + +!!! warning "中序迭代漏掉右子树" + 在中序迭代中,弹出节点后必须 `cur = node.right`,而不是继续向左走。忘记转向右子树会导致死循环或遗漏节点。 + +!!! warning "后序迭代的反转技巧" + 后序迭代用「前序翻转法」时,注意压栈顺序是**先左后右**(与标准前序相反),这样弹出顺序变成 根→右→左,反转后才是 左→右→根。 + +!!! warning "层序遍历中 queue 长度要在循环外快照" + `for _ in range(len(q))` 必须在 for 循环开始前锁住当前层的节点数。如果在循环内动态取 `len(q)`,会把新入队的下一层节点也算进来。 + +--- + +## 🏋️ 练习题 + +??? question "练习 1:LeetCode 144 — 二叉树的前序遍历" + 给你二叉树的根节点 `root`,返回它节点值的**前序遍历**。请用迭代方式实现。 + + ??? success "答案" + ```python + def preorderTraversal(root): + if not root: + return [] + stack, res = [root], [] + while stack: + node = stack.pop() + res.append(node.val) + if node.right: + stack.append(node.right) + if node.left: + stack.append(node.left) + return res + ``` + +??? question "练习 2:LeetCode 94 — 二叉树的中序遍历" + 给定一个二叉树的根节点 `root`,返回它的**中序遍历**。请用迭代方式实现。 + + ??? success "答案" + ```python + def inorderTraversal(root): + stack, res = [], [] + cur = root + while cur or stack: + while cur: + stack.append(cur) + cur = cur.left + cur = stack.pop() + res.append(cur.val) + cur = cur.right + return res + ``` + +??? question "练习 3:LeetCode 102 — 二叉树的层序遍历" + 给你二叉树的根节点 `root`,返回其节点值的**层序遍历**(即逐层地,从左到右访问所有节点)。 + + ??? success "答案" + ```python + from collections import deque + + def levelOrder(root): + if not root: + return [] + q, res = deque([root]), [] + while q: + level = [] + for _ in range(len(q)): + node = q.popleft() + level.append(node.val) + if node.left: + q.append(node.left) + if node.right: + q.append(node.right) + res.append(level) + return res + ``` + +--- + +## 🔗 相关链接 + +- [LeetCode 144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) — 前序迭代练习 +- [LeetCode 94. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) — 中序迭代练习 +- [LeetCode 145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) — 后序迭代练习 +- [LeetCode 102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) — 层序 BFS 练习 diff --git a/mkdocs.yml b/mkdocs.yml index e33c16c..ae8083d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -100,3 +100,4 @@ nav: - 计数布隆过滤器: algorithm/counting-bloom-filter.md - 布谷鸟过滤器: algorithm/cuckoo-filter.md - HeavyKeeper: algorithm/heavykeeper.md + - 二叉树的遍历: algorithm/binary-tree-traversal.md