二叉树遍历,其实就是在树上“摸球”

二叉树遍历,其实就是在树上“摸球”

别再死记硬背“根左右”了,用手指绕着树走一圈,什么都清楚了


一个让初学者崩溃的问题

学数据结构的时候,二叉树遍历大概是很多人第一次感到“我好像懂了,但一做题就错”的地方。

前序遍历?根左右。
中序遍历?左根右。
后序遍历?左右根。

口诀背得滚瓜烂熟,可一遇到下面这棵树:

A / \ B C / \ \ D E F

问你后序遍历是什么?你开始默念“左右根……左右根……”,先写D,再写E,然后B……不对,F和C什么时候写?A放在哪里?

脑子里的递归栈像一团乱麻,捋不清。

其实,不是你笨,而是这个方法本身就反直觉。今天,我们换一种方式来看遍历。保证你看完之后,再也不会搞混前序、中序和后序。


把树想象成一座过山车轨道

我们先做一个小小的思维实验。

请你把上面那棵二叉树,想象成一座过山车的轨道。节点A、B、C、D、E、F就是轨道的支柱,树枝就是连接支柱的轨道。

现在,我们要做一件非常简单的事:

从根节点A的左边出发,贴着轨道的外侧,逆时针走一圈,回到原点。

就这么简单。你不需要思考任何递归逻辑,只需要像小孩画圈一样,沿着树的边缘走一圈。

为了帮助理解,我们先在每个节点的左侧、正下方、右侧分别画上一个小球:

🔴A🔵 🟢 / \ 🔴B🔵 🔴C🔵 🟢 🟢 / \ \ 🔴D🔵 🔴E🔵 🔴F🔵 🟢 🟢 🟢
  • 🔴红色球= 节点左侧
  • 🟢绿色球= 节点正下方
  • 🔵蓝色球= 节点右侧

三种遍历,说白了就是三种不同的摸球规则

遍历方式摸什么颜色的球通俗说法
前序遍历🔴 只摸红色球只摸左边的球
中序遍历🟢 只摸绿色球只摸下边的球
后序遍历🔵 只摸蓝色球只摸右边的球

第一圈:前序遍历 —— 只摸红色球

伸出你的食指,放在A的左边,开始沿着树的边缘逆时针滑动。

这一圈,你只盯着红色的球。手指经过红色球时就记录,其他颜色一概忽略。

我们来走一遍:

  1. 从A左边出发 → 摸到A的红色球
  2. 往下走到B左边 → 摸到B的红色球
  3. 继续下到D左边 → 摸到D的红色球
  4. 绕过D底部(绿色球,忽略),绕到D右边(蓝色球,忽略),回到B
  5. 从B往右下走到E左边 → 摸到E的红色球
  6. 绕过E,回到B,回到A
  7. 从A往右下走到C左边 → 摸到C的红色球
  8. 继续走到F左边 → 摸到F的红色球

你摸到的红色球顺序是:

A → B → D → E → C → F

这就是前序遍历。


第二圈:中序遍历 —— 只摸绿色球

再走一圈,还是那条路,从A左边出发,逆时针绕行。

这一圈,你只盯着绿色的球。

  1. 经过A左边(红色,忽略),到B左边(红色,忽略),到D左边(红色,忽略)
  2. 绕到D底部 → 摸到D的绿色球
  3. 回到B底部 → 摸到B的绿色球
  4. 到E底部 → 摸到E的绿色球
  5. 回到A底部(注意,A的绿色球在B和C轨道交叉点的正下方)→ 摸到A的绿色球
  6. 到C底部 → 摸到C的绿色球
  7. 到F底部 → 摸到F的绿色球

你摸到的绿色球顺序是:

D → B → E → A → C → F

这就是中序遍历。


第三圈:后序遍历 —— 只摸蓝色球

再走最后一圈。

这一圈,你只盯着蓝色的球。

注意:根节点A的蓝色球在整棵树的最高点右侧,要到最后时刻才会被摸到

  1. 深入左子树,绕到D右侧 → 摸到D的蓝色球
  2. 回到B右侧 → 摸到B的蓝色球
  3. 到E右侧 → 摸到E的蓝色球
  4. 回到A,不急着摸A(还没绕到),继续进入C子树
  5. 先到F右侧 → 摸到F的蓝色球
  6. 回到C右侧 → 摸到C的蓝色球
  7. 最后绕到A右侧顶端 → 摸到A的蓝色球

你摸到的蓝色球顺序是:

D → E → B → F → C → A

这就是后序遍历。


三张图对比,一目了然

遍历方式摸球颜色摸球位置结果序列
前序🔴 红色摸左边A → B → D → E → C → F
中序🟢 绿色摸下边D → B → E → A → C → F
后序🔵 蓝色摸右边D → E → B → F → C → A

口诀只有一句话:前红、中绿、后蓝。不用再背“根左右”了。


代码验证:让计算机替我们走一圈

理解了“摸球法”之后,我们再来看代码。你会发现,递归代码本质上就是在模拟这“绕圈走”的过程。

