二叉树遍历的统一解法 - 双色标记法
背景介绍
在前端开发中,树形结构随处可见(比如 DOM 树、组件树、状态树等)。理解并掌握树的遍历方法对于前端开发者来说尤为重要。本文将介绍一种优雅的统一遍历方案 - 双色标记法。
双色标记法原理
这个方法的灵感来自于垃圾回收算法中的三色标记法,我们简化为双色标记:
- 白色(WHITE):表示节点尚未被访问
- 灰色(GRAY):表示节点已被访问
核心思路
- 使用栈来维护遍历顺序
- 每个入栈的节点都携带一个颜色标记
- 通过调整入栈顺序来实现不同的遍历方式
代码实现
通用模板
javascript
const traversal = (root) => {
const [WHITE, GRAY] = [0, 1];
const result = [];
const stack = [[WHITE, root]];
while (stack.length) {
const [color, node] = stack.pop();
if (!node) continue;
if (color === WHITE) {
// 根据需要的遍历顺序调整以下三个入栈顺序
stack.push([WHITE, node.right]); // 右子节点
stack.push([GRAY, node]); // 当前节点
stack.push([WHITE, node.left]); // 左子节点
} else {
result.push(node.val);
}
}
return result;
}三种遍历方式的实现
只需要调整入栈顺序,就能实现不同的遍历方式:
javascript
// 前序遍历 (根->左->右)
if (color === WHITE) {
stack.push([WHITE, node.right]);
stack.push([WHITE, node.left]);
stack.push([GRAY, node]); // 根节点最后入栈,最先访问
}
// 中序遍历 (左->根->右)
if (color === WHITE) {
stack.push([WHITE, node.right]);
stack.push([GRAY, node]); // 根节点在中间入栈
stack.push([WHITE, node.left]);
}
// 后序遍历 (左->右->根)
if (color === WHITE) {
stack.push([GRAY, node]); // 根节点最先入栈,最后访问
stack.push([WHITE, node.right]);
stack.push([WHITE, node.left]);
}实际应用示例
javascript
// 二叉树节点定义
class TreeNode {
constructor(val) {
this.val = val;
this.left = null;
this.right = null;
}
}
// 创建示例树
const root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
// 测试不同遍历方式
console.log('前序遍历:', preorderTraversal(root)); // [1, 2, 4, 5, 3]
console.log('中序遍历:', inorderTraversal(root)); // [4, 2, 5, 1, 3]
console.log('后序遍历:', postorderTraversal(root)); // [4, 5, 2, 3, 1]优化建议
对于前序遍历,其实可以使用更简单的方式实现:
javascript
const simplePreorder = (root) => {
if (!root) return [];
const result = [];
const stack = [root];
while (stack.length) {
const node = stack.pop();
if (!node) continue;
result.push(node.val);
// 注意:先压入右子节点,再压入左子节点
stack.push(node.right);
stack.push(node.left);
}
return result;
};总结
- 双色标记法提供了一种统一的思路来处理二叉树的三种遍历方式
- 通过调整入栈顺序,可以轻松实现不同的遍历顺序
- 这种方法在处理复杂的树形结构时特别有用,比如在开发树形组件或处理虚拟DOM时
练习建议
- 尝试在 LeetCode 上实现这些遍历方法
- 将这种思想应用到前端实际项目中的树形结构处理
- 尝试扩展这种方法来处理 N 叉树的遍历