算法学习记录03——二叉树学习笔记:从两道题看透后序位置的关键作用

最近在啃二叉树的知识点,过程中遇到两个看似简单的小问题,却让我对二叉树遍历里的「后序位置」有了新的理解。今天就把这个思考过程记录下来,方便以后回头看,也希望能给同样在学二叉树的朋友。

先从两个基础问题说起

刚开始练手的时候,我先后碰到了两道题,题面都很直白:​

  1. 给一棵二叉树,打印出每个节点所在的层数(根节点算第 1 层);
  2. 给一棵二叉树,计算出每个节点作为根的子树,包含多少个节点(比如叶子节点的子树节点数就是 1)。

本来以为都是常规遍历题,结果写的时候发现,这两道题的处理逻辑居然差了不少 ------ 而这个差异,恰好指向了二叉树遍历中一个很重要的点。​

问题 1:打印节点层数 ------ 遍历过程中就能 "顺带" 解决​

先看第一题,打印节点层数。其实稍微想一下就知道,根节点是第 1 层,那根节点的左右孩子就是第 2 层,孩子的孩子就是第 3 层...... 这个层数是 "自上而下" 传递的。​

所以写代码的时候,用前序、中序、后序遍历其实都能做。比如用前序遍历的话,思路是这样的:​

  1. 访问当前节点时,先把当前的层数记录下来(比如直接打印);
  2. 递归遍历左孩子时,把层数 + 1 传过去;
  3. 递归遍历右孩子时,同样把层数 + 1 传过去。
    核心代码大概长这样(用 Python 举个例子):
python 复制代码
def print_level(root, level):
    if root is None:
        return
    # 先访问当前节点,打印层数
    print(f"节点{root.val},层数:{level}")
    # 递归左孩子,层数+1
    print_level(root.left, level + 1)
    # 递归右孩子,层数+1
    print_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. 递归计算左子树的节点数;
  2. 递归计算右子树的节点数;
  3. 当前节点的子树节点数 = 左子树节点数 + 右子树节点数 + 1(自己);
  4. 返回当前节点的子树节点数(供父节点使用)。

代码大概是这样:

python 复制代码
def count_subtree_nodes(root):
    if root is None:
        # 空节点的子树节点数是0
        return 0
    # 先算左子树节点数
    left_count = count_subtree_nodes(root.left)
    # 再算右子树节点数
    right_count = count_subtree_nodes(root.right)
    # 后序位置:处理当前节点,计算子树节点数
    current_count = left_count + right_count + 1
    print(f"节点{root.val}的子树节点数:{current_count}")
    # 返回当前节点的子树节点数,给父节点用
    return current_count

# 调用的时候,从根节点开始
count_subtree_nodes(root)

这里的关键是 "返回值" 和 "后序位置"。当前节点的结果,依赖于左右子树的结果,而左右子树的结果只能通过递归的返回值拿到 ------ 只有等左右子树都遍历完(后序位置),才能计算当前节点的结果。​

对比之后:后序位置的核心特点 ------ 能拿到子树的信息​

把这两道题放在一起对比,就能很明显地看出后序位置的作用了:​

  • 像 "节点层数" 这种不需要子树信息的问题,前序、中序、后序都能做,因为信息是 "自上而下" 传递的;
  • 但像 "子树节点数" 这种需要子树信息的问题,只能靠后序位置 ------ 因为只有后序位置,才能通过递归返回值,拿到左子树和右子树处理后的结果。

换句话说,后序位置的本质,是 "处理当前节点时,左右子树已经处理完毕,能获取到子树的全部信息"。这是前序和中序位置做不到的 ------ 前序是没处理子树就先处理当前节点,中序是处理了左子树但没处理右子树(或者反过来,看遍历顺序),都拿不到完整的子树信息。​

一个小总结:遇到 "子树相关" 问题,优先想后序​

练完这两道题,我自己总结了一个小规律:​

以后在做二叉树的题目时,先看题目是不是和 "子树" 有关 ------ 比如求子树的节点数、子树的高度、子树的最大值、判断两棵子树是否对称等等。如果是,那大概率要用到后序遍历。​

而且这类题的解题步骤也很固定:​

  1. 先给递归函数定一个明确的 "返回值"------ 比如求子树节点数,返回值就是当前节点子树的节点数;求子树高度,返回值就是当前节点子树的高度;
  2. 递归处理左子树和右子树,拿到它们的返回值;
  3. 在后序位置,用左子树和右子树的返回值,计算当前节点的结果;
  4. 返回当前节点的结果,供父节点使用。
相关推荐
我先去打把游戏先7 小时前
ESP32学习笔记(基于IDF):ESP32连接MQTT服务器
服务器·笔记·单片机·嵌入式硬件·学习·esp32
我搞slam10 小时前
快乐数--leetcode
算法·leetcode·哈希算法
deng-c-f10 小时前
Linux C/C++ 学习日记(29):IO密集型与CPU密集型、CPU的调度与线程切换
linux·学习·线程·cpu·io密集·cpu密集
WWZZ202510 小时前
快速上手大模型:机器学习3(多元线性回归及梯度、向量化、正规方程)
人工智能·算法·机器学习·机器人·slam·具身感知
四谎真好看11 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
洋洋的笔记11 小时前
银行测试学习计划
学习
东方佑11 小时前
从字符串中提取重复子串的Python算法解析
windows·python·算法
西阳未落12 小时前
LeetCode——二分(进阶)
算法·leetcode·职场和发展
通信小呆呆12 小时前
以矩阵视角统一理解:外积、Kronecker 积与 Khatri–Rao 积(含MATLAB可视化)
线性代数·算法·matlab·矩阵·信号处理