JavaScript二叉树相关概念

概述

二叉树是最基础并且及其常用的数据结构之一,对于前端开发来说,必须掌握相关概念,下文总结部分和二叉树相关的概念知识。

二叉树节点定义

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));  // 应该与原中序一致

关键要点

  1. 前序+中序:可以唯一确定一棵二叉树
  2. 中序+后序:可以唯一确定一棵二叉树
相关推荐
attitude.x4 小时前
PyTorch 动态图的灵活性与实用技巧
前端·人工智能·深度学习
β添砖java4 小时前
CSS3核心技术
前端·css·css3
空山新雨(大队长)4 小时前
HTML第八课:HTML4和HTML5的区别
前端·html·html5
猫头虎-前端技术4 小时前
浏览器兼容性问题全解:CSS 前缀、Grid/Flex 布局兼容方案与跨浏览器调试技巧
前端·css·node.js·bootstrap·ecmascript·css3·媒体
阿珊和她的猫4 小时前
探索 CSS 过渡:打造流畅网页交互体验
前端·css
元亓亓亓4 小时前
JavaWeb--day1--HTML&CSS
前端·css·html
β添砖java4 小时前
CSS的文本样式
前端·css
前端小趴菜054 小时前
css - 滤镜
前端·css