二叉树基础知识
为什么前端开发要学习二叉树?
二叉树在前端开发中有广泛应用:
- DOM树的操作和遍历
- 组件树的管理
- 状态管理工具的数据结构
- 前端路由的实现
- 抽象语法树(AST)的处理
二叉树的核心概念
1. 基本特性
- 每个节点最多有两个子节点
- 第i层最大节点数:2^(i-1)
- 深度为k的最大节点总数:2^k - 1
- 叶节点数(n0)与度为2的节点数(n2)关系:n0 = n2 + 1
2. 二叉树节点的实现
js
class TreeNode {
constructor(val) {
this.val = val;
this.left = null;
this.right = null;
}
}二叉树的遍历方法
1. 深度优先遍历(DFS)
前序遍历(根->左->右)
js
// 递归实现
function preorder(root) {
if (!root) return;
console.log(root.val); // 处理当前节点
preorder(root.left); // 遍历左子树
preorder(root.right); // 遍历右子树
}
// 迭代实现
function preorderIterative(root) {
if (!root) return;
const stack = [root];
while (stack.length) {
const node = stack.pop();
console.log(node.val);
// 注意:先压入右节点,再压入左节点
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
}中序遍历(左->根->右)
js
// 迭代实现
function inorderIterative(root) {
const stack = [];
let current = root;
while (stack.length || current) {
// 遍历到最左节点
while (current) {
stack.push(current);
current = current.left;
}
current = stack.pop();
console.log(current.val);
current = current.right;
}
}2. 广度优先遍历(BFS)
js
function levelOrder(root) {
if (!root) return [];
const queue = [root];
const result = [];
while (queue.length) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}特殊的二叉树
1. 二叉搜索树(BST)
特点:
- 左子树所有节点值 < 根节点值
- 右子树所有节点值 > 根节点值
- 常用于快速查找和维护有序数据
js
class BST {
constructor() {
this.root = null;
}
insert(val) {
const newNode = new TreeNode(val);
if (!this.root) {
this.root = newNode;
return;
}
let current = this.root;
while (true) {
if (val < current.val) {
if (!current.left) {
current.left = newNode;
break;
}
current = current.left;
} else {
if (!current.right) {
current.right = newNode;
break;
}
current = current.right;
}
}
}
}2. 平衡二叉树
特点:
- 任意节点的左右子树高度差不超过1
- 保证树的高度在logN级别,提供稳定的性能
实际应用示例
1. 组件树的遍历
js
function traverseComponentTree(component) {
// 前序遍历处理组件
console.log(component.name);
// 遍历子组件
component.children?.forEach(child => {
traverseComponentTree(child);
});
}2. 虚拟DOM的diff算法
js
function diffTree(oldNode, newNode) {
if (!oldNode) return { type: 'CREATE', node: newNode };
if (!newNode) return { type: 'REMOVE' };
if (oldNode.type !== newNode.type) return { type: 'REPLACE', node: newNode };
// 递归比较子节点
const childPatches = [];
oldNode.children?.forEach((child, i) => {
childPatches.push(diffTree(child, newNode.children[i]));
});
return { type: 'UPDATE', patches: childPatches };
}性能优化技巧
- 使用迭代替代递归避免栈溢出
- 合理使用缓存减少重复计算
- 注意平衡性,避免树的退化
常见面试题
- 如何判断二叉树是否对称?
- 如何求二叉树的最大深度?
- 如何序列化和反序列化二叉树?
推荐练习
- LeetCode 94: 二叉树的中序遍历
- LeetCode 102: 二叉树的层序遍历
- LeetCode 236: 二叉树的最近公共祖先