欢迎光临
我们一直在努力

吃透树与二叉树:从递归原理到 JS 全场景遍历实战

目录

  • 一、树的基础认知:现实世界的抽象建模
  • 二、二叉树的核心定义:基于递归的精准描述
    • 2.1 递归思维的核心逻辑
    • 2.2 二叉树的标准递归定义
  • 三、二叉树的关键核心概念
    • 3.1 层次
    • 3.2 高度与深度
    • 3.3 节点的度
  • 四、JavaScript 中二叉树的存储结构
    • 4.1 标准节点构造函数
    • 4.2 完整二叉树实例搭建
  • 五、二叉树四大遍历算法(核心实战)
    • 5.1 前序遍历(根 → 左 → 右)
    • 5.2 中序遍历(左 → 根 → 右)
    • 5.3 后序遍历(左 → 右 → 根)
    • 5.4 层序遍历(广度优先迭代)
  • 六、递归算法实战:LeetCode 70 爬楼梯
    • 6.1 题目描述
    • 6.2 递归思路分析
    • 6.3 完整可运行代码
  • 七、全文总结
  • 八、核心知识点复盘
  • 九、常见问题与避坑指南

在数据结构体系中,树是仅次于数组、链表的核心基础结构,也是学习图、字典树、红黑树等进阶数据结构的前置知识。同时,二叉树作为树结构的核心分支,是前端算法、面试高频考点,绝大多数树类算法题的核心逻辑都围绕二叉树展开。

很多初学者觉得二叉树难懂,本质是无法理解其递归结构特性和遍历逻辑。本文将从现实类比、递归定义、核心概念、代码实现、算法实战五个维度,从零吃透二叉树,全程通俗易懂、代码可直接运行。

一、树的基础认知:现实世界的抽象建模

数据结构中的树,是对现实世界树木的简化与抽象,为了方便计算机存储和计算,我们将现实中的树倒置展示,也是代码中树结构的标准形态。两者对应关系如下:

  • 根节点:对应现实中的树根,是整棵树的起始节点,唯一且没有父节点
  • 边:对应现实中的树枝,用于连接上下级节点,代表节点间的关联关系
  • 节点:对应树枝的两端,用于存储数据,是树结构的基本单元
  • 叶子节点:对应现实中的树叶,是树的末端节点,没有子节点

区别于数组、链表的线性结构,树是非线性层级结构,天然具备「分层、递归」的特性,这也是树结构所有算法的核心底层逻辑。

二、二叉树的核心定义:基于递归的精准描述

2.1 递归思维的核心逻辑

想要学懂二叉树,必须先掌握递归思想。树结构是递归思想最典型的应用场景,递归的本质可以概括为:大问题拆解为同结构的小问题,重复执行相同逻辑,直到触发终止条件。

