树与二叉树:从概念到 JavaScript 实现

在数据结构的世界里,树是一种极具代表性的非线性结构,它模拟了现实世界中树的形态(只不过是 "倒过来" 的),广泛应用于数据库索引、编译器语法分析、文件系统管理等场景。其中,二叉树作为树结构中最基础也最常用的类型,更是程序员必须掌握的核心知识点。本文将从树的基本概念出发,详解二叉树的定义、表示方法,并结合 JavaScript 代码实例,讲解二叉树的遍历方式。

一、树的核心概念

1. 树的基本构成

树的结构可以拆解为三个核心要素:

  • 根节点:树的最顶层节点,是整棵树的 "起点",没有父节点;
  • 节点与边:树枝的两端为节点,节点存储数据,边表示节点间的父子关系;
  • 叶子节点:没有子节点的节点(度为 0 的节点),位于树的最底层。

2. 二叉树的定义

二叉树是树的特殊形式,其定义具有递归特性:

  • 可以是空树(没有任何节点);
  • 如果非空,必须由根节点左子树右子树组成,且左、右子树本身也必须是二叉树;
  • 二叉树的左右子树位置严格固定,不能随意交换,这是与普通树的关键区别。

3. 二叉树的关键术语

  • 层次:根节点层次为 1,子节点层次为父节点层次 + 1;
  • 高度:叶子节点高度为 1,向上每一层高度 + 1;
  • :一个节点拥有的子树数量(二叉树节点的度最大为 2);
  • 叶子节点:度为 0 的节点,即没有左、右子节点的节点。

二、JavaScript 中如何表示二叉树

在 JavaScript 中,二叉树的节点可以通过对象来表示,每个节点包含三部分:数据域(存储节点值)、左子节点引用、右子节点引用。

1. 定义二叉树节点

javascript

运行

kotlin 复制代码
// 定义二叉树节点构造函数
function TreeNode(val) {
    this.val = val; // 数据域:存储节点值
    this.left = null; // 左子节点引用,初始为null
    this.right = null; // 右子节点引用,初始为null
}

2. 构建一棵完整的二叉树

基于上述构造函数,我们可以手动构建一棵具体的二叉树:

javascript

运行

less 复制代码
// 构建一棵示例二叉树
const tree = {
    val: 'A', // 根节点
    left: { // 左子树
        val: 'B',
        left: { // B的左子树
            val: 'D',
            left: null,
            right: null
        },
        right: { // B的右子树
            val: 'E',
            left: null,
            right: null
        }
    },
    right: { // 右子树
        val: 'C',
        left: { // C的左子树
            val: 'F',
            left: null,
            right: null
        },
        right: { // C的右子树
            val: 'G',
            left: null,
            right: null
        }
    }
};

这棵树的结构如下:

plaintext

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

三、二叉树的遍历

遍历是二叉树最核心的操作,目的是按特定顺序访问树中所有节点。根据访问顺序的不同,分为递归遍历 (前序、中序、后序)和迭代遍历(层序)两类。

1. 递归遍历:基于 "自顶向下" 的思想

递归遍历的核心是 "将大问题拆解为小问题":遍历整棵树 = 访问根节点 + 遍历左子树 + 遍历右子树,仅调整三者的顺序,就形成了不同的遍历方式。递归的关键是明确退出条件(节点为 null 时停止)。

(1)前序遍历:根节点 → 左子树 → 右子树

javascript

运行

scss 复制代码
function preorder(root) {
    // 退出条件:节点为null时,直接返回
    if (!root) {
        return;
    }
    // 第一步:访问根节点
    console.log('前序遍历 - 当前节点:', root.val);
    // 第二步:递归遍历左子树
    preorder(root.left);
    // 第三步:递归遍历右子树
    preorder(root.right);
}

// 调用示例
preorder(tree); // 输出:A → B → D → E → C → F → G

(2)中序遍历:左子树 → 根节点 → 右子树

javascript

运行

scss 复制代码
function inorder(root) {
    // 退出条件:节点为null时,直接返回
    if (!root) {
        return;
    }
    // 第一步:递归遍历左子树
    inorder(root.left);
    // 第二步:访问根节点
    console.log('中序遍历 - 当前节点:', root.val);
    // 第三步:递归遍历右子树
    inorder(root.right);
}

// 调用示例
inorder(tree); // 输出:D → B → E → A → F → C → G

(3)后序遍历:左子树 → 右子树 → 根节点

