morris后序遍历不能直接套用前序/中序逻辑,因其需第三次访问根节点而morris最多两次访问;实际通过第二次到达时逆序打印左子树右边界实现,最后单独加入根节点。

为什么Morris后序遍历不能直接套用前序/中序逻辑
Morris遍历对每个节点最多访问两次,而传统后序要求“第三次到达才访问根”,这天然冲突。前序在第一次到节点时打印,中序在第二次(回溯时)打印,但后序没有第三次机会——所以不能靠“判断访问次数”来决定打印时机。
真正可行的思路是:在每次第二次到达某节点 cur 时,逆序打印其左子树的“右边界”(即从左孩子的最右节点,一路 left 指针回退到左孩子本身)。这个边界在Morris过程中恰好是临时链路的一部分,且只含未被访问过的节点。
常见翻车点:
- 试图复用前序的
cout val位置,漏掉左子树右边界反转逻辑 - 调用反转函数时传入空指针或单节点链表,导致越界或无限循环
- 忘记在最后补上整棵树的根节点(当整棵树有左子树时,根不会被上述右边界逻辑覆盖)
如何安全提取并逆序打印左子树右边界
关键不是“存下来再反转”,而是边遍历边倒着输出:从 mostRight 开始,沿 right 指针反向走(注意此时这些 right 指针已被 Morris 过程设为指向父节点),每走一步就记录值,最后再整体 reverse 一次结果——但实际代码中更高效的做法是用栈暂存、再依次 pop,或直接用 vector + std::reverse。
实操要点:
- 提取边界前必须确认
cur->left != nullptr,否则跳过 - 起始点是
mostRight = cur->left,然后 while 循环找最右:while (mostRight->right && mostRight->right != cur) mostRight = mostRight->right - 此时
mostRight就是左子树最右节点;从它开始,沿right链向上走到cur->left,把路径上所有节点值 push_back 到临时vector - 调用
std::reverse(temp.begin(), temp.end())后再 merge 进最终结果
Morris后序遍历主循环中的三个关键分支
主循环变量仍是 TreeNode* cur = root,每次迭代依据左子树是否存在、以及 mostRight->right 当前指向谁,分三类处理:
- 若
cur->left == nullptr:无左子树,直接cur = cur->right - 若
cur->left != nullptr且mostRight->right == nullptr:第一次到cur,建线索mostRight->right = cur,然后cur = cur->left - 若
cur->left != nullptr且mostRight->right == cur:第二次到cur,先恢复树结构mostRight->right = nullptr,再执行左子树右边界逆序打印,最后cur = cur->right
注意:第三分支里打印完边界后,不立即访问 cur->val——那是最后统一补的。
最终补根节点与空树边界处理
整个循环结束后,cur 会变成 nullptr,此时所有左子树的右边界都已处理完毕,但整棵树的根节点还没被加入结果——因为根没有“父节点”来触发它的第二次到达。
所以必须在循环外单独加一句:
if (root) {
vector<int> rightEdge;
TreeNode* node = root;
while (node) {
rightEdge.push_back(node->val);
node = node->right;
}
std::reverse(rightEdge.begin(), rightEdge.end());
res.insert(res.end(), rightEdge.begin(), rightEdge.end());
}</int>
但这其实不对:上面这段是错的,它打印的是根的右链,不是后序需要的。正确做法是——循环结束后,手动把根加到结果末尾,仅当整棵树非空且你没在过程中把它加进去。而标准实现中,根只会在它自己成为某个节点的“左子树右边界终点”时被覆盖,否则需显式追加。
最容易被忽略的点:Morris后序的最终结果顺序依赖于两次“逆序”操作——一次是单个右边界内部逆序,另一次是整条路径在结果中的相对位置。稍一错位,输出就是镜像错误。写的时候建议先用 vector 收集所有中间片段,最后一次性拼接并验证小样例 [1,null,2,3] 的输出是否为 [3,2,1]。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











