Morris后序遍历必须翻转右子路径才能获得合法访问序列,因无栈无递归且线索仅能指向父节点,需通过翻转构造临时链实现逆序访问,再整体反转结果;漏翻转或翻转后未还原必错。

Morris 后序遍历不能直接套用前序或中序的写法,核心卡点就一个:必须翻转右子路径才能拿到合法访问序列,漏掉翻转或翻转后不还原,结果必错。
为什么 reverseRightPath 不是可选操作而是强制步骤
后序要求“左→右→根”,但 Morris 只能单向推进、无栈无递归,也没有回溯指针。它唯一能复用的是空 right 指针建线索——而这只能指向父节点,无法表达“刚从右子树回来”这个状态。所以必须靠翻转构造一条临时链:从 current->left 出发,沿 right 一路走到前驱 predecessor(即左子树最右节点),把这段链原地反转,使指针方向变成从下往上。这样正向遍历这条新链,就等价于逆序访问左子树所有后序节点(即“根→右→左”)。不翻转,你就根本拿不到这段序列。
常见错误现象:
-
Segmentation fault:翻转循环里用tail != nullptr当终止条件,实际应为tail != current,否则会越界解引用 - 输出漏节点:翻转前没判
current->left == nullptr,导致predecessor为nullptr还继续走循环 - 重复访问根:翻转后没把
predecessor->right = nullptr,下次再进同一分支误认为线索未处理
reverseRightPath 的安全实现三要素
翻转本身是链表反转,但 Morris 场景下边界和时机更敏感。关键不是“怎么翻”,而是“翻谁、何时翻、翻完立刻干什么”。
- 翻转范围严格限定在
current->left到predecessor构成的右链上,不是整棵树,也不是current->right -
prev必须显式初始化为nullptr:这是让原链尾节点翻转后right正确指向空的关键,漏写会导致首节点乱指 - 翻转循环终止条件必须是
tail != current,不是tail != nullptr——因为终点是current,而current->right可能已被设为线索(即等于predecessor),不能靠nullptr判定 - 翻转完成后,
prev指向原链最右下节点,这才是你该开始收集的起点;收集时每步都要把node->right立即设回nullptr,否则后续线索逻辑会错乱
翻转后为何还要对整个 result 调用 std::reverse
每次翻转右链并正向遍历,得到的是“当前子树的逆后序”(根→右→左),不是最终要的“左→右→根”。比如一棵子树结构是 1(根)→右连 3,1→左连 2,翻转收集顺序是 1 → 3 → 2,而正确后序是 2 → 3 → 1。所以必须把所有这类局部逆序拼起来之后,再整体 std::reverse(result.begin(), result.end())。
注意:不能靠在收集时压栈再弹出来替代——那需要额外 O(h) 空间,违背了 Morris 的 O(1) 空间目标。
主循环里 pred->right == current 是唯一安全的触发点
这个判断代表:左子树已完整遍历完毕,且线索是“你亲手建的”,可以放心翻转、收集、还原。其他任何时机都不行:
-
current->left == nullptr:直接跳过翻转,current先不访,等右子树处理完再统一补上(靠最后全局 reverse 保证位置) -
pred->right == nullptr:说明第一次来,建线索pred->right = current,然后current = current->left,不触发翻转 - 误在
current->right == nullptr时翻转:这可能是叶子节点,也可能是右子树还没走,此时翻转毫无意义,还会破坏结构
真正复杂的部分不在循环结构,而在翻转段内部的指针操作是否原子化——翻转、遍历、还原三步必须紧挨着,中间不能穿插任何 current 移动或条件判断,否则指针状态就不可控了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











