二叉搜索树(Binary Search Tree, BST)

一句话说明

二叉搜索树最值钱的性质不是“它是一棵树”,而是“中序遍历有序”,因此很多题都能转成有序数组上的二分思维。

先把核心性质刻进脑子

对任意节点 root:

  • 左子树所有节点值都 < root.Val
  • 右子树所有节点值都 > root.Val

所以 BST 的很多操作,本质都在做“朝一边缩小搜索范围”。

        8
      /   \
     3     10
    / \      \
   1   6      14
      / \    /
     4   7  13

这类题为什么像二分查找

普通二叉树里,找一个值可能要把整棵树都走完。
但 BST 不一样:

  • 如果目标比当前节点小,只去左边
  • 如果目标比当前节点大,只去右边

这和二分查找“每次排掉一半不可能区域”是同一个思路。

Go 节点定义

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

Go 代码:查找

func SearchBST(root *TreeNode, val int) *TreeNode {
    for root != nil {
        if root.Val == val {
            return root
        }
        if val < root.Val {
            root = root.Left
        } else {
            root = root.Right
        }
    }
    return nil
}

Go 代码:插入

插入的本质是先找到“应该挂在哪个空位上”。

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

Go 代码:删除

BST 删除之所以老让人卡住,是因为要分情况。

先记住三种情况

  • 没有子节点:直接删
  • 只有一个子节点:让孩子顶上来
  • 有两个子节点:用后继或前驱替换,再删那个替身节点

最常用的是“用右子树最小节点,也就是后继节点替换”。

func DeleteNode(root *TreeNode, key int) *TreeNode {
    if root == nil {
        return nil
    }
 
    if key < root.Val {
        root.Left = DeleteNode(root.Left, key)
        return root
    }
    if key > root.Val {
        root.Right = DeleteNode(root.Right, key)
        return root
    }
 
    if root.Left == nil {
        return root.Right
    }
    if root.Right == nil {
        return root.Left
    }
 
    successor := root.Right
    for successor.Left != nil {
        successor = successor.Left
    }
 
    root.Val = successor.Val
    root.Right = DeleteNode(root.Right, successor.Val)
    return root
}

Go 代码:验证 BST

很多人第一反应是“只比较左右孩子”,这是不够的。
真正要维护的是整棵子树的上下界。

func IsValidBST(root *TreeNode) bool {
    return validate(root, nil, nil)
}
 
func validate(node *TreeNode, low, high *int) bool {
    if node == nil {
        return true
    }
    if low != nil && node.Val <= *low {
        return false
    }
    if high != nil && node.Val >= *high {
        return false
    }
    return validate(node.Left, low, &node.Val) &&
        validate(node.Right, &node.Val, high)
}

中序遍历是 BST 的总开关

只要题目涉及下面这些关键词,就应该立刻想到中序遍历:

  • 第 k 小
  • 最小绝对差
  • 众数
  • 转换成递增结构

因为 BST 的中序遍历结果天然有序。

Go 代码:第 K 小元素

func KthSmallest(root *TreeNode, k int) int {
    stack := []*TreeNode{}
 
    for root != nil || len(stack) > 0 {
        for root != nil {
            stack = append(stack, root)
            root = root.Left
        }
 
        root = stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        k--
        if k == 0 {
            return root.Val
        }
        root = root.Right
    }
 
    return -1
}

Go 代码:最近公共祖先

这个题特别能体现 BST 的“二分味道”:

  • 两个节点都比根小,就往左
  • 两个节点都比根大,就往右
  • 一大一小,当前根就是分叉点
func LowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
    for root != nil {
        if p.Val < root.Val && q.Val < root.Val {
            root = root.Left
        } else if p.Val > root.Val && q.Val > root.Val {
            root = root.Right
        } else {
            return root
        }
    }
    return nil
}

Go 代码:有序数组转平衡 BST

这个题是在反过来利用“有序性”。

func SortedArrayToBST(nums []int) *TreeNode {
    var build func(left, right int) *TreeNode
    build = func(left, right int) *TreeNode {
        if left > right {
            return nil
        }
        mid := left + (right-left)/2
        root := &TreeNode{Val: nums[mid]}
        root.Left = build(left, mid-1)
        root.Right = build(mid+1, right)
        return root
    }
    return build(0, len(nums)-1)
}

BST 最容易出错的地方

这几件事必须分清

  • BST 的有序性是“整棵左子树 / 整棵右子树”的约束,不只是左右孩子比较。
  • 删除两个孩子的节点时,常用后继节点替换,不要替换完忘了继续删后继。
  • BST 平均复杂度是 O(log n),最坏可以退化成链表变成 O(n)。
  • 很多 BST 题的突破口不是树形 DP,而是中序遍历的有序序列。

平衡为什么重要

普通 BST 如果按升序插入:

1 -> 2 -> 3 -> 4 -> 5

整棵树会退化成链表。
所以工程里更常见的是:

也就是说,BST 更像“思想原型”,平衡树才是它的工程化版本。

复杂度

操作平均复杂度最坏复杂度
查找O(log n)O(n)
插入O(log n)O(n)
删除O(log n)O(n)

经典题目

相关主题

  • 二叉树:BST 的结构基础。
  • 平衡二叉树:解决 BST 退化问题。
  • 堆:也是树形结构,但“有序性”完全不是一回事。

返回:数据结构 | 算法学习导航