从零理解树与二叉树 —— 概念、实现与遍历
2026/6/13 22:45:12 网站建设 项目流程

💡一、什么是树?

数据结构中的"树",是对现实世界中树的抽象和简化:

  • 根节点:对应现实中的树根,是一切的起点
  • :对应现实中的树枝,连接两个节点
  • 叶子节点:对应现实中的树叶,是树的末端

把现实中的树倒过来看,就是数据结构中树的样子:从根节点开始,每个节点延伸出一个或多个子节点,层层向下,直到叶子节点为止。

A ← 根节点(第 1 层) / \ B C ← 第 2 层 / \ / \ D E F G ← 第 3 层(叶子节点)

🧠关键概念速览

概念说明
层次根节点为第 1 层,其子节点为第 2 层,依此类推
高度叶子节点高度为 1,每向上一层 +1
一个节点拥有的子树数量
叶子节点度为 0 的节点,即没有子节点的节点
深度从根节点到目标节点的路径长度

⚖️二、二叉树

📚定义

二叉树是每个节点最多有两个子节点的树。它的定义天然适合用递归来描述:

一棵二叉树要么是空树,要么由根节点左子树右子树三部分组成,且左右子树本身也都是二叉树。

这里有两点值得注意:

  1. 左右严格区分:二叉树中左子树和右子树的位置是严格约定的,不能随意交换。左右互换后是两棵不同的二叉树。
  2. 度 ≤ 2 不等于二叉树:二叉树不能简单定义为"每个节点度最大为 2 的树",因为普通树不区分左右,而二叉树区分。

📚在 JavaScript 中表示二叉树

一个二叉树节点由三部分构成:数据域+左子节点引用+右子节点引用

functionTreeNode(val){this.val=val;this.left=this.right=null;// 左右子节点初始为空}

构建一棵具体的二叉树:

consttree={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}}};

这棵二叉树的结构如下:

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

📚多叉树的表示

如果不是二叉树,而是一个节点可以有任意多个子节点的情况,可以用children数组来表示:

consttree={value:'A',children:[{value:'B',children:[{value:'D',children:[]},{value:'E',children:[]}]},{value:'C',children:[{value:'F',children:[]},{value:'G',children:[]}]}]};

两种表示方式的选择取决于实际场景:二叉树用left/right语义更清晰,多叉树用children数组更灵活。


🎯三、二叉树的遍历

遍历是树最核心的操作。按照访问根节点的时机不同,分为四种遍历方式。递归遍历中,左右子树的访问顺序永远是先左后右,区别只在于根节点何时被访问。

✨3.1 前序遍历(根 → 左 → 右)

先访问根节点,再递归访问左子树,最后递归访问右子树。

functionpreorder(root){if(!root){return;// 退出条件:空节点}console.log(root.val);// 先访问根preorder(root.left);// 再访问左子树preorder(root.right);// 最后访问右子树}

对示例树调用preorder(tree),输出顺序为:A → B → D → E → C → F → G

动态演示

✨3.2 中序遍历(左 → 根 → 右)

先递归访问左子树,再访问根节点,最后递归访问右子树。

functioninorder(root){if(!root){return;// 退出条件:空节点}inorder(root.left);// 先访问左子树console.log(root.val);// 再访问根inorder(root.right);// 最后访问右子树}

输出顺序为:D → B → E → A → F → C → G

二叉搜索树的中序遍历结果是有序的,这是中序遍历的一个重要特性。

✨3.3 后序遍历(左 → 右 → 根)

先递归访问左子树,再递归访问右子树,最后访问根节点。

functionpostorder(root){if(!root){return;// 退出条件:空节点}postorder(root.left);// 先访问左子树postorder(root.right);// 再访问右子树console.log(root.val);// 最后访问根}

输出顺序为:D → E → B → F → G → C → A

后序遍历的一个典型应用场景是删除树:先删除子节点,最后删除父节点,避免"删了父节点找不到子节点"的问题。

✨3.4 层序遍历(广度优先)

层序遍历不使用递归,而是借助队列实现逐层访问。

functionlevelorder(root){constqueue=[];// 辅助队列constresult=[];// 存放遍历结果if(!root){returnresult;}queue.push(root);// 根节点入队while(queue.length>0){constnode=queue.shift();// 队首出队result.push(node.val);// 访问当前节点if(node.left){queue.push(node.left);// 左子节点入队}if(node.right){queue.push(node.right);// 右子节点入队}}returnresult;}

输出顺序为:A → B → C → D → E → F → G(逐层从左到右)。

✅四种遍历对比

遍历方式访问顺序实现方式示例输出
前序根 → 左 → 右递归A B D E C F G
中序左 → 根 → 右递归D B E A F C G
后序左 → 右 → 根递归D E B F G C A
层序逐层从左到右队列迭代A B C D E F G

📌四、递归思想 —— 从爬楼梯理解树状结构

树和递归是天然绑定的一对概念。递归的核心三要素:

  1. 自顶向下思考:把大问题拆成小问题
  2. 找到递归公式:每次解决的问题模式相同
  3. 明确退出条件:递归不能无限进行

爬楼梯问题

假设你正在爬楼梯,每次可以爬 1 阶或 2 阶。爬到第n阶有多少种不同的方法?

这个问题从顶向下看,呈现出典型的树状结构

f(10) / \ f(9) f(8) / \ / \ f(8) f(7) f(7) f(6) ... ... ... ... 最终: f(1) = 1 种 f(2) = 2 种
  • 递归公式f(n) = f(n-1) + f(n-2)
  • 退出条件n ≤ 2时,f(1) = 1f(2) = 2
functionclimbStairs(n){if(n<=2){returnn;// 退出条件}leta=climbStairs(n-1);// 最后一步走 1 阶letb=climbStairs(n-2);// 最后一步走 2 阶returna+b;// 递归公式}console.log(climbStairs(10));// 89// ⚠️ 注意:这种朴素递归当 n 较大时(如 n=100)会卡死// 原因:大量重复计算,时间复杂度 O(2ⁿ),函数调用栈也可能爆栈

⚡递归的隐患

  • 爆栈:每次递归调用都会在调用栈中压入一个新的函数帧。递归过深时,栈内存会被耗尽。
  • 重复计算:从上图可以看到,f(8)被计算了两次,f(7)被计算了三次……随 n 增大,重复量呈指数级增长。

实际工程中,可以用记忆化搜索(缓存已计算的结果)或动态规划(自底向上迭代)来优化,这里不展开。


🧩五、总结

整篇文章的核心脉络可以归纳为一条线:

现实中的树 → 数据结构的树 → 二叉树 → JS 中的表示 → 遍历(递归 / 迭代) → 递归思维(爬楼梯)
  1. 是递归定义的,天然适合用递归思维去理解和操作
  2. 二叉树的四种遍历(前序、中序、后序、层序)是树操作的基础,三种深度优先遍历的区别仅在于"访问根节点的时机"
  3. 递归三要素(自顶向下、递归公式、退出条件)不仅是树的解题框架,也是所有递归问题的通用心法
  4. 递归代码简洁优雅,但要警惕重复计算爆栈的风险
标签:树与二叉树

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询