前端数据结构:二叉树入门 + 四大遍历 + 递归思想实战(附爬楼梯案例)
前言
在前端算法与数据结构学习中,二叉树 是高频考点,而递归是解决二叉树问题的核心思想。本文结合 JavaScript 代码,从零讲解二叉树基础概念、节点封装、经典四大遍历(前 / 中 / 后 / 层序),同时搭配递归经典例题「爬楼梯」,梳理递归解题通用思路,适合零基础入门、日常复习。
一、树与二叉树基础概念
1. 通用树基本概念
数据结构中的「树」是对现实树形结构的抽象,由多个节点通过上下级关系组成,核心术语如下:
- 根节点:树最顶层节点,没有父节点,是整棵树的唯一入口。
- 父节点 / 子节点:直接相连的上下节点,上层为父,下层为子。
- 叶子节点:没有任何子节点的末端节点(节点度为 0)。
- 节点度:一个节点拥有的子节点数量。
- 层级:根节点默认为第 1 层,向下每一级层级 +1。
- 树的高度:从最底层叶子节点向上计数,叶子节点高度为 1,逐层累加至根节点。
2. 二叉树专属定义
二叉树是特殊的树结构,有严格规则:
- 可以是空树(无任何节点);
- 非空二叉树由根节点 + 左子树 + 右子树组成;
- 左右子树严格区分,位置不能互换(这是二叉树和普通树最大区别);
- 每个节点的子节点数量最多为 2 个。
核心特点:所有二叉树的子树,本身也都是二叉树,天然适合用递归处理。
3. JavaScript 封装二叉树节点
在 JS 中,我们用构造函数模拟二叉树单个节点,每个节点固定包含 3 部分:
val:数据域,存储节点内容;left:引用类型,指向左子节点 ,无子节点则为null;right:引用类型,指向右子节点 ,无子节点则为null。
节点构造函数代码
javascript
运行
kotlin
// 二叉树节点构造器
function TreeNode(val) {
this.val = val; // 节点存储的数据
this.left = null; // 左子节点,默认空
this.right = null;// 右子节点,默认空
}
4. 手动构建一棵完整二叉树
基于上面的节点构造函数,我们创建节点并建立父子引用关系,构建如下树形结构:
plaintext
css
a
/ \
b c
/ \ / \
d e f g
完整代码:
javascript
运行
ini
// 1. 创建独立节点
const a = new TreeNode("a");
const b = new TreeNode("b");
const c = new TreeNode("c");
const d = new TreeNode("d");
const e = new TreeNode("e");
const f = new TreeNode("f");
const g = new TreeNode("g");
// 2. 建立左右子节点引用(左右不可互换)
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
二、二叉树四大遍历(JS 完整实现)
二叉树遍历 = 按照特定规则,访问树中每一个节点且仅访问一次 。分为两大类:递归深度优先遍历 (前、中、后序)、迭代广度优先遍历(层序)。
通用规则:深度优先遍历默认遵循 先左子树,后右子树。
1. 前序遍历(根 → 左 → 右)
遍历规则 :先访问当前根节点 → 递归遍历左子树 → 递归遍历右子树。适用场景:快速复制二叉树、获取树结构前缀。
代码实现
javascript
运行
scss
function preOrder(root) {
// 递归终止条件:空节点,直接返回
if (!root) return;
console.log(root.val); // 1. 访问根节点
preOrder(root.left); // 2. 遍历左子树
preOrder(root.right); // 3. 遍历右子树
}
// 传入根节点 a,遍历整棵树
preOrder(a);
输出结果 :a b d e c f g
2. 中序遍历(左 → 根 → 右)
遍历规则 :递归遍历左子树 → 访问当前根节点 → 递归遍历右子树。适用场景:二叉搜索树排序(最常用)。
代码实现
javascript
运行
scss
function inOrder(root) {
if (!root) return;
inOrder(root.left); // 1. 遍历左子树
console.log(root.val); // 2. 访问根节点
inOrder(root.right); // 3. 遍历右子树
}
inOrder(a);
输出结果 :d b e a f c g
3. 后序遍历(左 → 右 → 根)
遍历规则 :递归遍历左子树 → 递归遍历右子树 → 访问当前根节点。适用场景:删除二叉树、计算树高度。
代码实现
javascript
运行
scss
function postOrder(root) {
if (!root) return;
postOrder(root.left); // 1. 遍历左子树
postOrder(root.right); // 2. 遍历右子树
console.log(root.val); // 3. 访问根节点
}
postOrder(a);
输出结果 :d e b f g c a
4. 层序遍历(广度优先,队列实现)
前三种为递归深度优先 ,层序遍历是迭代广度优先 ,不使用递归 ,依靠队列实现。
遍历规则 :从上到下、同一层级从左到右依次访问节点。核心原理:队列「先进先出」,取出队首节点,再将其左右子节点追加到队尾。
代码实现
javascript
运行
scss
function levelOrder(root) {
const queue = []; // 模拟队列:存放待遍历节点
const res = []; // 存储遍历结果
// 空树直接返回空数组
if (!root) return res;
queue.push(root); // 根节点入队,作为遍历起点
// 队列不为空则持续循环
while (queue.length) {
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 arr = levelOrder(a);
console.log(arr);
输出结果 :[ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]
四大遍历总结表
表格
| 遍历方式 | 执行顺序 | 实现方式 | 核心特点 |
|---|---|---|---|
| 前序 | 根 → 左 → 右 | 递归 | 先读根节点 |
| 中序 | 左 → 根 → 右 | 递归 | 根节点在中间 |
| 后序 | 左 → 右 → 根 | 递归 | 最后读根节点 |
| 层序 | 从上到下、从左到右 | 队列迭代 | 按层级遍历 |
三、递归思想详解(二叉树核心思想)
二叉树天然适合递归,我们结合遍历代码 + 经典例题「爬楼梯」,总结递归通用解题模板。
1. 递归核心定义
递归:函数自身调用自身,把一个「大规模问题」拆解为多个「结构相同的小规模子问题」,逐层求解。
2. 递归解题两大必备条件(重中之重)
- 递归公式:找到大问题与子问题的关系(规律);
- 递归终止条件(出口) :最小规模问题的固定解,防止无限递归、栈溢出。
3. 递归执行特点
- 自顶向下拆解问题,像树状结构逐层拆分;
- 依赖函数调用栈,递归层数过大会造成栈溢出;
- 简单直观、代码简洁,但纯递归容易产生大量重复计算。
4. 递归实战案例:爬楼梯
题目描述
假设需要爬 n 阶楼梯,每次只能爬 1 阶或 2 阶,求一共有多少种不同爬法。
步骤 1:推导递归公式
想爬到第 n 阶,只有两种最后一步:
- 最后一步爬 1 阶:前面需要爬到
n-1阶; - 最后一步爬 2 阶:前面需要爬到
n-2阶。
因此得出公式:(F(n) = F(n-1) + F(n-2))
步骤 2:确定递归终止条件
最小规模问题,直接给出结果:
- 当
n = 1(1 阶楼梯):只有 1 种爬法,F(1) = 1; - 当
n = 2(2 阶楼梯):1+1、2,共 2 种爬法,F(2) = 2。
步骤 3:完整递归代码
javascript
运行
scss
function climbStairs(n) {
// 递归终止条件(最小问题解)
if (n === 1) return 1;
if (n === 2) return 2;
// 递归公式:大问题拆分为两个子问题求和
return climbStairs(n - 1) + climbStairs(n - 2);
}
// 测试:3阶楼梯,结果为 3
console.log(climbStairs(3));
// 测试:8阶楼梯,结果为 34
console.log(climbStairs(8));
步骤 4:执行流程演示(以 n=8 为例)
plaintext
scss
F(8) = F(7) + F(6)
F(7) = F(6) + F(5)
F(6) = F(5) + F(4)
F(5) = F(4) + F(3)
F(4) = F(3) + F(2)
F(3) = F(2) + F(1) = 2 + 1 = 3
// 逐层向上汇总,最终 F(8) = 34
5. 递归通用解题模板(复习背诵)
- 分析问题:判断是否可以拆分为同结构子问题;
- 找规律:推导递归公式;
- 定边界:设置递归终止条件;
- 编写代码:函数内部调用自身,结合终止条件返回结果。
四、整体复习总结
1. 二叉树核心要点
- 节点结构:
val存数据 +left/right存子节点引用,无子节点为null; - 二叉树左右子树不可互换,所有子树都是二叉树;
- 深度优先(前 / 中 / 后序)用递归 ,广度优先(层序)用队列迭代。
2. 遍历记忆口诀
- 前序:根左右
- 中序:左根右
- 后序:左右根
- 层序:从上到下、从左到右
3. 递归核心要点
- 两大要素:递归公式 + 终止条件,缺一不可;
- 适用场景:问题可拆分、子问题和原问题结构一致(二叉树、斐波那契、爬楼梯等);
- 缺点:纯递归重复计算多、易栈溢出,复杂场景建议改用循环迭代优化。
五、完整整合代码(可直接运行复习)
将本文所有代码整合,复制到浏览器控制台 / Node.js 即可运行测试:
javascript
运行
ini
// 二叉树节点构造器
function TreeNode(val) {
this.val = val;
this.left = null;
this.right = null;
}
// 构建二叉树
const a = new TreeNode("a");
const b = new TreeNode("b");
const c = new TreeNode("c");
const d = new TreeNode("d");
const e = new TreeNode("e");
const f = new TreeNode("f");
const g = new TreeNode("g");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
// 前序遍历
function preOrder(root) {
if (!root) return;
console.log("前序:", root.val);
preOrder(root.left);
preOrder(root.right);
}
preOrder(a);
// 中序遍历
function inOrder(root) {
if (!root) return;
inOrder(root.left);
console.log("中序:", root.val);
inOrder(root.right);
}
inOrder(a);
// 后序遍历
function postOrder(root) {
if (!root) return;
postOrder(root.left);
postOrder(root.right);
console.log("后序:", root.val);
}
postOrder(a);
// 层序遍历
function levelOrder(root) {
const queue = [];
const res = [];
if (!root) return res;
queue.push(root);
while (queue.length) {
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 arr = levelOrder(a);
console.log("层序结果:", arr);
// 爬楼梯-递归
function climbStairs(n) {
if (n === 1) return 1;
if (n === 2) return 2;
return climbStairs(n - 1) + climbStairs(n - 2);
}
console.log("8阶楼梯爬法:", climbStairs(8));