从0到1搞懂二叉树遍历:一篇让前端人秒懂的保姆级解析

136 阅读7分钟

作为前端开发者,你可能在面试时被问过「二叉树遍历」,也可能在刷LeetCode时被各种遍历题难住。但你有没有想过:为什么这道题能成为高频考点?其实答案很简单——二叉树的遍历是理解树结构、递归思想、甚至后端数据结构的基础。今天咱们就用最接地气的方式,一次性搞定所有遍历方式。


一、为什么先学二叉树?它到底长什么样?

要理解二叉树,先想象一个公司的组织架构:老板(根节点)下面有两个直属部门(左子树、右子树),每个部门又有自己的下属(子节点)。这种「一个节点最多有两个子节点」的结构,就是二叉树的核心特征。

举个具体的例子,假设有一个二叉树结构如下:

    A
   / \
  B   C
 / \   \
D   E   F

这里的每个字母都是一个节点,A是根节点,B是左子节点,C是右子节点,依此类推。这棵树的结构完美符合二叉树的定义:每个节点最多有两个子节点,且左右子树严格区分(比如B的左子节点是D,右是E,顺序不能调换)。


二、遍历的本质:给节点「打卡」的顺序

所谓「遍历」,就是按照某种规则访问树中的每个节点一次。就像你去游乐园玩,需要按路线打卡每个项目。二叉树的遍历主要分两大类:

  1. 深度优先遍历(DFS):一条路走到底,再回头(先序、中序、后序)
  2. 广度优先遍历(BFS):按层访问,逐层推进(层序遍历)

咱们先从最常用的深度优先遍历开始。


(一)深度优先三兄弟:先序、中序、后序

这三个遍历方式的名字里藏着关键信息——根节点的访问顺序

1. 先序遍历:根→左→右(先打根节点卡)

