Skip to content

树与二叉树

树是数据结构中第一个真正「非线性」的结构。它用层次化的方式组织数据,在文件系统、数据库索引、编译器语法树、路由表等场景无处不在。二叉树是树中最简单也最重要的形式,它的遍历、搜索、平衡操作是面试的高频考点。本篇将从树的基本概念讲起,覆盖二叉树的四种遍历(前中后序 + 层序)、Morris 遍历的 O(1) 空间技巧、二叉搜索树的增删查、AVL 和红黑树概念,以及 Go 标准库 container/heap 的用法,最后用 LeetCode 真题收尾。理解树,是打开后续堆、图、字典树等结构大门的钥匙。

一、树的基本概念

树是一种由节点组成的层次结构,满足:

  1. 有且仅有一个根节点(root),没有父节点。
  2. 其余节点有且仅有一个父节点。
  3. 节点之间通过边连接,无环。

常见术语:

  • 节点(Node):树的基本单元。
  • 根(Root):顶层的节点。
  • 叶节点(Leaf):没有子节点的节点。
  • 深度(Depth):从根到该节点的边数(根深度为 0)。
  • 高度(Height):从该节点到最深叶节点的边数(叶节点高度为 0)。
  • 度(Degree):节点的子节点个数。
  • 层(Level):根为第 1 层,往下递增。

二叉树是每个节点最多有两个子节点(左、右)的树。满二叉树:每个节点要么是叶,要么有两个子。完全二叉树:除最后一层外全满,最后一层从左到右连续。

二、二叉树

1. 实现:节点结构体

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

// 辅助:按层序构建完全二叉树(用 -1 表示空节点,简化测试)
func buildTree(vals []int) *TreeNode {
	if len(vals) == 0 || vals[0] == -1 {
		return nil
	}
	root := &TreeNode{Val: vals[0]}
	queue := []*TreeNode{root}
	i := 1
	for len(queue) > 0 && i < len(vals) {
		node := queue[0]
		queue = queue[1:]
		if i < len(vals) && vals[i] != -1 {
			node.Left = &TreeNode{Val: vals[i]}
			queue = append(queue, node.Left)
		}
		i++
		if i < len(vals) && vals[i] != -1 {
			node.Right = &TreeNode{Val: vals[i]}
			queue = append(queue, node.Right)
		}
		i++
	}
	return root
}

func main() {
	root := buildTree([]int{1, 2, 3, 4, 5, -1, 6})
	fmt.Println("根节点:", root.Val)
	fmt.Println("左子:", root.Left.Val)
	fmt.Println("右子:", root.Right.Val)
}

2. 遍历:前序、中序、后序(递归 + 迭代)

前序(根左右)、中序(左根右)、后序(左右根)是三种深度优先遍历。中序遍历二叉搜索树会得到有序序列。

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

// 递归前序
func preorderRecursive(root *TreeNode) []int {
	var result []int
	var dfs func(*TreeNode)
	dfs = func(node *TreeNode) {
		if node == nil {
			return
		}
		result = append(result, node.Val)
		dfs(node.Left)
		dfs(node.Right)
	}
	dfs(root)
	return result
}

// 递归中序
func inorderRecursive(root *TreeNode) []int {
	var result []int
	var dfs func(*TreeNode)
	dfs = func(node *TreeNode) {
		if node == nil {
			return
		}
		dfs(node.Left)
		result = append(result, node.Val)
		dfs(node.Right)
	}
	dfs(root)
	return result
}

// 递归后序
func postorderRecursive(root *TreeNode) []int {
	var result []int
	var dfs func(*TreeNode)
	dfs = func(node *TreeNode) {
		if node == nil {
			return
		}
		dfs(node.Left)
		dfs(node.Right)
		result = append(result, node.Val)
	}
	dfs(root)
	return result
}

// 迭代前序:用栈
func preorderIterative(root *TreeNode) []int {
	var result []int
	if root == nil {
		return result
	}
	stack := []*TreeNode{root}
	for len(stack) > 0 {
		node := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		result = append(result, node.Val)
		// 先压右后压左,保证左先出
		if node.Right != nil {
			stack = append(stack, node.Right)
		}
		if node.Left != nil {
			stack = append(stack, node.Left)
		}
	}
	return result
}

