递归:visit 写在哪,就是哪种序
遍历的定义本身就是递归的:遍历一棵树 = 访问根、遍历左子树、遍历右子树。把这三件事写成 dfs(node) 里的三句话,前 / 中 / 后序的唯一区别就是 visit(node) 这一句相对 dfs(node.left) 与
dfs(node.right) 摆在哪个位置:之前是前序、之间是中序、之后是后序。其余代码一字不差。
本页约定: 演示树是二叉搜索树 (BST)(左 < 根 < 右),故中序输出恰为升序,便于对照。当前正在执行的节点 node 用橙色描边;已访问节点填实心青色。下方调用栈那条横条 =
从根到当前节点的祖先链——每次进入 dfs 入栈、返回时出栈,它就是递归占用的 O(h) 额外空间(h 为树高)。
三序只差一行的位置。 切换上面的开关,盯住代码里 visit(node) 那一句:它从 dfs(left) 之前(前序)挪到两个 dfs 之间(中序)、再挪到之后(后序),输出序列随之改变,而调用栈的压入 /
弹出节奏完全一样——这说明「访问什么顺序」与「怎么走完树」是两件独立的事。
1 · 递归的代价:调用栈与爆栈风险
递归看似没有「数据结构」,但它借用了语言运行时的调用栈:每层 dfs 的局部变量、返回地址都压在调用栈上,栈的最大深度 = 树高 h。平衡树 h ≈ log n,但退化成链(如按升序插入 BST)时 h =
n——试试上面的「右链」预设,看调用栈一路压到底。深度很大时(几万层)可能爆栈 (stack overflow),这正是显式栈迭代把调用栈搬到堆上、以及 Morris 索性省掉栈的动机。