二叉树从入门到实战:四大遍历 + 递归思想详解

前端数据结构:二叉树入门 + 四大遍历 + 递归思想实战(附爬楼梯案例)

前言

在前端算法与数据结构学习中,二叉树 是高频考点,而递归是解决二叉树问题的核心思想。本文结合 JavaScript 代码,从零讲解二叉树基础概念、节点封装、经典四大遍历(前 / 中 / 后 / 层序),同时搭配递归经典例题「爬楼梯」,梳理递归解题通用思路,适合零基础入门、日常复习。

一、树与二叉树基础概念

1. 通用树基本概念

数据结构中的「树」是对现实树形结构的抽象,由多个节点通过上下级关系组成,核心术语如下:

  1. 根节点:树最顶层节点,没有父节点,是整棵树的唯一入口。
  2. 父节点 / 子节点:直接相连的上下节点,上层为父,下层为子。
  3. 叶子节点:没有任何子节点的末端节点(节点度为 0)。
  4. 节点度:一个节点拥有的子节点数量。
  5. 层级:根节点默认为第 1 层,向下每一级层级 +1。
  6. 树的高度:从最底层叶子节点向上计数,叶子节点高度为 1,逐层累加至根节点。

2. 二叉树专属定义

二叉树是特殊的树结构,有严格规则:

  1. 可以是空树(无任何节点);
  2. 非空二叉树由根节点 + 左子树 + 右子树组成;
  3. 左右子树严格区分,位置不能互换(这是二叉树和普通树最大区别);
  4. 每个节点的子节点数量最多为 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. 递归解题两大必备条件(重中之重)

  1. 递归公式:找到大问题与子问题的关系(规律);
  2. 递归终止条件(出口) :最小规模问题的固定解,防止无限递归、栈溢出。

3. 递归执行特点

  1. 自顶向下拆解问题,像树状结构逐层拆分;
  2. 依赖函数调用栈,递归层数过大会造成栈溢出
  3. 简单直观、代码简洁,但纯递归容易产生大量重复计算。

4. 递归实战案例:爬楼梯

题目描述

假设需要爬 n 阶楼梯,每次只能爬 1 阶或 2 阶,求一共有多少种不同爬法。

步骤 1:推导递归公式

想爬到第 n 阶,只有两种最后一步:

  1. 最后一步爬 1 阶:前面需要爬到 n-1 阶;
  2. 最后一步爬 2 阶:前面需要爬到 n-2 阶。

因此得出公式:(F(n) = F(n-1) + F(n-2))

步骤 2:确定递归终止条件

最小规模问题,直接给出结果:

  • n = 1(1 阶楼梯):只有 1 种爬法,F(1) = 1
  • n = 2(2 阶楼梯):1+12,共 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. 分析问题:判断是否可以拆分为同结构子问题
  2. 找规律:推导递归公式
  3. 定边界:设置递归终止条件
  4. 编写代码:函数内部调用自身,结合终止条件返回结果。

四、整体复习总结

1. 二叉树核心要点

  1. 节点结构:val 存数据 + left/right 存子节点引用,无子节点为 null
  2. 二叉树左右子树不可互换,所有子树都是二叉树;
  3. 深度优先(前 / 中 / 后序)用递归 ,广度优先(层序)用队列迭代

2. 遍历记忆口诀

  • 前序:根左右
  • 中序:左根右
  • 后序:左右根
  • 层序:从上到下、从左到右

3. 递归核心要点

  1. 两大要素:递归公式 + 终止条件,缺一不可;
  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));
相关推荐
渣波1 小时前
全栈开发的“影分身”之术(mock):别再手动造数据了,你的 CRUD 不配让我等!
前端·javascript
亿元程序员1 小时前
小伙伴说这个撕胶带游戏很火很解压,于是我连夜做了一个Cocos教程...
前端
如果超人不会飞1 小时前
一文读懂 TinyRobot:前端 AI 组件库定位、价值与适用场景
前端·vue.js
如果超人不会飞1 小时前
用TinyRobot Welcome组件打造贴心的AI助手欢迎页
前端·vue.js
悟空瞎说1 小时前
Compose内嵌Flutter混合开发详解:页面嵌入、引擎缓存与双向通信完整实战
前端
如果超人不会飞1 小时前
TinyRobot DragOverlay轻松实现AI对话中的拖拽上传
前端·vue.js
elirlove11 小时前
打造属于自己的网页工匠台:HTML在线编辑器技术深度解析
前端·编辑器·html
wh_xmy1 小时前
从HTML5到AI,我的前端十年
前端·程序人生·十年程序员·ai 对前端的影响
程序员mine1 小时前
Web服务密码存储安全详解:从哈希到密钥派生的演进
前端·后端