概述
二叉树是最基础并且及其常用的数据结构之一,对于前端开发来说,必须掌握相关概念,下文总结部分和二叉树相关的概念知识。
二叉树节点定义
js
// 二叉树节点类
class TreeNode {
constructor(val = 0, left = null, right = null) {
this.val = val; // 节点值
this.left = left; // 左子树
this.right = right; // 右子树
}
}
前序遍历
顺序:根 → 左 → 右
js
/**
* 前序遍历 - 递归版本
* @param {TreeNode} root 根节点
* @return {number[]} 遍历结果数组
*/
function preorderTraversal(root) {
const result = [];
function traverse(node) {
if (!node) return;
result.push(node.val); // 先访问根节点
traverse(node.left); // 再遍历左子树
traverse(node.right); // 最后遍历右子树
}
traverse(root);
return result;
}
/**
* 前序遍历 - 迭代版本(使用栈)
*/
function preorderTraversalIterative(root) {
const result = [];
const stack = [];
let current = root;
while (current || stack.length) {
// 不断向左深入,访问节点并压入栈
while (current) {
result.push(current.val); // 访问当前节点
stack.push(current); // 压入栈,用于后续访问右子树
current = current.left; // 移动到左子节点
}
// 弹出栈顶节点,处理其右子树
current = stack.pop();
current = current.right;
}
return result;
}
中序遍历
顺序:左 → 根 → 右
js
/**
* 中序遍历 - 递归版本
* @param {TreeNode} root 根节点
* @return {number[]} 遍历结果数组
*/
function inorderTraversal(root) {
const result = [];
function traverse(node) {
if (!node) return;
traverse(node.left); // 先遍历左子树
result.push(node.val); // 再访问根节点
traverse(node.right); // 最后遍历右子树
}
traverse(root);
return result;
}
/**
* 中序遍历 - 迭代版本
*/
function inorderTraversalIterative(root) {
const result = [];
const stack = [];
let current = root;
while (current || stack.length) {
// 不断向左深入,将所有左子节点压入栈
while (current) {
stack.push(current);
current = current.left;
}
// 弹出栈顶节点并访问
current = stack.pop();
result.push(current.val);
// 处理右子树
current = current.right;
}
return result;
}
后续遍历
顺序:左 → 右 → 根
js
/**
* 后序遍历 - 递归版本
* @param {TreeNode} root 根节点
* @return {number[]} 遍历结果数组
*/
function postorderTraversal(root) {
const result = [];
function traverse(node) {
if (!node) return;
traverse(node.left); // 先遍历左子树
traverse(node.right); // 再遍历右子树
result.push(node.val); // 最后访问根节点
}
traverse(root);
return result;
}
/**
* 后序遍历 - 迭代版本(使用两个栈)
*/
function postorderTraversalIterative(root) {
if (!root) return [];
const result = [];
const stack1 = [root];
const stack2 = [];
// 使用第一个栈进行类似前序遍历(根→右→左)
while (stack1.length) {
const node = stack1.pop();
stack2.push(node);
if (node.left) stack1.push(node.left);
if (node.right) stack1.push(node.right);
}
// 第二个栈弹出顺序即为后序遍历结果(左→右→根)
while (stack2.length) {
result.push(stack2.pop().val);
}
return result;
}
根据遍历结果重构二叉树
1. 前序 + 中序 → 二叉树
javascript
/**
* 根据前序遍历和中序遍历结果构建二叉树
* @param {number[]} preorder 前序遍历结果
* @param {number[]} inorder 中序遍历结果
* @return {TreeNode} 重构的二叉树根节点
*/
function buildTreeFromPreIn(preorder, inorder) {
// 创建中序遍历值到索引的映射,便于快速查找根节点位置
const inorderMap = new Map();
for (let i = 0; i < inorder.length; i++) {
inorderMap.set(inorder[i], i);
}
let preIndex = 0; // 前序遍历索引指针
function build(left, right) {
// 递归终止条件:左右边界交叉
if (left > right) return null;
// 前序遍历的第一个元素就是根节点
const rootVal = preorder[preIndex++];
const root = new TreeNode(rootVal);
// 在中序遍历中找到根节点的位置
const inorderIndex = inorderMap.get(rootVal);
// 递归构建左子树和右子树
// 左子树:中序遍历中根节点左边的部分
root.left = build(left, inorderIndex - 1);
// 右子树:中序遍历中根节点右边的部分
root.right = build(inorderIndex + 1, right);
return root;
}
return build(0, inorder.length - 1);
}
2. 中序 + 后序 → 二叉树
javascript
/**
* 根据中序遍历和后序遍历结果构建二叉树
* @param {number[]} inorder 中序遍历结果
* @param {number[]} postorder 后序遍历结果
* @return {TreeNode} 重构的二叉树根节点
*/
function buildTreeFromInPost(inorder, postorder) {
const inorderMap = new Map();
for (let i = 0; i < inorder.length; i++) {
inorderMap.set(inorder[i], i);
}
let postIndex = postorder.length - 1; // 后序遍历索引指针(从后往前)
function build(left, right) {
if (left > right) return null;
// 后序遍历的最后一个元素是根节点
const rootVal = postorder[postIndex--];
const root = new TreeNode(rootVal);
const inorderIndex = inorderMap.get(rootVal);
// 注意:先构建右子树,再构建左子树(因为后序遍历是左右根,倒序是根右左)
root.right = build(inorderIndex + 1, right);
root.left = build(left, inorderIndex - 1);
return root;
}
return build(0, inorder.length - 1);
}
测试示例
javascript
// 创建测试二叉树
// 1
// / \
// 2 3
// / \
// 4 5
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]
// 测试重构
const preorder = [1, 2, 4, 5, 3];
const inorder = [4, 2, 5, 1, 3];
const postorder = [4, 5, 2, 3, 1];
const rebuiltRoot1 = buildTreeFromPreIn(preorder, inorder);
const rebuiltRoot2 = buildTreeFromInPost(inorder, postorder);
console.log('重构后的前序遍历:', preorderTraversal(rebuiltRoot1)); // 应该与原前序一致
console.log('重构后的中序遍历:', inorderTraversal(rebuiltRoot2)); // 应该与原中序一致
关键要点
- 前序+中序:可以唯一确定一棵二叉树
- 中序+后序:可以唯一确定一棵二叉树