题目描述
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
思路
有一棵二叉树:
中
/ \
左 右
- 前序遍历:中,左,右
- 中序遍历:左,中,右
- 后序遍历:左,右,中
递归的方式相对比较简单,就是递归的遍历左子树,然后添加根节点的值,再去递归遍历右子树
下面有一种比较通用的方法,这种方法使用栈和循环来进行遍历,原文内容来自leetcode.cn/problems/bi…
核心思想如下:
- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
下面谈一些我的理解:
颜色标记法通过为每个节点分配颜色来跟踪其访问状态:
- WHITE (0):节点尚未被处理,需要遍历其子节点。
- GRAY (1):节点已遍历完左子树,准备访问节点本身。
const WHITE = 0, GRAY = 1, res = [];
const stack = [[WHITE, root]];
- 颜色常量:
- WHITE表示需要处理的节点。
- GRAY 表示节点已经处理过左子树,待访问节点值。
- 结果数组 res:用于存储中序遍历的结果。
- 栈 stack:初始化时将根节点以 WHITE 标记压入栈中。栈用于模拟递归调用。
遍历过程:
while (stack.length > 0) {
const [color, node] = stack.pop();
if (!node) continue;
if (color === WHITE) {
stack.push([WHITE, node.right])//WHITE(0):节点尚未被处理,需要遍历其子节点。
stack.push([GRAY, node])//GRAY(1):节点已遍历完左子树,准备访问节点本身
stack.push([WHITE, node.left])//WHITE(0):节点尚未被处理,需要遍历其子节点。
} else {
res.push(node.val);
}
}
- 循环条件:当栈不为空时,持续执行遍历。
- 弹出栈顶元素:每次从栈中弹出一个
[颜色, 节点]的元组。 - 处理空节点:如果当前节点为
null,则跳过本次循环。 - 处理 WHITE 节点:
- 右子节点:将右子节点以 WHITE 标记压入栈中,稍后需要遍历右子树。
- 当前节点:将当前节点以 GRAY 标记压入栈中,表示左子树已遍历完,待访问节点值。
- 左子节点:将左子节点以 WHITE 标记压入栈中,优先遍历左子树。
- 处理 GRAY 节点:将节点的值添加到结果数组 res 中。
先序遍历是左中右,为什么添加到栈的顺序是右中左:
栈是后进先出,所以我们通过右中左的添加顺序来构造我们想要的出栈顺序,实际上的出栈顺序是左中右
同理,对于前序或者后续遍历,我们只要调整入栈顺序即可
修改入栈顺序实现不同遍历
| 遍历方式 | 入栈顺序(从上到下) |
|---|---|
| 中序遍历 | 右子节点 [WHITE] -> 当前节点 ([GRAY]) -> 左子节点 ([WHITE]) |
| 前序遍历 | 右子节点 ([WHITE] -> 左子节点 ([WHITE]) -> 根节点 ([GRAY]) |
| 后序遍历 | 根节点 ([GRAY]) -> 右子节点 ([GRAY]) -> 左子节点 ([GRAY]) |
复杂度分析
时间复杂度
所有三种遍历方法的时间复杂度均为 O(n),其中 n 是二叉树的节点数。
空间复杂度
空间复杂度主要取决于栈的大小,最坏情况下为 O(n)。
code
递归
var inorderTraversal = function (root, res = []) {
if(!root) return res
inorderTraversal(root.left, res)//递归遍历左子树
res.push(root.val)//添加子树的根节点
inorderTraversal(root.right, res)//递归遍历右子树
return res
};
循环
/**
* 定义一个二叉树
*/
function TreeNode(val, left = null, right = null) {
this.val = val === undefined ? 0 : val;
this.left = left;
this.right = right;
}
/**
* 中序遍历二叉树
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
const WHITE = 0,
GRAY = 1,
res = [];
const stack = [[WHITE, root]];
while (stack.length > 0) {
const [color, node] = stack.pop();
if (!node) continue;
if (color === WHITE) {
stack.push([WHITE, node.right])//WHITE(0):节点尚未被处理,需要遍历其子节点。
stack.push([GRAY, node])//GRAY(1):节点已遍历完左子树,准备访问节点本身
stack.push([WHITE, node.left])//WHITE(0):节点尚未被处理,需要遍历其子节点。
} else {
res.push(node.val);
}
}
return res;
};
// 示例输入:构建二叉树 [1, null, 2, 3]
/*
1
\
2
/
3
*/
const root = new TreeNode(1);
root.right = new TreeNode(2);
root.right.left = new TreeNode(3);
// 调用中序遍历函数
const result = inorderTraversal(root);
console.log(result); // 输出: [1, 3, 2]