算法学习记录03——二叉树学习笔记:从两道题看透后序位置的关键作用
最近在啃二叉树的知识点,过程中遇到两个看似简单的小问题,却让我对二叉树遍历里的「后序位置」有了新的理解。今天就把这个思考过程记录下来,方便以后回头看,也希望能给同样在学二叉树的朋友。
先从两个基础问题说起
刚开始练手的时候,我先后碰到了两道题,题面都很直白:
- 给一棵二叉树,打印出每个节点所在的层数(根节点算第 1 层);
- 给一棵二叉树,计算出每个节点作为根的子树,包含多少个节点(比如叶子节点的子树节点数就是 1)。
本来以为都是常规遍历题,结果写的时候发现,这两道题的处理逻辑居然差了不少 —— 而这个差异,恰好指向了二叉树遍历中一个很重要的点。
问题 1:打印节点层数 —— 遍历过程中就能 “顺带” 解决
先看第一题,打印节点层数。其实稍微想一下就知道,根节点是第 1 层,那根节点的左右孩子就是第 2 层,孩子的孩子就是第 3 层…… 这个层数是 “自上而下” 传递的。
所以写代码的时候,用前序、中序、后序遍历其实都能做。比如用前序遍历的话,思路是这样的:
- 访问当前节点时,先把当前的层数记录下来(比如直接打印);
- 递归遍历左孩子时,把层数 + 1 传过去;
- 递归遍历右孩子时,同样把层数 + 1 传过去。
核心代码大概长这样(用 Python 举个例子):
def print_level(root, level):if root is None:return# 先访问当前节点,打印层数print(f"节点{root.val},层数:{level}")# 递归左孩子,层数+1print_level(root.left, level + 1)# 递归右孩子,层数+1print_level(root.right, level + 1)# 调用的时候,根节点层数是1
print_level(root, 1)
你看,这里的层数信息,是在遍历到父节点的时候,直接传递给子节点的。不需要等子节点处理完,当前节点的层数在 “访问它的时候” 就已经确定了 —— 完全是 “顺带” 解决的事。
问题 2:统计子树节点数 —— 必须等子树遍历完才能算
再看第二题,统计每个节点的子树节点数。这时候就发现,事情不一样了。
比如现在有个节点 A,它有左孩子 B 和右孩子 C。要算 A 的子树节点数,得先知道 B 的子树有多少节点,再知道 C 的子树有多少节点,最后加上 A 自己(1 个),才能得到 A 的子树节点数。
也就是说,必须先遍历完当前节点的左右子树,才能计算当前节点的结果。
这时候就不能用前序遍历了 —— 如果前序遍历先访问 A,这时候 B 和 C 的子树还没遍历,根本不知道它们的节点数,怎么算 A 的?
这时候就得用到后序遍历了。后序遍历的顺序是:左子树 → 右子树 → 当前节点。刚好符合 “先处理完子树,再处理当前节点” 的需求。
同样用 Python 写核心代码,思路是这样的:
- 递归计算左子树的节点数;
- 递归计算右子树的节点数;
- 当前节点的子树节点数 = 左子树节点数 + 右子树节点数 + 1(自己);
- 返回当前节点的子树节点数(供父节点使用)。
代码大概是这样:
def count_subtree_nodes(root):if root is None:# 空节点的子树节点数是0return 0# 先算左子树节点数left_count = count_subtree_nodes(root.left)# 再算右子树节点数right_count = count_subtree_nodes(root.right)# 后序位置:处理当前节点,计算子树节点数current_count = left_count + right_count + 1print(f"节点{root.val}的子树节点数:{current_count}")# 返回当前节点的子树节点数,给父节点用return current_count# 调用的时候,从根节点开始
count_subtree_nodes(root)
这里的关键是 “返回值” 和 “后序位置”。当前节点的结果,依赖于左右子树的结果,而左右子树的结果只能通过递归的返回值拿到 —— 只有等左右子树都遍历完(后序位置),才能计算当前节点的结果。
对比之后:后序位置的核心特点 —— 能拿到子树的信息
把这两道题放在一起对比,就能很明显地看出后序位置的作用了:
- 像 “节点层数” 这种不需要子树信息的问题,前序、中序、后序都能做,因为信息是 “自上而下” 传递的;
- 但像 “子树节点数” 这种需要子树信息的问题,只能靠后序位置 —— 因为只有后序位置,才能通过递归返回值,拿到左子树和右子树处理后的结果。
换句话说,后序位置的本质,是 “处理当前节点时,左右子树已经处理完毕,能获取到子树的全部信息”
。这是前序和中序位置做不到的 —— 前序是没处理子树就先处理当前节点,中序是处理了左子树但没处理右子树(或者反过来,看遍历顺序),都拿不到完整的子树信息。
一个小总结:遇到 “子树相关” 问题,优先想后序
练完这两道题,我自己总结了一个小规律:
以后在做二叉树的题目时,先看题目是不是和 “子树” 有关 —— 比如求子树的节点数、子树的高度、子树的最大值、判断两棵子树是否对称等等。如果是,那大概率要用到后序遍历
。
而且这类题的解题步骤也很固定:
- 先给递归函数定一个明确的 “返回值”—— 比如求子树节点数,返回值就是当前节点子树的节点数;求子树高度,返回值就是当前节点子树的高度;
- 递归处理左子树和右子树,拿到它们的返回值;
- 在后序位置,用左子树和右子树的返回值,计算当前节点的结果;
- 返回当前节点的结果,供父节点使用。