C++实现二叉树的后序遍历(非递归) _ 双栈法逻辑详述【源码】

风杰酱_1106

风杰酱_1106

2026-04-11

578人浏览

原创

后序非递归必须用双栈,单栈无法判断左右子树是否均已访问;双栈法通过s1生成“根→右→左”序列并压入s2,再弹出s2即得左→右→根的后序序列。

c++实现二叉树的后序遍历(非递归) _ 双栈法逻辑详述【源码】

为什么后序非递归必须用双栈,单栈不行

单栈模拟后序遍历时,核心矛盾在于:你无法在弹出节点的瞬间判断「它的左右子树是否都已访问过」。中序可以靠「左链压到底再弹」来锚定访问时机,前序靠「先访根再压右左」就能保证顺序,但后序要求左→右→根,而根必须最后出现——这意味着根节点得在它两个子树全部处理完之后才输出。单栈没有额外状态记录,一旦弹出根,就丢失了“右子树还没走”的上下文。

双栈法绕开了这个状态管理难题:它不直接求后序,而是先构造一个「根→右→左」的序列(这和前序只差压栈顺序),再把整个序列倒过来——而栈天然支持倒序,所以第二栈就是个缓冲容器。

  • s1 扮演变形前序栈:每次 pop 一个节点,立刻 push 到 s2;然后按「先左后右」压回 s1(注意:不是先右后左)
  • 压栈顺序反直觉:为让 s1 弹出顺序是「根→右→左」,你得先压左、再压右——因为栈 FILO,后压的右会先弹出
  • s2 最终内容就是后序逆序,pop 出来自然就是左→右→根

双栈法 C++ 实现要点与易错点

关键不在写对,而在写稳。下面这段代码经多个边界测试验证(空树、单节点、左偏树、右偏树):

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> res;
    if (!root) return res;
<pre class="brush:php;toolbar:false;">stack<TreeNode*> s1, s2;
s1.push(root);

while (!s1.empty()) {
    TreeNode* node = s1.top(); s1.pop();
    s2.push(node);
    // 注意:这里先 push left,再 push right
    if (node->left)  s1.push(node->left);
    if (node->right) s1.push(node->right);
}

while (!s2.empty()) {
    res.push_back(s2.top()->val);
    s2.pop();
}
return res;

}

常见翻车点:

C++14
C++14

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

下载
  • 把 s1 的压栈顺序写成「先右后左」——那样 s2 得到的是「根→左→右」,倒过来是「右→左→根」,错
  • 漏判 root == nullptr,导致 s1.push(nullptr) 后崩溃
  • 误在 s2 循环里用 top() 但没 pop(),死循环
  • 用 vector 模拟栈(比如 push_back/pop_back)代替 stack,语义不清且易索引越界

双栈法 vs 单栈+标记法:选哪个更实际

单栈+bool标记(如 pair<treenode bool></treenode>)也能实现后序,逻辑上更贴近「我刚从哪来」的回溯感,但实际编码负担更大:每轮要拆包、判标记、决定是继续下探还是输出。双栈法虽然多用一倍内存,但逻辑线性、分支少、无状态切换,更适合面试手写或嵌入式等强调可读性与稳定性的场景。

性能上,双栈法时间复杂度仍是 O(n),空间是 O(h)(h 为树高),和单栈法一致;但常数项略高——两套栈操作、两次遍历。不过现代 CPU 缓存友好,这点开销几乎不可测。

  • 如果你在调试期,优先用双栈:错一次就能看到 s2 里塞了什么,方便断点观察
  • 如果树极深(>10⁵ 层)且内存敏感,考虑改用 Morris 后序(但实现复杂度陡增)
  • 永远别为了“节省一个栈”去硬啃单栈 while 套 while + 标记位的写法,除非你在写底层库

层序遍历为什么不用栈而用队列

这是个容易被连带混淆的点:后序非递归用双栈,是因为它本质在做「逆序控制」;而层序遍历目标是「同层节点一起出来」,天然需要 FIFO 行为——先进来的节点,它的子节点也该排在后面节点的子节点之前被处理。队列的 push/pop 顺序正好匹配这一需求。

拿双栈去套层序只会让逻辑扭曲:你得不断在两个栈之间倒腾,还要记录层级边界,远不如一个 queue + for (int i = q.size(); i > 0; --i) 清晰。

真正容易被忽略的是:所有非递归遍历中,只有后序的双栈法存在「结果需二次反转」这个隐含步骤。其他遍历(前序、中序、层序)的结果都是边处理边收集,而双栈后序必须等 s1 空了,才能开始从 s2 取最终答案——这个延迟交付,是它区别于其他迭代法最本质的特征。

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

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.25

354

5

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

6199

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3005

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

3868

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

2705

10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

2023.07.18

5647

5

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2023.08.10

2448

6

Revel框架模板渲染与表单验证
Revel框架模板渲染与表单验证

本专题围绕Revel框架视图层开发,介绍模板语法、变量输出、布局复用、表单提交、参数绑定与验证规则,帮助开发者完成安全规范的页面交互。

2026.10.16

0

15

Revel框架MVC路由与控制器
Revel框架MVC路由与控制器

本专题聚焦Revel框架MVC核心机制,讲解路由定义、控制器Action、参数绑定、请求响应和反向路由,并结合常见场景梳理接口请求处理流程。

2026.10.16

0

15

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
GDB Reference Card
GDB Reference Card

共0课时 | 0人学习

《Debugging with GDB》用户手册
《Debugging with GDB》用户手册

共0课时 | 0人学习

Valgrind FAQ
Valgrind FAQ

共0课时 | 0人学习