您现在的位置是:烟消火灭网 > 热点

后序遍历

烟消火灭网2026-10-17 07:46:41【热点】7人已围观

简介```html 后序遍历 body { font-family: 'Georgia', '宋体', serif; line-height: 1.8; max-width: 720px; ma

```html 后序遍历

后序遍历

去年冬天,我在调试一棵表达式树。后序遍历中缀转后缀,后序遍历后缀建树,后序遍历然后计算。后序遍历代码写得很顺,后序遍历直到需要释放整棵树的后序遍历内存。我盯着屏幕,后序遍历脑子里蹦出一个词:后序遍历。后序遍历先处理左右孩子,后序遍历最后回到根节点——这不就是后序遍历最自然的释放顺序吗?

后序遍历的规则简单到一句话就能说完:左、右、后序遍历根。后序遍历递归写法三行代码。后序遍历但就是后序遍历这三行,让我在面试里挂过一次。面试官问:“后序遍历的迭代实现会吗?”我心想,用栈模拟递归嘛,先压右再压左,最后输出。结果写出来顺序全乱——根节点跑到了中间。后来才明白,那不过是“逆后序”的变种,真正的后序迭代需要额外标记或双栈配合。你看,越简单的规则,实现起来越容易踩坑。

我见过很多人把后序遍历当成前序的镜像。前序是根左右,后序是左右根,看起来只是顺序颠倒。但递归展开后,事情没那么对称。前序的根节点在第一次访问时就处理,后序的根节点要等到左右子树全部处理完才轮得到。这就导致一个有趣的现象:后序遍历序列的最后一个元素永远是整棵树的根。这个性质在重建二叉树时特别有用——给定中序和后序,你能唯一确定一棵树;但给定前序和后序,往往不行。为什么?因为后序的根在末尾,前序的根在开头,你无法仅凭这两个序列区分左右子树的边界。

写编译器那会儿,语法分析树的后序遍历直接对应逆波兰表达式。比如表达式 (a + b) * c,后序输出是 a b + c *。计算机读这个序列,不需要括号,也不需要优先级表,一个栈就能算出来。这让我觉得后序遍历有种“去结构化”的美感——它把树形结构的依赖关系压扁成线性序列,而且恰好符合“先算孩子再算父亲”的计算逻辑。你想想,任何表达式求值,不都是这个顺序吗?

不过后序遍历最让我着迷的地方,是它和实际问题的对应关系。文件系统的磁盘占用统计:一个目录的大小等于它所有子目录和文件大小之和。你要统计根目录,就得先递归统计每个子目录。这就是后序。再比如,Git 仓库里计算每个提交的变更行数,也得先遍历所有祖先提交,最后汇总到当前提交。这些场景里,根节点的处理必须发生在所有后代之后,否则数据不完整。

我有个习惯:写递归函数时,先写递归基,再写递归调用,最后写当前层的处理。这其实就是后序的思维模式——把当前节点的处理放在递归返回之后。很多人写递归喜欢先处理当前节点再递归,那是前序。前序适合“自顶向下”传递信息,后序适合“自底向上”汇总结果。

上周帮同事看一段代码,他用后序遍历求树的高度。逻辑没错:空节点返回 0,否则返回左右子树高度的最大值加 1。但他把加 1 写在了递归调用之前。结果每层都多算了一次,高度翻倍。我指出来时他愣了一下:“顺序还有影响?”当然有。后序的“根”操作必须在左右子树返回之后,这是它区别于前序和中序的本质。前序的根操作在递归之前,中序在左递归之后、右递归之前。三种顺序,三种时机,对应三种不同的信息流方向。

夜深了,我关掉编辑器。那棵表达式树的内存已经干净释放,后序遍历的调用栈也早已清空。但脑子里还在转:为什么人类直觉上更习惯前序?读一本书,从第一章开始;走迷宫,从入口开始。可后序遍历偏偏要你先走到最深处,再一步步退回来。也许正因为反直觉,它才值得多写几遍。下次再有人问我后序迭代怎么写,我大概会先画个栈,再画个标记位,然后说——别急,先让左右孩子走完。

```

很赞哦!(2925)