目录
- 一、树的基础认知:现实世界的抽象建模
- 二、二叉树的核心定义:基于递归的精准描述
-
- 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 中没有原生的二叉树结构,我们通过自定义节点类/对象模拟,每个二叉树节点固定包含三部分,完美匹配递归结构:
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 递归思路分析
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
💡 核心关联:该问题的拆解过程,完全是二叉树的递归分支逻辑,也是所有树递归算法的基础思维。 但是要注意,递归如果数据太大可能会导致栈溢出的结果,该题更适合用动态规划的思路解题
七、全文总结
本文从现实类比出发,循序渐进讲解了树与二叉树的完整知识体系,覆盖「概念定义-底层原理-代码实现-算法实战」全链路。核心围绕二叉树的递归本质展开,所有树的遍历、计算、查找算法,底层都是递归拆解子问题的思维。
八、核心知识点复盘
九、常见问题与避坑指南
- 误区1:认为二叉树是「每个节点最多两个子节点」 ✅ 纠正:核心是左右子树有序,位置不可交换,这是二叉树与普通树的本质区别
- 误区2:递归不写终止条件 ✅ 后果:无限递归导致栈溢出,所有树递归必须优先写终止条件
- 误区3:遍历递归调用混淆 ✅ 坑点:中序、后序遍历容易误调用前序递归,导致遍历顺序错乱,必须严格匹配对应递归函数
- 误区4:层序遍历队列操作出错 ✅ 常见Bug:忘记出队、入队左右节点写反、结果数组无值写入,严格遵循「出队-记录-入队」流程
- 性能坑:纯递归实现爬楼梯、树遍历会存在大量重复计算,高阶场景可配合记忆化优化