// 迭代中序:用栈
func inorderIterative(root *TreeNode) []int {
	var result []int
	var stack []*TreeNode
	cur := root
	for cur != nil || len(stack) > 0 {
		// 一路向左压栈
		for cur != nil {
			stack = append(stack, cur)
			cur = cur.Left
		}
		cur = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		result = append(result, cur.Val)
		cur = cur.Right
	}
	return result
}

// 迭代后序:前序「根左右」改成「根右左」再反转
func postorderIterative(root *TreeNode) []int {
	var result []int
	if root == nil {
		return result
	}
	stack := []*TreeNode{root}
	for len(stack) > 0 {
		node := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		result = append(result, node.Val)
		if node.Left != nil {
			stack = append(stack, node.Left)
		}
		if node.Right != nil {
			stack = append(stack, node.Right)
		}
	}
	// 反转得到「左右根」
	for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
		result[i], result[j] = result[j], result[i]
	}
	return result
}

func buildTree(vals []int) *TreeNode {
	if len(vals) == 0 || vals[0] == -1 {
		return nil
	}
	root := &TreeNode{Val: vals[0]}
	queue := []*TreeNode{root}
	i := 1
	for len(queue) > 0 && i < len(vals) {
		node := queue[0]
		queue = queue[1:]
		if i < len(vals) && vals[i] != -1 {
			node.Left = &TreeNode{Val: vals[i]}
			queue = append(queue, node.Left)
		}
		i++
		if i < len(vals) && vals[i] != -1 {
			node.Right = &TreeNode{Val: vals[i]}
			queue = append(queue, node.Right)
		}
		i++
	}
	return root
}

func main() {
	root := buildTree([]int{1, 2, 3, 4, 5, -1, 6})
	fmt.Println("前序(递归):", preorderRecursive(root))
	fmt.Println("前序(迭代):", preorderIterative(root))
	fmt.Println("中序(递归):", inorderRecursive(root))
	fmt.Println("中序(迭代):", inorderIterative(root))
	fmt.Println("后序(递归):", postorderRecursive(root))
	fmt.Println("后序(迭代):", postorderIterative(root))
}

3. 层序遍历(BFS)

