中序遍历迭代版易错写成“先访问再压右左”,本质是混淆了中序(左→根→右)与前序(根→左→右)的访问时机;正确做法是先沿左链压栈至空,再弹出访问,然后转向右子树,全程需防御性检查空指针。

为什么中序遍历迭代版容易写成“先访问再压右左”?
这是最典型的逻辑错位:把中序(左→根→右)和前序(根→左→右)的迭代模板混用了。递归里我们自然地“走到最左才开始访问”,但迭代时若不显式维护“该不该访问当前节点”的状态,就会在刚拿到 root 时就把它输出,结果变成前序。
关键在于:中序的访问时机不是“刚取到节点时”,而是“它的左子树已全部处理完之后”。所以不能一上来就 cout val,而要先一路向左压栈,直到空;再弹出、访问、转向右子树。
实操建议:
- 用
stack存待处理节点,初始只压入根节点 - 外层循环条件是
!stk.empty() || cur != nullptr(注意不是仅靠栈非空) - 内层 while 循环负责“沿左链下行并压栈”,直到
cur == nullptr - 每次从栈弹出一个节点,立刻访问它,然后将
cur设为它的右子节点,进入下一轮
如何避免空指针解引用崩溃?
常见错误是没检查 cur 就直接访问 cur->left 或 cur->right,尤其在 while 下行和转向右子树时。C++ 中对空指针调用成员访问会立即 crash,不是返回 false。
实操建议:
- 所有对
cur的成员访问前,必须加if (cur)或确保它非空 - 压栈前判空:
if (cur) stk.push(cur),而不是无条件压入 - 转向右子树时,直接赋值
cur = cur->right是危险的——应先确认cur非空,或改用cur = popped->right(此时popped刚从栈弹出,必不为空) - 初始化
cur时不要写成TreeNode* cur = root;后就假设它可用;后续每步都需防御性判断
stack 里到底该存什么?TreeNode* 还是带状态的结构体?
对于标准中序,只存 TreeNode* 完全够用。加状态(比如 bool 表示“是否已访问过左子树”)属于过度设计,反而增加分支判断和内存开销。
原因很简单:中序的控制流由“压栈方向 + 弹出时机 + 右转动作”三者严格耦合,不需要额外标记。一旦你发现需要判断“这个节点左子树是否处理过了”,说明控制逻辑已经混乱——正确做法是靠 while 循环统一处理左链,弹出即代表左子树已尽。
实操建议:
- 坚持用
stack,别封装匿名结构体或 pair - 不要在栈里存
nullptr,它无法提供有效信息,还会干扰空判断 - 如果真要扩展(如实现通用遍历器),再考虑状态标记;日常中序无需
LeetCode 提交时遇到 “member access within null pointer” 怎么快速定位?
这通常是某次 cur = cur->left 或 cur = cur->right 后,cur 变成 nullptr,但下一行没检查就又写了 cur->val 或 cur->left。
调试技巧:
- 在所有访问
cur->xxx前加断言:assert(cur != nullptr);(提交前删掉) - 把
cur = cur->left拆成两步:if (cur) cur = cur->left; - 观察报错行号,往前看最近一次对
cur的赋值是不是来自->left或->right - 典型高危位置:while 循环体内、弹出后立刻访问
popped->right却没确认popped是否为空(其实不会空,但若你误把cur当popped用就危险)
真正难的不是写对逻辑,而是每次修改后都重新走一遍“根→左空→弹根→转右→右空→弹右”这种边界路径。少一次手动模拟,就多一个 nullptr 解引用。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











