morris后序遍历的核心约束是零辅助空间且必须确保节点在左右子树均访问完毕后才输出,因此需借助逆序打印技巧:沿左子树构建临时右链、逆序输出并还原指针。

Morris后序遍历的核心约束是什么
它必须在 O(1) 额外空间下完成,不能用栈模拟递归,也不能用 std::stack 或函数调用栈。关键在于:后序访问顺序(左→右→根)与 Morris 中序遍历(左→根→右)天然冲突,所以不能简单复用中序的线索化逻辑——必须借助“逆序输出”技巧,即把「从根到右子节点」的链表翻转后临时输出,再翻转回来恢复结构。
为什么不能直接改写 Morris 中序为后序
中序 Morris 利用前驱节点的 right 指针指向当前节点,形成临时线索;但后序需要在访问完右子树后才访问根,而此时原右子树已被破坏(线索已建立或拆除)。常见错误是试图在找到前驱时直接 push 根节点,结果导致重复访问或跳过节点。
- 中序遍历中,每个节点最多被访问 2 次(第一次建立线索,第二次拆除并输出)
- 后序要求每个节点在「其右子树完全处理完毕后」才输出,这意味着必须等右子树最右路径上的所有节点都确认无右孩子,才能安全输出该路径逆序
- 若不翻转路径直接尝试记录,会因指针被反复修改而丢失原始结构,尤其当树含单边链时极易崩溃
关键步骤与指针操作细节
以 TreeNode* 为例,假设节点结构为 struct TreeNode { int val; TreeNode *left, *right; };,核心动作如下:
- 从
curr = root开始,每次尝试找curr的前驱(即左子树最右节点) - 若前驱
right为空,建立线索:pred->right = curr,然后curr = curr->left - 若前驱
right == curr,说明左子树已遍历完,此时要「处理右子树到 curr 的逆序路径」:先断开线索(pred->right = nullptr),再调用辅助函数reverseAndPrint(curr->left, pred)输出该段逆序(即把 curr->left 到 pred 的链表翻转、打印、再翻转回原状) - 最后始终向右走:
curr = curr->right(注意不是 left!这是和中序最易混淆的一点)
其中 reverseAndPrint 是纯链表操作,不依赖树结构,仅靠指针移动完成翻转+输出+还原,确保空间仍是 O(1)。
容易崩溃的边界场景与修复方式
实际写的时候最容易栽在空指针和循环链上,比如:
-
curr为nullptr时未提前跳出循环 → 在 while 头部加while (curr)即可 - 前驱判断写成
pred->right == nullptr而非pred->right == curr→ 会导致线索无法识别,陷入死循环 - 翻转链表时忘记保存原头节点,导致还原失败 → 必须用临时变量记下翻转前的
first和翻转后的last - 叶子节点的
left == nullptr,找前驱时直接解引用崩溃 → 找前驱前先判curr->left == nullptr,此时跳过线索建立,直接curr = curr->right
真正难的不是逻辑,而是翻转链表那三步(prev/curr/next)在嵌套循环里不能写错顺序,且必须严格配对:翻一次、输出、再翻一次。少一次翻转,树结构就永久损坏。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











