从递归到迭代,一文吃透二叉树的核心知识与 JavaScript 实现

从现实到代码:深入理解树与二叉树

数据结构中的树,是对现实世界树形结构的一次精妙抽象

引言

在现实生活中,树随处可见------大树有根、有枝干、有树叶。计算机科学中的树结构正是从这一自然形态中汲取灵感,只不过它被倒置了:根在上,叶在下。

树结构在计算机领域应用广泛,从文件系统、DOM 树,到编译器语法分析、数据库索引,处处都有它的身影。今天,我们就来深入理解树与二叉树,并用 JavaScript 实现它的核心操作。


一、二叉树:递归定义的典范

1.1 什么是二叉树?

二叉树是树结构中最基础也最重要的一种。它的定义本身就是递归的:

  • 它可以是一棵空树(没有根节点)
  • 如果不是空树,那么它由根节点左子树右子树组成,且左右子树都是二叉树

⚠️ 注意:二叉树不能 简单地定义为"每个节点度不超过 2 的树"。二叉树的左右子树位置是严格约定的,不可交换。即使只有一个子节点,也要明确它是左孩子还是右孩子。

1.2 为什么用递归定义?

递归的核心思想是:将一个大问题分解为若干个相似的子问题

scss 复制代码
         f(10)
        /     \
     f(9)     f(8)
    /   \    /   \
  f(8) f(7) f(7) f(6)
  ...

二叉树天然具有这种自相似性------一个节点下面又挂载着同样结构的子树,这正是递归的用武之地。

不过要记住:递归依赖函数调用栈,如果层数过深可能导致栈溢出(爆栈),后续我们会讨论优化方案。


二、树的核心概念速览

在深入二叉树之前,先来梳理几个树结构的通用术语:

概念 含义
层次 根节点所在层为第 1 层,子节点依次+1
高度 叶子节点高度为 1,向上逐层+1(目标节点高度 = 子节点高度 + 1)
一个节点拥有子树的个数
叶子节点 度为 0 的节点(没有子节点)

💡 记忆技巧:层次 从上往下数,高度从下往上数。


三、二叉树的分类与性质

3.1 两种特殊的二叉树

满二叉树(Full Binary Tree)

每一层的节点数都达到最大值。也就是说,除了叶子节点外,每个节点都有两个子节点。

markdown 复制代码
        1
       / \
      2   3
     / \ / \
    4  5 6  7

完全二叉树(Complete Binary Tree)

除了最后一层,其他层都是满的,且最后一层的节点都靠左排列

markdown 复制代码
        1
       / \
      2   3
     / \ /
    4  5 6

3.2 重要性质

设二叉树的高度为 h(根节点高度为 1):

  • 第 i 层最多有 2^(i-1) 个节点
  • 高度为 h 的二叉树最多有 2^h - 1 个节点
  • 叶子节点数 = 度为 2 的节点数 + 1

四、JavaScript 中如何表示二叉树?

在 JS 中,每个二叉树节点可以抽象为三个部分:

  1. 数据域(val)
  2. 左子节点引用(left)
  3. 右子节点引用(right)
javascript 复制代码
function TreeNode(val) {
  this.val = val;
  this.left = null;
  this.right = null;
}

构建一棵示例二叉树:

javascript 复制代码
const tree = {
  val: 'A',
  left: {
    val: 'B',
    left: { val: 'D', left: null, right: null },
    right: { val: 'E', left: null, right: null }
  },
  right: {
    val: 'C',
    left: { val: 'F', left: null, right: null },
    right: { val: 'G', left: null, right: null }
  }
};

对应的树结构如下:

css 复制代码
        A
       / \
      B   C
     / \ / \
    D  E F  G

五、二叉树的遍历

遍历是二叉树最核心的操作。按照访问根节点的时机 ,可以分为三类递归遍历,且始终遵循先左后右的原则。

5.1 递归遍历

前序遍历(Preorder)

根节点 → 左子树 → 右子树

javascript 复制代码
function preorder(root) {
  if (!root) return;
  console.log(root.val);  // 根
  preorder(root.left);    // 左
  preorder(root.right);   // 右
}
// 输出:A B D E C F G
中序遍历(Inorder)

左子树 → 根节点 → 右子树

