递归DFS代码简洁但栈溢出风险高,迭代DFS用stack控制力强但易错;二者选择取决于环境约束与场景需求,常见错误集中于空指针、入栈顺序和访问时机。

递归实现DFS:代码简洁但要注意栈溢出风险
递归写法最直观,直接模拟“访问根→递归左子树→递归右子树”的过程,适合理解逻辑,但实际项目中得警惕深度过大的树导致栈溢出。
-
void dfs_recursive(TreeNode* root)必须判空,否则访问nullptr会崩溃 - 前序、中序、后序仅靠调整
cout val和两次递归调用的顺序即可,不需要额外容器 - 递归深度 = 树的高度,链状树(如只有右子节点)在 C++ 默认栈大小下,超 10⁵ 层大概率触发
std::stack_overflow或 SIGSEGV - 若需返回路径或中间状态(比如找某值第一次出现位置),递归回溯比迭代更自然,但需手动维护状态参数
迭代实现DFS:用 stack 模拟系统栈,控制力更强
迭代本质是把递归隐式栈显式化,用 std::stack 存待处理节点,顺序由入栈方式决定——想前序就先压右再压左,想后序就得用双栈或标记法。
- 前序迭代最简:每次 pop 后,先处理当前节点,再按「右→左」顺序 push 非空子节点(保证左先被 pop)
- 中序迭代不能简单套用前序逻辑,必须「一路向左压栈,遇到空就弹出并转向右」,核心是
while (cur || !st.empty())这个循环条件 - 后序迭代最容易写错:若只靠单栈+简单入栈顺序,无法区分“刚访问完左子树”和“刚访问完右子树”,必须引入辅助标记(如
pair<treenode bool></treenode>)或第二栈反转 - 迭代版内存占用更可控,
std::stack在堆上分配,避免栈空间耗尽;但手动管理逻辑增加出错概率,比如漏判空指针、压栈顺序颠倒
递归 vs 迭代:性能与可读性的实际权衡
别迷信“迭代一定更快”——现代编译器对尾递归优化有限,且函数调用开销在树不深时几乎可忽略;真正影响选择的是场景约束。
- 嵌入式或栈空间受限环境(如裸机、RTOS),必须用迭代,哪怕多写 20 行
- LeetCode 类题目优先递归:代码短、调试快、边界清晰;但提交前务必检查
root == nullptr分支 - 需要中断遍历(如找到目标即返回)、或中途修改节点状态时,迭代更容易插入 break / continue / 状态更新逻辑
- 递归的局部变量自动生命周期管理,迭代需手动维护临时容器(
std::stack、vector等),释放时机易被忽略
常见错误:空指针、入栈顺序、访问时机三连坑
90% 的 DFS 实现 bug 集中在这三点,尤其混合使用智能指针或自定义分配器时更隐蔽。
- 忘记在
push前判空:写成st.push(root->left)而非if (root->left) st.push(root->left),运行时报segmentation fault - 前序迭代中把「左→右」入栈误写成「右→左」,结果变成根→右→左,输出序列反了
- 中序迭代里在
cur = cur->right前没先访问当前节点,导致跳过该层值 - 递归版本传参用值传递
dfs(TreeNode root)(C++ 中会拷贝整个树结构!),正确写法必须是dfs(TreeNode* root)或dfs(const TreeNode& root)
实际写的时候,先按递归把逻辑跑通,再根据部署环境或性能要求决定是否转迭代。后序遍历的迭代写法尤其容易翻车,建议真要手写就用双栈法——虽然多一个容器,但逻辑干净,比标记法少两个 if 判断。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











