递归遍历必须先写 if node == nil { return },否则访问 nil.Val 会 panic;迭代中序需用 visited 标记或两阶段压栈;层序推荐切片模拟队列;TreeNode 字段必须为 *TreeNode 指针。

递归遍历只改一行,但 nil 判断不能漏
Go 中写 preorderTraversal、inorderTraversal、postorderTraversal 三个函数,骨架完全一样,区别仅在 res = append(res, node.Val) 这行代码的位置。但几乎所有新手第一次运行都会 panic,原因就一个:if node == nil 判断被跳过或写反。
- 必须写成
if node == nil { return },而不是if node != nil { ... }后不加return - 一旦漏判,
node.Left或node.Right为nil时继续递归,下一层访问nil.Val就触发invalid memory address or nil pointer dereference - 三种顺序的递归体里,只有这行位置变:前序在递归左/右之前,中序在递归左之后、递归右之前,后序在两个递归调用之后
迭代中序遍历不是“压左弹右”,得带状态标记
很多人照着“一路压左、弹出、转向右”写迭代中序,结果输出乱序或漏节点。问题出在:弹出一个节点后,你不知道它是否已处理过左子树——这需要额外信息。
- 错误做法:
stack []*TreeNode只存指针,无法区分“刚入栈”和“左子树已遍历完” - 正确做法:用
stack []struct{ node *TreeNode; visited bool },遇到未访问节点先压自身(visited: false),再压左子节点;遇到已访问节点才append值,并压右子节点 - 另一种等价方案是两阶段压栈:先压
node(打标记),再压node.Right和node.Left(不打标记),靠栈序控制访问时机
层序遍历别硬套递归,切片模拟队列最稳
所谓“递归实现层序”,本质是按深度传参 dfs(node, depth),把值塞进 res[depth] 对应切片。但它在真实工程中基本不用,因为有硬伤。
- 必须提前预估树高分配
res切片,否则频繁扩容影响性能 - 无法自然支持“每层单独处理”的逻辑(比如求每层最大值、判断是否对称)
- 推荐用切片模拟 FIFO 队列:
queue := []*TreeNode{root},每次queue = queue[1:]出队,queue = append(queue, node.Left, node.Right)入队 - 注意:入队前必须判
node.Left != nil,否则nil被加入队列,后续解引用 panic
TreeNode 定义必须用指针字段,否则树结构就断了
Go 没有引用类型,结构体默认值拷贝。如果把 Left 和 Right 定义成 TreeNode 值类型,插入节点时只会拷贝一份,原树的连接关系完全丢失。
立即学习“go语言免费学习笔记(深入)”;
- 正确写法:
type TreeNode struct { Val int; Left *TreeNode; Right *TreeNode } - 初始化空节点一律用
nil,别用TreeNode{}——后者不是空指针,if node == nil判断永远为 false - 所有遍历函数参数都必须是
*TreeNode,否则传入的是副本,递归修改不影响原始树
nil 判断的位置和逻辑,比遍历顺序本身更容易决定程序是否崩溃**。


















