C++ 实现二叉树的 Morris 后序遍历算法及其零辅助空间路径翻转逻辑【实战】

星宇大大_5399

星宇大大_5399

2026-05-22

873人浏览

原创

必须翻转右子链是因为Morris后序需在无栈无递归下逆序收集左子树最右路径节点,翻转构造可遍历单向链,正向走即得逻辑逆序;不翻转则无法确定收集时机且破坏线索结构。

c++ 实现二叉树的 morris 后序遍历算法及其零辅助空间路径翻转逻辑【实战】

为什么 Morris 后序遍历必须翻转右子链,而不是直接逆序打印

Morris 后序的核心难点不在“找前驱”,而在“如何在不递归、不栈、不标记的情况下收集子树访问序列”。right 指针被临时复用为线索指针,当某节点的左子树处理完、准备进入右子树时,它的 right 可能已被设为指向祖先(即形成向上的线索),此时无法自然回溯。翻转右子链的本质是把“从当前节点到前驱的路径”变成一条单向链表,从而能正向遍历并逆序输出——这比试图动态重建访问栈更轻量且确定。

常见错误现象:Segmentation fault 或输出顺序错乱,往往是因为翻转逻辑漏判了 nullptr 边界,或在翻转后未恢复原 right 指针。

  • 只翻转「当前节点左子树的最右路径」上所有节点的 right 指针,不是整棵树
  • 翻转起点是 predecessor(左子树最右节点),终点是 current->left
  • 翻转后必须用临时指针正向走一遍该链,并在每步将节点值加入结果——这是唯一合法收集时机
  • 翻转完成后,需再走一遍链表,把所有 right 指针恢复成 nullptr(否则破坏原树结构)

如何安全实现右子链翻转与恢复:三步不可省略

翻转不是目的,是为构造一个可线性遍历的临时链。关键在于翻转、遍历、恢复三阶段必须原子化,中间不能穿插其他指针操作。

典型误用:current->right = nullptr 被提前写在翻转循环里,导致后续找不到下一个节点。

  • 第一步(翻转):用 prev = nullptr,node = current->left,循环中先缓存 node->right,再令 node->right = prev,最后更新 prev = node 和 node = temp
  • 第二步(收集):从 prev(此时是链头)开始,正向遍历该链,将每个节点的 val 推入结果容器;注意:此时链方向已反,所以正向走 = 逻辑上的逆序访问
  • 第三步(恢复):再次从 prev 开始,把每个节点的 right 设回 nullptr;若跳过此步,后续 Morris 循环可能误用残留线索,引发崩溃或死循环

示例片段(核心逻辑):

TreeNode* reverse_and_collect(TreeNode* head, vector<int>& res) {
    TreeNode* prev = nullptr;
    TreeNode* node = head;
    while (node != nullptr) {
        TreeNode* next = node->right;
        node->right = prev;
        prev = node;
        node = next;
    }
    // 此时 prev 是翻转后的头
    node = prev;
    while (node != nullptr) {
        res.push_back(node->val);
        node = node->right;
    }
    // 恢复
    node = prev;
    while (node != nullptr) {
        TreeNode* next = node->right;
        node->right = nullptr;
        node = next;
    }
    return prev; // 返回原 head,便于调用方理解
}
</int>

Morris 主循环中何时触发翻转:判断条件必须精确

翻转只发生在「当前节点有左子树,且其左子树的最右节点的 right 尚未指向当前节点」时——即第一次到达该节点,准备建立线索。此时才需要把左子树最右路径翻转并输出(因为后序要求左右根,左子树已处理完,右子树尚未进入,但右子路径上的节点需在返回当前节点前被访问)。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

容易踩的坑:if (current->left == nullptr) 分支里也去翻转,或在 else 分支里漏掉对 predecessor->right == nullptr 的判断。

  • 正确触发点:if (current->left != nullptr && predecessor->right == nullptr)
  • 此时先设 predecessor->right = current(建线索),再调用翻转函数处理 current->left 子树的最右链
  • 若 predecessor->right == current,说明是第二次回来,应断开线索(predecessor->right = nullptr),然后进入右子树
  • 绝对不要在 current->right != nullptr 分支里做翻转——Morris 后序不依赖右线索,只用左线索驱动

