二叉搜索树:有序性的结构表达

package main

import "fmt"

type BST struct {
    val         int
    left, right *BST
}

func (t *BST) Insert(val int) *BST {
    if t == nil {
        return &BST{val: val}
    }
    if val < t.val {
        t.left = t.left.Insert(val)
    } else if val > t.val {
        t.right = t.right.Insert(val)
    }
    return t
}

// BST 性质:中序遍历 = 升序序列
func (t *BST) InOrder() []int {
    if t == nil {
        return nil
    }
    result := t.left.InOrder()
    result = append(result, t.val)
    result = append(result, t.right.InOrder()...)
    return result
}

// 高度:衡量最坏情况复杂度
func (t *BST) Height() int {
    if t == nil {
        return 0
    }
    lh, rh := t.left.Height(), t.right.Height()
    if lh > rh {
        return lh + 1
    }
    return rh + 1
}

func main() {
    var root *BST
    for _, v := range []int{5, 3, 7, 1, 4, 6, 8} {
        root = root.Insert(v)
    }
    fmt.Println("中序遍历:", root.InOrder())  // [1 2 3 4 5 6 7 8]
    fmt.Println("树高:", root.Height())  // 3(平衡)

    // 最坏情况:顺序插入退化成链表
    var degenerate *BST
    for _, v := range []int{1, 2, 3, 4, 5, 6, 7} {
        degenerate = degenerate.Insert(v)
    }
    fmt.Println("退化树高:", degenerate.Height())  // 7(链表)
}

AVL 树:最严格的平衡

AVL 树要求每个节点的左右子树高度差(平衡因子)≤ 1。通过四种旋转(左旋、右旋、左右旋、右左旋)维持这个不变量。树高严格 ≤ 1.44 log₂(n+2),保证所有操作 O(log n)。代价是插入/删除时可能需要多次旋转,比红黑树常数略大。

package main

import "fmt"

type AVL struct {
    val, height int
    left, right *AVL
}

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

func updateHeight(n *AVL) {
    lh, rh := height(n.left), height(n.right)
    if lh > rh { n.height = lh + 1 } else { n.height = rh + 1 }
}

func balanceFactor(n *AVL) int { return height(n.left) - height(n.right) }

// 右旋(修复左重不平衡)
func rotateRight(y *AVL) *AVL {
    x := y.left
    y.left = x.right
    x.right = y
    updateHeight(y)
    updateHeight(x)
    return x
}

// 左旋
func rotateLeft(x *AVL) *AVL {
    y := x.right
    x.right = y.left
    y.left = x
    updateHeight(x)
    updateHeight(y)
    return y
}

func balance(n *AVL) *AVL {
    updateHeight(n)
    bf := balanceFactor(n)
    if bf > 1 {  // 左重
        if balanceFactor(n.left) < 0 { n.left = rotateLeft(n.left) }  // 左右情况
        return rotateRight(n)
    }
    if bf < -1 {  // 右重
        if balanceFactor(n.right) > 0 { n.right = rotateRight(n.right) }  // 右左情况
        return rotateLeft(n)
    }
    return n
}

func insert(n *AVL, val int) *AVL {
    if n == nil { return &AVL{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) }
    return balance(n)
}

func main() {
    var root *AVL
    for _, v := range []int{1, 2, 3, 4, 5, 6, 7} {  // 顺序插入
        root = insert(root, v)
    }
    fmt.Println("AVL 树高:", height(root))  // 3(自动平衡)
}

B 树:为磁盘 IO 优化的平衡树

B 树(B-tree)是 m 阶平衡树,每个节点最多 m-1 个键和 m 个子节点。树高 h = O(log_m n)m 越大,树越矮,磁盘 IO 越少。数据库索引用 B+ 树(所有数据在叶节点,叶节点用链表串联),范围查询只需扫描叶节点链表。

树结构查询插入/删除内存效率适用场景
BSTO(n) 最坏O(n) 最坏不推荐生产使用
AVL 树O(log n)O(log n)读多写少,严格平衡
红黑树O(log n)O(log n)写多场景(Go map 底层)
B/B+ 树O(log_m n)O(log_m n)磁盘数据库索引(MySQL、PostgreSQL)
口诀内存用红黑树,磁盘用 B+树。m 越大 IO 越少,但内存代价越高。
推荐做法
  • 需要有序操作(范围查找、最大最小)用平衡 BST
  • 需要持久化/大数据量索引用 B/B+ 树
  • Go 的 sort 包 + 有序切片可以替代简单场景的 BST
不推荐
  • 生产代码自己实现红黑树——用 github.com/google/btree
  • 以为 BST 查询总是 O(log n)——只有平衡时才是
常见误区
  • B+ 树叶节点的链表结构让范围查询 O(k+log n),而非 B 树的 O(k log n)

判断标准:能解释为什么数据库索引用 B+树而非红黑树 → 掌握本章。

树是图的简化,也是数据结构的骨架——理解树,你就理解了分层的本质。

— 算法导论