Morris前序遍历需利用空右指针构建临时线索,严格遵循“见根即访”原则:先访问curr再向左/右推进,左子树为空则直接访根后右移;否则找前驱建/拆线索,确保每条线索建一次、拆一次,最终恢复原树结构。

要在不使用栈、递归或任何额外容器的前提下完成二叉树前序遍历,必须依靠节点自身空闲指针临时构建回溯路径,且每一步推进都必须严格匹配“根→左→右”的访问时机,稍有错位就会漏根、重复或死循环。
理解Morris前序的核心约束
前序遍历的本质是“一见到节点就访问”,不是等左子树回来再访——这和中序有根本区别。若把访问语句挪到线索拆除分支里,输出序列立刻变成中序行为,root可能被跳过,或某个节点被访问两次。
所有线索只建一次、只拆一次;每个被修改的right指针最终必须恢复为原始值(nullptr或原右子节点);否则第二次运行同一棵树时,会因残留线索陷入无限循环。
算法仅依赖两个指针变量:curr(当前节点)和pred(前驱),空间复杂度确为【O(1)】,但前提是不把输出容器result计入辅助空间。
立即学习“C++免费学习笔记(深入)”;
初始化与主循环框架
声明TreeNode* curr = root;创建空vector
这一步不能省略判空:若直接对nullptr调用curr->left,程序立即崩溃。while条件本身已防护curr为空的情况,无需在循环体内重复检查curr是否为空。
循环体内部按curr->left是否为空分两大路径,后续步骤严格对应这两类场景展开。
处理无左子树的节点
第一步:判断if (curr->left == nullptr)成立。
第二步:立即将curr->val加入result。
第三步:执行curr = curr->right,跳转至右子节点继续处理。
这三步顺序不可颠倒:若先跳右再访问,当前节点就被永久跳过;若跳向curr->left则必然解引用空指针——【此处写成curr = curr->left是高频段错误】。
处理有左子树的节点
方法一:标准双阶段前驱定位+访问建链
声明TreeNode* pred = curr->left;注意:此处必须确保curr->left非空才执行,否则pred为nullptr,下一步while将崩溃。
执行while (pred->right != nullptr && pred->right != curr) pred = pred->right;该循环必须同时排除nullptr和已建线索两种情况,否则会误入环路或越界。
若此时pred->right == nullptr,说明首次抵达curr:立即将curr->val加入result → 设置pred->right = curr → 更新curr = curr->left。
若此时pred->right == curr,说明已从左子树返回:将pred->right置为nullptr以恢复树结构 → 更新curr = curr->right。
方法二:提前防护的紧凑写法(推荐用于生产环境)
在进入while前加一句if (!curr->left) { result.push_back(curr->val); curr = curr->right; continue; },可提前收口无左子树分支,避免嵌套过深;后续代码只需专注处理有左子树情形,逻辑更清晰。
注意:pred查找循环内绝不能简写为while (pred->right),必须显式带上pred->right != curr,否则当线索已存在时会无限绕圈。
关键边界防护点
每次对pred->right赋值前,必须确认pred非空——因为pred = curr->left后未做判空就直接进入while,是导致段错误的首要原因。
在curr = curr->left或curr = curr->right前,无需再次判空:这是Morris算法的设计契约——空子节点自然使下一轮循环终止,无需提前拦截。
所有线索操作必须成对出现:pred->right = curr之后,必有对应的pred->right = nullptr;少一次恢复,整棵树结构即被污染。


















