37. 二叉树的中序遍历
题目描述
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
求解
回忆回忆二叉树的相关概念:
二叉树的前序、中序、后序遍历是 ** 深度优先遍历**(DFS) 的三种核心方式,区别在于访问根节点、左子树、右子树的顺序不同。先明确几个概念:
根节点 :当前子树的根(或整棵树的根);
左子树 :根节点的左孩子为根的子树;
右子树:根节点的右孩子为根的子树。
一、前序遍历(根→左→右)
顺序:先访问根节点,再递归遍历左子树,最后递归遍历右子树。"前" 指根节点在遍历顺序中排在左、右子树之前。
示例(二叉树结构):
htmlplaintext
1
/ \
2 3
/ \
4 5
前序遍历结果:1 → 2 → 4 → 5 → 3
核心逻辑(递归):
javascript
function preorderTraversal(root) {
const res = [];
const dfs = (node) => {
if (!node) return;
res.push(node.val); // 先访问根
dfs(node.left); // 再遍历左子树
dfs(node.right); // 最后遍历右子树
};
dfs(root);
return res;
}
二、中序遍历(左→根→右)
顺序:先递归遍历左子树,再访问根节点,最后递归遍历右子树。"中" 指根节点在遍历顺序中排在左子树之后、右子树之前。
示例(同上二叉树):
中序遍历结果:4 → 2 → 5 → 1 → 3
核心逻辑(递归):
javascript
function inorderTraversal(root) {
const res = [];
const dfs = (node) => {
if (!node) return;
dfs(node.left); // 先遍历左子树
res.push(node.val); // 再访问根
dfs(node.right); // 最后遍历右子树
};
dfs(root);
return res;
}
特殊性质:
二叉搜索树(BST)的中序遍历结果是严格升序的,这是 BST 的重要特征。
三、后序遍历(左→右→根)
顺序:先递归遍历左子树,再递归遍历右子树,最后访问根节点。"后" 指根节点在遍历顺序中排在左、右子树之后。
示例(同上二叉树):
后序遍历结果:4 → 5 → 2 → 3 → 1
核心逻辑(递归):
javascript
function postorderTraversal(root) {
const res = [];
const dfs = (node) => {
if (!node) return;
dfs(node.left); // 先遍历左子树
dfs(node.right); // 再遍历右子树
res.push(node.val); // 最后访问根
};
dfs(root);
return res;
}
特殊性质:
后序遍历常用于删除树节点(先删子节点,再删根)、计算子树和等场景。
四、总结对比
| 遍历方式 | 顺序 | 核心特点 | 典型应用 |
|---|---|---|---|
| 前序 | 根→左→右 | 根节点最先访问 | 复制树、序列化树 |
| 中序 | 左→根→右 | BST 遍历结果升序 | 验证 BST、找 BST 的最值 |
| 后序 | 左→右→根 | 根节点最后访问 | 删除树、计算子树高度 / 和 |
记忆技巧:
- 前序:根在最前;
- 中序:根在中间;
- 后序:根在最后;
(左子树永远在右子树之前遍历)。
代码实现如下:
js
var inorderTraversal = function(root) {
// 先写递归
// 中序遍历:左 根 右
let ans = [];
const dfs = (node) => {
if (!node) return null;
dfs(node.left); // 先遍历左边
ans.push(node.val); // 访问根节点
dfs(node.right); // 在遍历右边
}
dfs(root);
return ans;
};
进阶:(使用迭代遍历)
js
var inorderTraversal = function(root) {
// 迭代: 使用 栈
// 中序遍历:左 根 右
let ans = [];
let stk = []; // 定义一个栈
while (root || stk.length !== 0) {
while(root) {
stk.push(root);
root = root.left;
}
root = stk.pop();
ans.push(root.val);
root = root.right;
}
return ans;
};
在题解中 有一个特别厉害的方法,作者称为"颜色标记法 " ,兼具栈迭代方法的高效,又像递归方法一样简洁易懂,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码。链接
其核心思想如下:
- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
代码如下:
js
var inorderTraversal = function(root) {
// 迭代: 使用 栈
// 中序遍历:左 根 右
let ans = [];
let stk = [{ node: root, flag: 0}]; // 定义一个栈
while (stk.length !== 0) {
const {node, flag} = stk.pop()
if (!node) continue;
if (flag === 0) {
stk.push({node: node.right, flag: 0})
stk.push({node: node, flag: 1})
stk.push({node: node.left, flag: 0})
} else {
ans.push(node.val);
}
}
return ans;
};