先序遍历的规则是:先访问根节点,再递归遍历左子树,最后递归遍历右子树。用上面的例子,遍历顺序应该是:

  • 第一步:访问根节点A
  • 第二步:递归左子树B(此时B成为当前子树的根)
    • 访问B,然后递归B的左子树DD是叶子节点,无左右子树,直接访问D
    • 回到B的右子树E,访问E
  • 第三步:递归右子树CC是当前子树的根)
    • 访问C,然后递归C的右子树F(访问F

最终顺序是:A → B → D → E → C → F

看看代码怎么实现:

function preorder(root) {
  // 退出条件:当前节点为空时返回
  if (!root) return;
  // 先访问当前节点(根)
  console.log(root.val); 
  // 递归左子树
  preorder(root.left);
  // 递归右子树
  preorder(root.right);
}

代码的核心逻辑完美对应了「根→左→右」的顺序:先处理当前节点,再处理左,最后处理右。

2. 中序遍历:左→根→右(中间打根节点卡)

中序遍历的规则是:先递归遍历左子树,再访问根节点,最后递归遍历右子树。还是用上面的例子:

  • 第一步:递归左子树B(此时B是当前子树的根)
    • 递归B的左子树DD无左子树,访问D
    • 访问B(此时B的左子树已处理完)
    • 递归B的右子树E(访问E
  • 第二步:访问根节点A(此时左子树已处理完)
  • 第三步:递归右子树CC是当前子树的根)
    • C无左子树,直接访问C
    • 递归C的右子树F(访问F

最终顺序是:D → B → E → A → C → F

对应的代码实现:

function inorder(root) {
  if (!root) return;
  // 先递归左子树
  inorder(root.left);
  // 中间访问当前节点(根)
  console.log(root.val);
  // 最后递归右子树
  inorder(root.right);
}

这里的关键是把「访问当前节点」的操作放在了递归左子树之后、递归右子树之前,完美体现「左→根→右」。

3. 后序遍历:左→右→根(最后打根节点卡)

后序遍历的规则是:先递归遍历左子树,再递归遍历右子树,最后访问根节点。继续用例子验证:

  • 第一步:递归左子树B
    • 递归B的左子树D(访问D
    • 递归B的右子树E(访问E
    • 访问B(此时B的左右子树都处理完)
  • 第二步:递归右子树C
    • 递归C的右子树F(访问F
    • 访问C(此时C的左右子树处理完)
  • 第三步:访问根节点A(此时左右子树都处理完)

最终顺序是:D → E → B → F → C → A

代码实现:

function postorder(root) {
  if (!root) return;
  // 先递归左子树
  postorder(root.left);
  // 再递归右子树
  postorder(root.right);
  // 最后访问当前节点(根)
  console.log(root.val);
}

这里「访问当前节点」的操作被放在了最后,严格遵循「左→右→根」。

三兄弟对比表

为了帮你快速区分,我做了个对比表:

image.png image.png


(二)广度优先:层序遍历(按层打卡)

深度优先的三个兄弟是「一条路走到底」,而层序遍历则是「按层访问」。想象一下你站在树的顶端,从上到下、从左到右依次扫描每一层的节点。

用前面的例子,层序遍历的顺序是:

  • 第一层:A
  • 第二层:BC
  • 第三层:DEF

最终顺序是:A → B → C → D → E → F

层序遍历的实现需要借助「队列」(FIFO,先进先出)。具体步骤如下:

  1. 将根节点入队
  2. 循环取出队首节点,访问它
  3. 将该节点的左子节点和右子节点(如果存在)依次入队
  4. 重复步骤2-3,直到队列为空

代码实现:

function levelOrderTraversal(root) {
  if (!root) return [];
  const result = [];
  const queue = [root]; // 初始化队列,根节点入队
  while (queue.length > 0) {
    const currentNode = queue.shift(); // 取出队首节点
    result.push(currentNode.val);
    // 左子节点入队
    if (currentNode.left) queue.push(currentNode.left);
    // 右子节点入队
    if (currentNode.right) queue.push(currentNode.right);
  }
  return result;
}

这里的关键是用队列维护「当前层的节点」,每次处理完一个节点就把它的子节点加入队列,确保下一层节点能被顺序处理。


三、为什么要学这么多遍历方式?它们的实际用途

你可能会问:「学这么多遍历方式,实际开发中用得到吗?」答案是:不仅用得到,而且场景各不相同

  • 先序遍历:适合「从上到下」的操作,比如拷贝整棵树的结构(先创建根节点,再创建左子树,最后右子树)、解析JSON(先处理根字段,再处理子字段)。
  • 中序遍历:二叉搜索树(BST)的中序遍历结果是有序的,这在排序和查找中非常有用。
  • 后序遍历:适合「从下到上」的操作,比如计算文件大小(先计算所有子文件大小,最后汇总父文件夹)、垃圾回收(先回收子节点内存,再回收父节点)。
  • 层序遍历:适合按层处理的场景,比如获取二叉树的最大宽度、判断是否为完全二叉树。

四、学习心得:如何避免混淆?

刚开始学的时候,我也经常把先序、中序、后序搞混。后来总结了两个方法:

1. 画图+手动模拟

遇到复杂的树结构时,先画出树的形状,然后用不同颜色的笔标注访问顺序。比如先序用红色,中序用蓝色,后序用绿色,直观看到区别。

2. 记住「递归的本质是栈」

递归的过程其实隐式维护了一个调用栈。以先序遍历为例,代码执行时会先处理根节点,然后把左子树压入栈顶,处理完左子树再处理右子树——这和栈「后进先出」的特性一致。理解了这一点,就能更清晰地把握递归的执行顺序。


五、总结

二叉树的遍历是前端工程师的基础技能,更是打开算法世界的一把钥匙。无论是面试中的高频题,还是实际开发中的场景(如虚拟DOM的遍历、树形结构的操作),都需要扎实的遍历基础。

最后再帮你总结一遍:

  • 深度优先三兄弟:先序(根左右)、中序(左根右)、后序(左右根),用递归实现。
  • 广度优先:层序(按层访问),用队列实现。
  • 实际应用:根据业务需求选择合适的遍历方式。

下次遇到二叉树的题,不妨先画个图,手动模拟一遍遍历过程——你会发现,问题往往迎刃而解。