递归思维两大核心要素:

  • 递归公式(递推关系):大规模问题拆解为多个小规模同类问题,逻辑完全复用
  • 递归终止条件(出口):避免无限递归,防止程序栈溢出
  • 举个经典的拆解示例:计算层级问题时,f(n) 的结果依赖 f(n-1)、f(n-2) 的结果,层层向下拆解,直到 f(1)、f(2) 等已知结果的终止条件,再层层回溯计算出最终结果。

    递归底层原理:递归的每一次函数调用,都会在内存栈中开辟新的栈帧,保存当前函数的执行上下文。如果没有合理的终止条件,会无限创建栈帧,最终导致栈溢出(爆栈)。

    2.2 二叉树的标准递归定义

    结合递归思维,我们可以给出二叉树最严谨的定义(行业通用标准):

  • 空树:没有任何节点,是合法的二叉树(递归终止条件)
  • 非空树:由一个根节点 + 左子树 + 右子树 三部分组成
  • 递归约束:左子树、右子树本身,也必须是合法的二叉树(可以是空树)
  • ⚠️ 核心易错点:二叉树绝对不能简单定义为「每个节点最多有两个子节点的树」。普通树的子节点无顺序区分,但二叉树的左子树、右子树位置严格固定,不可互换,左右调换后是两棵完全不同的二叉树。

    三、二叉树的关键核心概念

    掌握基础概念是做题和写代码的前提,本节统一行业标准定义,规避认知误区。

    3.1 层次

    层级从根节点开始计数:根节点为第 1 层,根节点的子节点为第 2 层,以此类推,逐层向下递增。层级是层序遍历的核心依据。

    3.2 高度与深度

    这两个概念极易混淆,记住统一标准:

    • 高度(自下而上):叶子节点高度为 1,每向上一层高度 +1,指节点到最底部叶子的最长路径长度
    • 深度(自上而下):根节点深度为 1,每向下一层深度 +1,指节点到根节点的路径长度

    3.3 节点的度

    一个节点拥有的子树数量,称为该节点的度。

    • 度为 0:没有子节点 → 叶子节点(二叉树末端节点)
    • 度为 1:只有左子树或只有右子树
    • 度为 2:同时拥有左、右子树

    四、JavaScript 中二叉树的存储结构

    JS 中没有原生的二叉树结构,我们通过自定义节点类/对象模拟,每个二叉树节点固定包含三部分,完美匹配递归结构:

  • 数据域:存储节点自身的值(val)
  • 左引用:指向左子树节点(left,默认 null)
  • 右引用:指向右子树节点(right,默认 null)
  • 4.1 标准节点构造函数

    // 二叉树节点构造函数(行业标准写法)
    function TreeNode(val) {
    // 数据域:存储节点值
    this.val = val;
    // 左子节点引用:默认空
    this.left = null;
    // 右子节点引用:默认空
    this.right = null;
    }

    4.2 完整二叉树实例搭建

    基于上述构造函数,搭建一颗标准测试二叉树(后文所有遍历算法均基于此树测试):

    树结构示意:

    A
    / \\
    B C
    / \\ / \\
    D E F G

    // 手动构建完整二叉树
    const root = new TreeNode('A');
    root.left = new TreeNode('B');
    root.right = new TreeNode('C');
    root.left.left = new TreeNode('D');
    root.left.right = new TreeNode('E');
    root.right.left = new TreeNode('F');
    root.right.right = new TreeNode('G');

    同时提供字面量写法(直观易懂,适合新手调试):

    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 }
    }
    };

    五、二叉树四大遍历算法(核心实战)

    遍历是二叉树所有算法的基础(求和、求高度、查找节点、判断对称等功能,均基于遍历实现)。二叉树遍历分为两大类:深度优先遍历(递归)、广度优先遍历(迭代)。 深度优先遵循 先左后右 的固定规则,根据「根节点访问时机」分为前、中、后序遍历。

    5.1 前序遍历(根 → 左 → 右)

    规则:先访问当前根节点,再递归遍历左子树,最后递归遍历右子树

    /**
    * 前序遍历:根 → 左 → 右
    * @param {TreeNode} tree – 二叉树根节点
    */

    function preorderOrder(tree) {
    // 递归终止条件:空节点直接返回
    if (tree === null) return;
    // 1. 访问当前根节点
    console.log('当前遍历节点值:', tree.val);
    // 2. 递归遍历左子树
    preorderOrder(tree.left);
    // 3. 递归遍历右子树
    preorderOrder(tree.right);
    }

    // 测试执行
    preorderOrder(root);
    // 输出结果:A B D E C F G

    5.2 中序遍历(左 → 根 → 右)

    规则:先递归遍历左子树,再访问当前根节点,最后递归遍历右子树

    /**
    * 中序遍历:左 → 根 → 右
    * @param {TreeNode} tree – 二叉树根节点
    */

    function InorderOrder(tree) {
    // 递归终止条件:空节点直接返回
    if (tree === null) return;
    // 1. 递归遍历左子树
    InorderOrder(tree.left);
    // 2. 访问当前根节点
    console.log('当前遍历节点值:', tree.val);
    // 3. 递归遍历右子树
    InorderOrder(tree.right);
    }

    // 测试执行
    InorderOrder(root);
    // 输出结果:D B E A F C G

    ⚠️ 原代码易错点修复:新手常误写为调用前序递归,本文已修正为对应中序递归调用,保证逻辑正确。

    5.3 后序遍历(左 → 右 → 根)

    规则:先递归遍历左子树,再递归遍历右子树,最后访问当前根节点

    /**
    * 后序遍历:左 → 右 → 根
    * @param {TreeNode} tree – 二叉树根节点
    */

    function PostorderOrder(tree) {
    // 递归终止条件:空节点直接返回
    if (tree === null) return;
    // 1. 递归遍历左子树
    PostorderOrder(tree.left);
    // 2. 递归遍历右子树
    PostorderOrder(tree.right);
    // 3. 访问当前根节点
    console.log('当前遍历节点值:', tree.val);
    }

    // 测试执行
    PostorderOrder(root);
    // 输出结果:D E B F G C A

    5.4 层序遍历(广度优先迭代)

    层序遍历英文名 Level Order Traversal,属于广度优先遍历(BFS),不使用递归,核心依赖队列(先进先出)实现,按层级从上到下、从左到右遍历所有节点。

    修复原代码所有 Bug,提供可直接运行的标准完整版:

    /**
    * 层序遍历(广度优先)
    * @param {TreeNode} root – 二叉树根节点
    * @returns {Array} 层序遍历结果数组
    */

    function levelOrder(root) {
    const result = []; // 存储最终遍历结果
    const queue = []; // 队列:存储待遍历节点

    // 空树直接返回空数组
    if (root === null) return result;

    // 根节点入队
    queue.push(root);

    // 队列不为空则持续遍历
    while (queue.length) {
    // 队首节点出队(当前层首个节点)
    const node = queue.shift();
    // 记录当前节点值
    result.push(node.val);

    // 左子节点优先入队
    if (node.left !== null) queue.push(node.left);
    // 右子节点后入队
    if (node.right !== null) queue.push(node.right);
    }

    return result;
    }

    // 测试执行
    console.log(levelOrder(root));
    // 输出结果:['A', 'B', 'C', 'D', 'E', 'F', 'G']

    六、递归算法实战:LeetCode 70 爬楼梯

    为了彻底吃透二叉树依赖的递归思想,我们结合经典算法题实战,验证递归公式、终止条件的落地逻辑。

    6.1 题目描述

    假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶?

    6.2 递归思路分析

  • 问题拆解(递归公式):爬到第 n 阶的方法数 = 爬到第 n-1 阶的方法数(最后爬1阶) + 爬到第 n-2 阶的方法数(最后爬2阶),即 f(n) = f(n-1) + f(n-2)
  • 终止条件:n=1 只有1种方法,n=2 有2种方法
  • 6.3 完整可运行代码

    /**
    * @param {number} n – 楼梯阶数
    * @return {number} 总方法数
    */

    var climbStairs = function(n) {
    // 递归终止条件
    if (n === 1) return 1;
    if (n === 2) return 2;
    // 递归公式:拆解子问题
    return climbStairs(n 1) + climbStairs(n 2);
    };

    // 测试
    console.log(climbStairs(3)); // 输出 3
    console.log(climbStairs(5)); // 输出 8

    💡 核心关联:该问题的拆解过程,完全是二叉树的递归分支逻辑,也是所有树递归算法的基础思维。 但是要注意,递归如果数据太大可能会导致栈溢出的结果,该题更适合用动态规划的思路解题

    七、全文总结

    本文从现实类比出发,循序渐进讲解了树与二叉树的完整知识体系,覆盖「概念定义-底层原理-代码实现-算法实战」全链路。核心围绕二叉树的递归本质展开,所有树的遍历、计算、查找算法,底层都是递归拆解子问题的思维。

    八、核心知识点复盘

  • 二叉树是典型递归结构:空树为终止条件,非空树由「根节点+左子树+右子树」组成,左右子树不可互换
  • 递归两大核心:递推公式(拆解子问题)、终止条件(防止爆栈)
  • 二叉树四大遍历:前/中/后序(递归深度优先)、层序(队列迭代广度优先)
  • JS 二叉树核心结构:数据域+左引用+右引用,是所有代码实现的基础
  • 节点度、层次、高度、深度是区分二叉树状态的核心指标,叶子节点度为0
  • 九、常见问题与避坑指南

    • 误区1:认为二叉树是「每个节点最多两个子节点」 ✅ 纠正:核心是左右子树有序,位置不可交换,这是二叉树与普通树的本质区别
    • 误区2:递归不写终止条件 ✅ 后果:无限递归导致栈溢出,所有树递归必须优先写终止条件
    • 误区3:遍历递归调用混淆 ✅ 坑点:中序、后序遍历容易误调用前序递归,导致遍历顺序错乱,必须严格匹配对应递归函数
    • 误区4:层序遍历队列操作出错 ✅ 常见Bug:忘记出队、入队左右节点写反、结果数组无值写入,严格遵循「出队-记录-入队」流程
    • 性能坑:纯递归实现爬楼梯、树遍历会存在大量重复计算,高阶场景可配合记忆化优化
    赞(0)
    未经允许不得转载:171主机测评 » 吃透树与二叉树:从递归原理到 JS 全场景遍历实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址