安全递归遍历的关键是:空指针检查必须前置(首行if(root==nullptr)return;),递归调用顺序严格对应遍历定义,深树改用迭代;收集结果宜传vector&引用,避免重复初始化。

怎么写一个安全的递归遍历函数,不崩栈也不漏节点
直接说结论:C++ 里写前/中/后序遍历,核心不是“怎么递归”,而是「空指针检查必须前置」+「递归调用顺序严格对应遍历定义」。否则轻则结果错乱,重则 Segmentation fault(尤其树深 > 1000 时)。
常见错误现象:nullptr 没判就解引用、递归没写 base case、左右子树访问顺序颠倒(比如中序写成左→右→根)。
- 所有递归入口第一行必须是
if (root == nullptr) return;,不能靠后续条件“顺便”跳过 - 参数传
TreeNode*就够了,别传TreeNode&—— 空节点无法绑定引用 - 如果树可能很深(比如 > 2000 层),递归遍历本身就不合适,得换迭代 +
std::stack
前序/中序/后序三者只差两行代码,但顺序绝对不能记混
区别全在「访问当前节点」这一步的位置。记混的典型表现:中序输出像前序,或者后序结果为空(实际是访问被跳过了)。
以中序为例,正确结构是:左 → 访问 → 右。如果写成访问 → 左 → 右,那就是前序;写成左 → 右 → 访问,才是后序。顺序错一丁点,输出序列就全错。
立即学习“C++免费学习笔记(深入)”;
- 前序:
visit(root); preorder(root->left); preorder(root->right); - 中序:
inorder(root->left); visit(root); inorder(root->right); - 后序:
postorder(root->left); postorder(root->right); visit(root);
注意:这里的 visit 是你自己的处理逻辑,比如 std::cout val ,它不参与控制流,只负责输出或存入容器。
为什么用 std::vector<int></int> 收集结果时总多一个 0 或少一个值
根本原因:返回值传递方式不对,或在递归中重复初始化容器。不是算法错,是 C++ 值语义没处理好。
错误写法:std::vector<int> result; result.push_back(root->val); return result;</int> —— 每次递归都新建 vector,上层结果全丢。
- 推荐做法:把
std::vector<int>&</int>当参数传入,所有递归共用同一份内存(避免拷贝,也避免丢失) - 不要在递归函数里
returnvector,除非你明确写了移动语义(std::move(result)),但没必要复杂化 - 如果非要用返回值风格,确保顶层调用是
auto res = preorderTraversal(root);,且函数签名是std::vector<int> preorderTraversal(TreeNode* root)</int>,内部用局部 vector + 递归拼接(性能稍差但逻辑直)
LeetCode 上跑通了,本地 g++ 却报 invalid memory reference
大概率是测试用例里有非法输入:比如 root 是野指针、或某个 TreeNode 的 left/right 指向已释放内存。LeetCode 的 judge 环境做了隔离,而本地调试器会严格检查。
排查重点不在遍历逻辑,而在建树过程。比如手写测试用例时用 new TreeNode(1),但忘了初始化 left 和 right(它们是未定义值,不是 nullptr)。
- 永远显式初始化指针:
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} - 用
valgrind --tool=memcheck ./a.out跑本地测试,比看报错信息快得多 - 如果用智能指针(
std::unique_ptr<treenode></treenode>),记得所有 new 都要配对,否则析构时 double free
递归本身很干净,问题往往出在树的构造和边界指针状态上。这点容易被忽略,但占实际调试时间的七成以上。