javascript

运行

scss 复制代码
function postorder(root) {
    // 退出条件:节点为null时,直接返回
    if (!root) {
        return;
    }
    // 第一步:递归遍历左子树
    postorder(root.left);
    // 第二步:递归遍历右子树
    postorder(root.right);
    // 第三步:访问根节点
    console.log('后序遍历 - 当前节点:', root.val);
}

// 调用示例
postorder(tree); // 输出:D → E → B → F → G → C → A

2. 迭代遍历:层序遍历(按层次访问)

层序遍历不使用递归,而是借助队列(先进先出)实现,按 "从上到下、从左到右" 的顺序访问每一层节点。

javascript

运行

arduino 复制代码
function levelorder(root) {
    const queue = []; // 队列:存储待访问的节点
    const res = []; // 结果数组:存储遍历结果
    // 边界条件:空树直接返回空数组
    if (!root) {
        return res;
    }
    // 第一步:根节点入队
    queue.push(root);
    // 循环:队列不为空时,持续处理节点
    while (queue.length > 0) {
        // 出队:取出队列头部的节点
        const node = queue.shift();
        // 访问当前节点:将值存入结果数组
        res.push(node.val);
        // 左子节点存在则入队(先左后右)
        if (node.left) {
            queue.push(node.left);
        }
        // 右子节点存在则入队
        if (node.right) {
            queue.push(node.right);
        }
    }
    // 返回遍历结果
    return res;
}

// 调用示例
const levelResult = levelorder(tree);
console.log('层序遍历结果:', levelResult); // 输出:["A", "B", "C", "D", "E", "F", "G"]

四、递归的延伸:树状结构的解题思路

递归是处理树状结构的核心思想,其本质是 "找规律、定退出条件"。例如经典的 "爬楼梯问题",也可以用树状递归思路解决:

javascript

运行

scss 复制代码
// 爬楼梯:n阶楼梯,每次走1或2阶,求总方法数
function climbStairs(n) {
    // 退出条件:n≤2时,方法数等于n(1阶1种,2阶2种)
    if (n <= 2) {
        return n;
    }
    // 递归公式:f(n) = f(n-1) + f(n-2)
    // 走n阶的方法数 = 走n-1阶的方法数 + 走n-2阶的方法数
    return climbStairs(n - 1) + climbStairs(n - 2);
}

console.log(climbStairs(5)); // 输出:8(5阶楼梯有8种走法)

这个问题的递归过程可以看作一棵二叉树:每个节点代表 "剩余楼梯数",左子节点是 "走 1 阶",右子节点是 "走 2 阶",最终叶子节点的数量就是总方法数。

五、总结

树(尤其是二叉树)是数据结构的核心,其核心特性是递归定义非线性结构。在 JavaScript 中,我们可以通过对象轻松表示二叉树节点,而遍历操作则分为递归(前、中、后序)和迭代(层序)两种方式:

  • 递归遍历的关键是 "先左后右" 的顺序和清晰的退出条件;
  • 层序遍历的核心是借助队列实现 "按层访问"。

掌握树的基本概念和遍历方法,不仅能解决算法题,更能理解递归的本质 ------ 将复杂问题拆解为重复的小问题,这也是编程中最重要的思维方式之一。

相关推荐
小小高不懂写代码1 小时前
Transformer与注意力机制
前端·人工智能
AiClaw1 小时前
AIClaw 的 Skills 机制:先注入索引,再按需读取完整说明
前端
YHHLAI1 小时前
HTML5 Canvas 从入门到实战:画布绘图 · 帧动画 · 小游戏 · 数据可视化
前端·信息可视化·html5
前端 贾公子1 小时前
npx skills:AI Agent Skill 的 npm,50+ 工具统一的 Skill 管理工具
前端
触底反弹1 小时前
面试官问"Ajax原理",我从XHR讲到async/await,他直接懵了!
前端·面试·架构
兰令水1 小时前
leecodecode【面试150】【2026.6.15打卡-java版本】
java·算法·面试
Chelsea05221 小时前
PC浏览器在线调试 Android 浏览器教程-chrome://inspect/#devices
android·前端·chrome
加号31 小时前
【C#】VS2022 传统 ASP.NET Web 服务(.asmx)接口实现指南
前端·c#·asp.net
小林ixn1 小时前
前端开发新利器:用Vite+通义万相实现多模态图像生成(附API密钥安全方案)
javascript