二叉树节点必须用指针字段定义,如Left *TreeNode;三种递归遍历仅访问根节点顺序不同;迭代遍历需手动维护栈或队列,层序用切片模拟FIFO队列。

二叉树节点定义必须包含指针字段
Go 里没有引用类型,所有结构体默认值拷贝,所以 TreeNode 的左右子节点必须声明为指针类型,否则递归遍历时会丢失连接。常见错误是写成 Left TreeNode,导致插入后子树无法被访问。
- 正确写法:
type TreeNode struct { Val int; Left *TreeNode; Right *TreeNode } - 如果用切片模拟树(如力扣部分题),注意索引计算:左子节点下标是
2*i + 1,右子是2*i + 2,但这是数组表示法,和指针树无关 - 初始化空节点一律用
nil,别用零值结构体,否则if node == nil判断失效
递归实现前/中/后序遍历只需改语句顺序
三种遍历本质区别仅在于“访问根节点”这一步的位置,递归代码几乎一样。容易错的是后序把 append 放在最后却忘了递归调用必须先完成。
func inorderTraversal(root *TreeNode) []int {
if root == nil {
return []int{}
}
var res []int
res = append(res, inorderTraversal(root.Left)...)
res = append(res, root.Val) // ← 中序:根在中间
res = append(res, inorderTraversal(root.Right)...)
return res
}
- 前序:访问 → 左 → 右(
append在最前) - 中序:左 → 访问 → 右(如上)
- 后序:左 → 右 → 访问(
append在最后,且左右递归必须已返回) - 注意切片拼接的开销,高频调用建议传入
[]int参数复用底层数组
迭代遍历必须用栈模拟系统调用栈
Go 没有尾递归优化,深度过大会栈溢出,生产环境建议迭代。核心是用 []*TreeNode 手动维护调用栈,关键点在于“什么时候把节点加入结果”以及“是否已访问过其子树”。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 前序迭代:先压右再压左,每次 pop 后立即
append值 —— 因为根优先 - 中序迭代:一路向左压栈,遇到
nil就 pop 并记录值,再转向右子树 - 后序最难:标准做法是压栈时附带标记位,或用两个栈,或反向记录(根→右→左)再反转结果
- 别直接用
for range遍历栈切片,要手动控制len(stack) > 0和stack = stack[:len(stack)-1]
层序遍历依赖队列,但 Go 没内置,用切片模拟即可
层序本质是 BFS,需要 FIFO。用 []*TreeNode 模拟队列足够轻量,避免引入额外依赖。常见坑是误用栈逻辑(LIFO)导致变成 DFS。
func levelOrder(root *TreeNode) [][]int {
if root == nil {
return [][]int{}
}
var res [][]int
queue := []*TreeNode{root}
for len(queue) > 0 {
levelSize := len(queue)
var level []int
for i := 0; i
- 每次循环开始前记下当前队列长度,确保只处理本层节点
- 不要在循环内动态修改
queue长度后再用range,会导致漏节点或 panic - 如果只要每层第一个/最后一个节点,可在内层循环开头/结尾取
queue[0]或queue[levelSize-1]
实际写的时候,递归够用就别硬套迭代;但一旦树深度超千级,或要求 O(1) 栈空间,就得认真处理标记位和双栈逻辑。后序迭代的标记方案最容易漏掉“已访问过子树”的状态判断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