零辅助空间的代价:时间复杂度仍是 O(n),但常数项明显升高

表面上没用栈和递归,空间 O(1),但每个边最多被遍历 3 次:一次找前驱,一次翻转,一次恢复。实测在深度较大的偏斜树上,比标准递归慢 2–3 倍。这不是 bug,是算法本质决定的。

兼容性提示:该算法要求 TreeNode 结构允许修改 right 指针;若节点被多线程共享或只读封装,Morris 方案直接不可用。

  • 翻转链长度 = 左子树高度,最坏情况(完全左倾树)下,单次翻转耗时 O(h),总时间 O(n × h) → 实际仍是 O(n),因每条边总访问次数有界
  • 若需调试,可在翻转前后加断言:assert(node->right == nullptr || node->right == current),快速捕获指针污染
  • 生产环境慎用:除非内存极端受限且树形态稳定,否则 std::stack + 迭代后序更可靠

真正难的从来不是写出翻转代码,而是想清楚:为什么必须翻转右子链,而不是左?为什么翻转后要立刻收集再立刻恢复?这些约束一旦松动,整个零空间保证就崩了。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关专题

更多
c++和c语言的区别有哪些
c++和c语言的区别有哪些

c++和c语言的区别:1、面向对象编程(OOP)支持不同;2、新增特性不同;3、标准库不同;4、编译方式不同;5、命名空间不同等等。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.14

2288

9

c++和python学习顺序推荐
c++和python学习顺序推荐

一般建议先学习C++,再学习Python,因为这样可以逐步从较为底层的编程语言向更高级的语言过渡。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

1039

6

python和c++学习性价比分析
python和c++学习性价比分析

Python易于学习,广泛应用于Web开发、数据科学和人工智能等领域,但性能较低。C语言性能高,适用于对性能要求较高的场景,如游戏开发和系统编程,但学习曲线陡峭,错误处理复杂。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

447

5

c语言和c++一样吗
c语言和c++一样吗

c语言和c++是两种不同的编程语言,虽然有相似之处,但存在显著差异。c语言专注于过程式编程和系统级开发,以简洁、高效著称。c++作为c语言的超集,引入了面向对象编程,增强了代码组织和管理能力,但学习曲线也更陡峭。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

347

5

c语言和c++先学哪个好
c语言和c++先学哪个好

初学者选择学习c语言还是c++语言,需要根据个人学习目标、背景以及编程兴趣和预期应用方向来决定。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

426

5

c语言和c++的区别和联系
c语言和c++的区别和联系

c语言和c++是计算机科学领域应用广泛的编程语言。虽然它们有着相似的基础,但它们在语言类型、语法功能和内存管理方面存在着显著差异。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

600

5

c++软件中文更改教程
c++软件中文更改教程

对于 ide,可通过打开设置,找到语言设置,选择中文,并保存更改。对于非 ide 应用程序,可查找设置或选项,选择语言设置,更改为中文,并保存更改。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.21

1429

9

python和java和c++学习性价比分析
python和java和c++学习性价比分析

Python以其易学性、丰富的库和活跃的社区而著称,适合数据科学、人工智能和Web开发。Java以其跨平台性、企业级应用开发和Android应用开发而闻名。C++以其底层控制能力、高效性能和游戏开发而著称。选择哪种语言取决于个人兴趣、职业方向和特定需求。想了解更多python和java和c++的相关内容,可以阅读本专题下面的文章。

2024.03.22

1237

7

c++和c语言学习顺序推荐
c++和c语言学习顺序推荐

对于初学者,建议先学习C语言,掌握编程基础后再转入C++,便于理解面向对象编程概念。有编程经验者可直接学习C++,快速接触高级编程技术。想了解更多c++和c语言的相关内容,可以阅读本专题下面的文章。

2024.03.25

1325

9

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习