二叉树的节点定义(Python)

classTreeNode:def__init__(self,val):self.val=val self.left=Noneself.right=None

构建我们示例中的那棵树

# 构建示例树# A# / \# B C# / \ \# D E FA=TreeNode('A')B=TreeNode('B')C=TreeNode('C')D=TreeNode('D')E=TreeNode('E')F=TreeNode('F')A.left=B A.right=C B.left=D B.right=E C.right=F

三种遍历的递归代码

defpreorder(root):"""前序遍历:根 -> 左 -> 右"""ifrootisNone:return[]return[root.val]+preorder(root.left)+preorder(root.right)definorder(root):"""中序遍历:左 -> 根 -> 右"""ifrootisNone:return[]returninorder(root.left)+[root.val]+inorder(root.right)defpostorder(root):"""后序遍历:左 -> 右 -> 根"""ifrootisNone:return[]returnpostorder(root.left)+postorder(root.right)+[root.val]# 运行验证print("前序遍历:",preorder(A))# ['A', 'B', 'D', 'E', 'C', 'F']print("中序遍历:",inorder(A))# ['D', 'B', 'E', 'A', 'C', 'F']print("后序遍历:",postorder(A))# ['D', 'E', 'B', 'F', 'C', 'A']

把摸球法的注释写在代码里

defpreorder(root):"""前序遍历 = 摸红色球(左侧球)"""ifrootisNone:return[]# 👆 离开当前节点之前,先摸左侧红球return[root.val]+preorder(root.left)+preorder(root.right)definorder(root):"""中序遍历 = 摸绿色球(正下方球)"""ifrootisNone:return[]left_result=inorder(root.left)# 👆 从左子树回来,到达节点正下方,摸绿色球returnleft_result+[root.val]+inorder(root.right)defpostorder(root):"""后序遍历 = 摸蓝色球(右侧球)"""ifrootisNone:return[]left_result=postorder(root.left)right_result=postorder(root.right)# 👆 左右子树都处理完了,绕到节点右侧,摸蓝色球returnleft_result+right_result+[root.val]

这段注释清晰地标出了:递归代码中的访问时机,正好对应了绕圈过程中碰到红、绿、蓝球的时刻


迭代版代码:模拟绕圈的过程

递归代码简洁,但“摸球法”的本质其实是沿着树的边缘走一圈。用迭代的方式写出来,更能体现这种“绕圈”的感觉:

defpreorder_iterative(root):"""前序遍历(迭代版):模拟沿着树边缘走,优先摸左侧红球"""ifnotroot:return[]result=[]stack=[root]whilestack:node=stack.pop()result.append(node.val)# 摸到红球# 注意:栈是后进先出,所以先压右孩子,再压左孩子ifnode.right:stack.append(node.right)ifnode.left:stack.append(node.left)returnresultdefinorder_iterative(root):"""中序遍历(迭代版):先深入左下,再摸绿球"""result=[]stack=[]cur=rootwhilestackorcur:# 一路向左,把经过的节点压栈whilecur:stack.append(cur)cur=cur.left# 到达最左下,弹出节点,摸绿色球cur=stack.pop()result.append(cur.val)# 转向右子树cur=cur.rightreturnresultdefpostorder_iterative(root):"""后序遍历(迭代版):最后摸蓝色球"""ifnotroot:return[]result=[]stack=[root]whilestack:node=stack.pop()result.append(node.val)ifnode.left:stack.append(node.left)ifnode.right:stack.append(node.right)# 反转得到正确顺序:左右根returnresult[::-1]# 验证print("前序(迭代):",preorder_iterative(A))print("中序(迭代):",inorder_iterative(A))print("后序(迭代):",postorder_iterative(A))

为什么“摸球法”比背口诀好?

对比维度传统口诀法(根左右/左根右/左右根)摸球法(红绿蓝)
记忆方式背三组不同的顺序只记“红绿蓝”三个颜色
出错概率容易搞混根的位置手指沿轨道走,绝不会漏
理解深度停留在口诀层面直观理解遍历的几何本质
与代码对应需要额外映射红=先输出,绿=中间输出,蓝=最后输出
适用人群需要一定抽象思维小学生都能看懂

总结

二叉树的三种遍历,本质上是同一条路径上的三次“经过”:

  • 第一次经过节点时访问 → 前序(摸红球)
  • 第二次经过节点时访问 → 中序(摸绿球)
  • 第三次经过节点时访问 → 后序(摸蓝球)
🔴(第1次经过) | 🔴A🔵(第3次经过) 🟢(第2次经过)

这就是为什么递归代码只需要调换三行代码的顺序,就能实现三种不同的遍历。因为它们本来就是同一套逻辑,只是访问的时机不同而已。

下次有人问你二叉树遍历,别急着背口诀。拿出笔,画一棵树,在每个节点旁边画上红、绿、蓝三个小球,然后伸出手指,沿着树的边缘走一圈。

前序摸红,中序摸绿,后序摸蓝。

就这么简单。