javascript 复制代码
function inorder(root) {
  if (!root) return;
  inorder(root.left);     // 左
  console.log(root.val);  // 根
  inorder(root.right);    // 右
}
// 输出:D B E A F C G
后序遍历(Postorder)

左子树 → 右子树 → 根节点

javascript 复制代码
function postorder(root) {
  if (!root) return;
  postorder(root.left);   // 左
  postorder(root.right);  // 右
  console.log(root.val);  // 根
}
// 输出:D E B F G C A

5.2 层序遍历(迭代实现)

层序遍历按照从上到下、从左到右 的顺序访问每个节点,使用队列来实现。

javascript 复制代码
function levelorder(root) {
  if (!root) return [];
  const queue = [];
  const result = [];
  queue.push(root);

  while (queue.length) {
    const node = queue.shift();    // 出队
    result.push(node.val);
    if (node.left) queue.push(node.left);
    if (node.right) queue.push(node.right);
  }
  return result;
}
// 输出:['A', 'B', 'C', 'D', 'E', 'F', 'G']

六、递归思想与树:爬梯子问题

树的递归结构让我们自然联想到其他递归问题。来看一个经典例子------爬楼梯问题

每次可以爬 1 或 2 个台阶,爬到第 n 级有多少种不同方法?

这个问题可以看作一棵递归树

scss 复制代码
          f(5)
        /      \
    f(4)        f(3)
   /    \      /    \
 f(3)   f(2) f(2)   f(1)
 ...

递归公式与退出条件:

javascript 复制代码
function climbStairs(n) {
  if (n <= 2) return n;                    // 退出条件
  return climbStairs(n - 1) + climbStairs(n - 2);  // 递归公式
}

不过,这段代码在 n 较大时(如 n=100)会爆栈,因为存在大量重复计算。优化方案我们将在后续动态规划专题中讨论。


七、拓展:非递归遍历(迭代实现)

递归虽然代码简洁,但有栈溢出风险。以下给出前序遍历的迭代实现:

javascript 复制代码
function preorderIterative(root) {
  if (!root) return;
  const stack = [root];
  while (stack.length) {
    const node = stack.pop();
    console.log(node.val);
    // 栈是 LIFO,先压右孩子,再压左孩子
    if (node.right) stack.push(node.right);
    if (node.left) stack.push(node.left);
  }
}

中序和后序的迭代实现稍复杂,核心思路同样是手动维护栈来模拟函数调用栈。


八、小结

本文我们系统梳理了:

知识点 核心要点
二叉树定义 递归结构,左右有序
关键概念 层次、高度、度、叶子节点
存储方式 JS 对象引用(链式存储)
递归遍历 前/中/后序,先左后右
层序遍历 队列实现,逐层访问
递归思想 自顶向下,分解子问题

树结构是理解更复杂数据结构(如堆、AVL 树、红黑树、B 树)的基础,也是算法面试中的常客。掌握递归与树的结合,你将在编程之路上走得更远。

🚀 下期预告:二叉搜索树(BST)与动态规划优化------从爆栈到高效。


如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关推荐
铁皮饭盒2 小时前
Bun 多线程有多快?postMessage 传输字符串比 Node.js 快 400 倍!
前端·javascript·后端
To_OC12 小时前
LC 49 字母异位词分组:想到哈希表很简单,选对 key 才是精髓
javascript·算法·leetcode
kyriewen15 小时前
用了半年 Claude Code 后,我尝试关掉它写了一周代码——结果比想象中严重
前端·javascript·ai编程
山河木马16 小时前
矩阵专题0-webGL中的矩阵
javascript·webgl·计算机图形学
Asize17 小时前
多模态生图:从 Vite 工程化到前端调用 Qwen Image
javascript·人工智能·后端
陳陈陳17 小时前
从Token到Embedding:一篇文章搞懂大模型的「文字数学变形记」
前端·javascript·ai编程
用户9385156350717 小时前
从 O(n²) 到 O(nlogn):一文读懂快速排序的“快”与“妙”
javascript·算法
橘子星17 小时前
LLM 无状态架构实践:从原理到代码落地
前端·javascript·人工智能
To_OC18 小时前
手写快排次次翻车?别死背快排模板了,这才是面试官想听的底层逻辑
javascript·算法·排序算法