层序遍历用队列实现,前面栈与队列篇已演示过。这里给出返回每一层的版本:

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func levelOrder(root *TreeNode) [][]int {
	var result [][]int
	if root == nil {
		return result
	}
	queue := []*TreeNode{root}
	for len(queue) > 0 {
		size := len(queue)
		level := make([]int, 0, size)
		for i := 0; i < size; i++ {
			node := queue[0]
			queue = queue[1:]
			level = append(level, node.Val)
			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
		result = append(result, level)
	}
	return result
}

func main() {
	root := &TreeNode{Val: 1,
		Left:  &TreeNode{Val: 2, Left: &TreeNode{Val: 4}, Right: &TreeNode{Val: 5}},
		Right: &TreeNode{Val: 3, Right: &TreeNode{Val: 6}}}
	fmt.Println("层序:", levelOrder(root))
}

4. Morris 遍历(O(1) 空间)

Morris 遍历利用叶节点的空指针临时指向后继,实现 O(1) 空间的中序遍历。核心:对每个节点,找到其左子树的最右节点,把它的右指针指向当前节点,遍历完左子树后能回到当前节点。

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func morrisInorder(root *TreeNode) []int {
	var result []int
	cur := root
	for cur != nil {
		if cur.Left == nil {
			result = append(result, cur.Val)
			cur = cur.Right
		} else {
			// 找前驱节点(左子树最右)
			prev := cur.Left
			for prev.Right != nil && prev.Right != cur {
				prev = prev.Right
			}
			if prev.Right == nil {
				// 建立临时链接
				prev.Right = cur
				cur = cur.Left
			} else {
				// 已访问过,恢复树结构
				prev.Right = nil
				result = append(result, cur.Val)
				cur = cur.Right
			}
		}
	}
	return result
}

func main() {
	root := &TreeNode{Val: 4,
		Left:  &TreeNode{Val: 2, Left: &TreeNode{Val: 1}, Right: &TreeNode{Val: 3}},
		Right: &TreeNode{Val: 6, Left: &TreeNode{Val: 5}}}
	fmt.Println("Morris 中序:", morrisInorder(root)) // [1 2 3 4 5 6]
}

Morris 遍历的时间复杂度仍是 O(n),但空间复杂度从 O(h) 降到 O(1)。代价是会临时修改树结构(虽然最终恢复),且代码相对复杂。

三、二叉搜索树(BST)

BST 满足:左子树所有节点值 < 根 < 右子树所有节点值。它的中序遍历是有序的,查找/插入/删除平均 O(log n)。

1. 插入、删除、查找

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

type BST struct {
	root *TreeNode
}

// 插入
func (b *BST) Insert(val int) {
	b.root = insert(b.root, val)
}

func insert(node *TreeNode, val int) *TreeNode {
	if node == nil {
		return &TreeNode{Val: val}
	}
	if val < node.Val {
		node.Left = insert(node.Left, val)
	} else if val > node.Val {
		node.Right = insert(node.Right, val)
	}
	return node
}

// 查找
func (b *BST) Search(val int) bool {
	return search(b.root, val)
}

func search(node *TreeNode, val int) bool {
	if node == nil {
		return false
	}
	if val == node.Val {
		return true
	}
	if val < node.Val {
		return search(node.Left, val)
	}
	return search(node.Right, val)
}

// 删除:分三种情况
func (b *BST) Delete(val int) {
	b.root = deleteNode(b.root, val)
}

func deleteNode(node *TreeNode, val int) *TreeNode {
	if node == nil {
		return nil
	}
	if val < node.Val {
		node.Left = deleteNode(node.Left, val)
	} else if val > node.Val {
		node.Right = deleteNode(node.Right, val)
	} else {
		// 找到目标
		if node.Left == nil {
			return node.Right
		}
		if node.Right == nil {
			return node.Left
		}
		// 两个孩子:用右子树最小值替换
		minNode := node.Right
		for minNode.Left != nil {
			minNode = minNode.Left
		}
		node.Val = minNode.Val
		node.Right = deleteNode(node.Right, minNode.Val)
	}
	return node
}

// 中序遍历验证有序性
func (b *BST) Inorder() []int {
	var result []int
	var dfs func(*TreeNode)
	dfs = func(n *TreeNode) {
		if n == nil {
			return
		}
		dfs(n.Left)
		result = append(result, n.Val)
		dfs(n.Right)
	}
	dfs(b.root)
	return result
}

func main() {
	bst := &BST{}
	values := []int{5, 3, 7, 1, 4, 6, 8}
	for _, v := range values {
		bst.Insert(v)
	}
	fmt.Println("中序(有序):", bst.Inorder())
	fmt.Println("查找 4:", bst.Search(4))
	fmt.Println("查找 9:", bst.Search(9))
	bst.Delete(3)
	fmt.Println("删除 3 后:", bst.Inorder())
}

2. 验证 BST

判断一棵树是否是合法 BST。错误做法:只比较 node 与左右孩子;正确做法:传递上下界。

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func isValidBST(root *TreeNode) bool {
	return validate(root, nil, nil)
}

// min、max 用指针以便区分「未设置」和「值为 0」
func validate(node *TreeNode, min, max *int) bool {
	if node == nil {
		return true
	}
	if min != nil && node.Val <= *min {
		return false
	}
	if max != nil && node.Val >= *max {
		return false
	}
	return validate(node.Left, min, &node.Val) && validate(node.Right, &node.Val, max)
}

func main() {
	root := &TreeNode{Val: 5,
		Left:  &TreeNode{Val: 1},
		Right: &TreeNode{Val: 7, Left: &TreeNode{Val: 6}, Right: &TreeNode{Val: 8}}}
	fmt.Println("是合法 BST?", isValidBST(root)) // true
}

四、平衡二叉树

普通 BST 在数据有序插入时会退化成链表,操作变 O(n)。平衡树通过旋转保持高度在 O(log n)。

1. AVL 树

AVL 树要求每个节点的左右子树高度差不超过 1。插入删除后通过四种旋转(LL、RR、LR、RL)恢复平衡。这里给出 AVL 节点和插入的核心代码:

go
package main

import "fmt"

type AVLNode struct {
	Val   int
	Left  *AVLNode
	Right *AVLNode
	height int
}

func height(n *AVLNode) int {
	if n == nil {
		return 0
	}
	return n.height
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func updateHeight(n *AVLNode) {
	n.height = 1 + max(height(n.Left), height(n.Right))
}

func balanceFactor(n *AVLNode) int {
	return height(n.Left) - height(n.Right)
}

// 右旋
func rotateRight(y *AVLNode) *AVLNode {
	x := y.Left
	t := x.Right
	x.Right = y
	y.Left = t
	updateHeight(y)
	updateHeight(x)
	return x
}

// 左旋
func rotateLeft(x *AVLNode) *AVLNode {
	y := x.Right
	t := y.Left
	y.Left = x
	x.Right = t
	updateHeight(x)
	updateHeight(y)
	return y
}

func insert(n *AVLNode, val int) *AVLNode {
	if n == nil {
		return &AVLNode{Val: val, height: 1}
	}
	if val < n.Val {
		n.Left = insert(n.Left, val)
	} else if val > n.Val {
		n.Right = insert(n.Right, val)
	} else {
		return n
	}
	updateHeight(n)
	bf := balanceFactor(n)
	// LL
	if bf > 1 && val < n.Left.Val {
		return rotateRight(n)
	}
	// RR
	if bf < -1 && val > n.Right.Val {
		return rotateLeft(n)
	}
	// LR
	if bf > 1 && val > n.Left.Val {
		n.Left = rotateLeft(n.Left)
		return rotateRight(n)
	}
	// RL
	if bf < -1 && val < n.Right.Val {
		n.Right = rotateRight(n.Right)
		return rotateLeft(n)
	}
	return n
}

func inorder(n *AVLNode) []int {
	var result []int
	if n == nil {
		return result
	}
	result = append(result, inorder(n.Left)...)
	result = append(result, n.Val)
	result = append(result, inorder(n.Right)...)
	return result
}

func main() {
	var root *AVLNode
	// 顺序插入 1..7,普通 BST 会退化成链表,AVL 保持平衡
	for _, v := range []int{1, 2, 3, 4, 5, 6, 7} {
		root = insert(root, v)
	}
	fmt.Println("AVL 中序:", inorder(root))
	fmt.Println("根节点:", root.Val, "高度:", root.height)
}

2. 红黑树概念

红黑树是另一种广泛使用的平衡树(C++ 的 std::map、Java 的 TreeMap、Go 的 syscall 等)。它的平衡条件比 AVL 宽松:不要求严格平衡,只要求最长路径不超过最短路径的两倍。

红黑树五条性质:

  1. 每个节点是红色或黑色。
  2. 根是黑色。
  3. 叶节点(NIL)是黑色。
  4. 红节点的子节点必须是黑色(不能有连续红节点)。
  5. 任一节点到其叶节点的所有路径包含相同数量的黑节点。

红黑树插入删除通过变色 + 旋转维护平衡,旋转次数比 AVL 少,适合写多读少的场景。Go 标准库 container/list 没用红黑树,但 runtime 内部、golang.org/x/exp/slices 排序等地方有相关思想。

五、Go 标准库:container/heap

container/heap 实现了最小堆,提供 heap.Interface 接口。基于它可以构建优先队列,下一篇会专门讲堆。这里先看一个最小堆的简单用法:

go
package main

import (
	"container/heap"
	"fmt"
)

type IntHeap []int

func (h IntHeap) Len() int            { return len(h) }
func (h IntHeap) Less(i, j int) bool  { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x any)         { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() any {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[:n-1]
	return x
}

func main() {
	h := &IntHeap{5, 3, 8, 1, 9}
	heap.Init(h)
	heap.Push(h, 0)
	fmt.Println("堆顶:", (*h)[0])
	for h.Len() > 0 {
		fmt.Printf("%d ", heap.Pop(h))
	}
	fmt.Println()
}

六、LeetCode 经典题解析

1. 二叉树的最大深度(LC 104)

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func maxDepth(root *TreeNode) int {
	if root == nil {
		return 0
	}
	left := maxDepth(root.Left)
	right := maxDepth(root.Right)
	if left > right {
		return left + 1
	}
	return right + 1
}

func main() {
	root := &TreeNode{Val: 3,
		Left:  &TreeNode{Val: 9},
		Right: &TreeNode{Val: 20, Left: &TreeNode{Val: 15}, Right: &TreeNode{Val: 7}}}
	fmt.Println("最大深度:", maxDepth(root)) // 3
}

2. 对称二叉树(LC 101)

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func isSymmetric(root *TreeNode) bool {
	if root == nil {
		return true
	}
	return isMirror(root.Left, root.Right)
}

func isMirror(a, b *TreeNode) bool {
	if a == nil && b == nil {
		return true
	}
	if a == nil || b == nil {
		return false
	}
	return a.Val == b.Val && isMirror(a.Left, b.Right) && isMirror(a.Right, b.Left)
}

func main() {
	root := &TreeNode{Val: 1,
		Left:  &TreeNode{Val: 2, Left: &TreeNode{Val: 3}, Right: &TreeNode{Val: 4}},
		Right: &TreeNode{Val: 2, Left: &TreeNode{Val: 4}, Right: &TreeNode{Val: 3}}}
	fmt.Println("对称?", isSymmetric(root)) // true
}

3. 翻转二叉树(LC 226)

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func invertTree(root *TreeNode) *TreeNode {
	if root == nil {
		return nil
	}
	root.Left, root.Right = root.Right, root.Left
	invertTree(root.Left)
	invertTree(root.Right)
	return root
}

func preorder(root *TreeNode) []int {
	if root == nil {
		return nil
	}
	return append(append([]int{root.Val}, preorder(root.Left)...), preorder(root.Right)...)
}

func main() {
	root := &TreeNode{Val: 4,
		Left:  &TreeNode{Val: 2, Left: &TreeNode{Val: 1}, Right: &TreeNode{Val: 3}},
		Right: &TreeNode{Val: 7, Left: &TreeNode{Val: 6}, Right: &TreeNode{Val: 9}}}
	fmt.Println("翻转前:", preorder(root))
	invertTree(root)
	fmt.Println("翻转后:", preorder(root))
}

4. 二叉搜索树验证(LC 98)

前面已实现 isValidBST,注意要用上下界而非只比较父子。

5. 二叉搜索树第 K 小(LC 230)

利用中序遍历有序的特性,中序遍历到第 k 个即为答案。

go
package main

import "fmt"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func kthSmallest(root *TreeNode, k int) int {
	var result int
	var dfs func(*TreeNode) bool
	dfs = func(node *TreeNode) bool {
		if node == nil {
			return false
		}
		if dfs(node.Left) {
			return true
		}
		k--
		if k == 0 {
			result = node.Val
			return true
		}
		return dfs(node.Right)
	}
	dfs(root)
	return result
}

func main() {
	root := &TreeNode{Val: 5,
		Left:  &TreeNode{Val: 3, Left: &TreeNode{Val: 2, Left: &TreeNode{Val: 1}}, Right: &TreeNode{Val: 4}},
		Right: &TreeNode{Val: 6}}
	fmt.Println("第 3 小:", kthSmallest(root, 3)) // 3
}

七、复杂度分析

操作普通 BST(平均/最坏)AVL/红黑树说明
查找O(log n) / O(n)O(log n)最坏退化为链表
插入O(log n) / O(n)O(log n)
删除O(log n) / O(n)O(log n)
遍历O(n)O(n)
空间O(n)O(n)
Morris 遍历空间O(1)O(1)临时改树

八、小结

树是层次化数据的自然表示,本篇要点:

  1. 基本概念:根、叶、深度(从根算)、高度(从叶算)、度。二叉树每个节点最多两个孩子。
  2. 遍历:前序(根左右)、中序(左根右)、后序(左右根)有递归和迭代两种写法;层序用队列。中序遍历 BST 得到有序序列。
  3. Morris 遍历:利用叶节点空指针临时指向后继,实现 O(1) 空间中序遍历,代价是临时修改树结构。
  4. BST:左 < 根 < 右,查找/插入/删除平均 O(log n),但可能退化。删除节点分无子、一子、双子三种情况,双子用右子树最小值替换。
  5. 平衡树:AVL 严格平衡(高度差 ≤ 1),读多写少;红黑树宽松平衡,写多读少。Go 标准库 container/heap 用于堆场景。
  6. 验证 BST 必须用上下界,不能只比父子;第 K 小 用中序遍历到第 k 个即停。

树的学习难点在于递归思维的训练。建议每道题都先想清楚「当前节点要做什么、左右子树递归返回什么」,这就是「递归函数的定义」。

下一篇我们将聚焦堆——一种用数组实现的特殊完全二叉树,它是优先队列的标准实现,也是 Top K 问题、堆排序的核心。