二叉搜索树 BST:查找、插入与删除

在普通二叉树里,左右孩子的位置本身并不一定携带数值上的顺序信息。也就是说,虽然我们能遍历整棵树,但如果想高效查找某个值,普通二叉树未必比线性扫描好多少。二叉搜索树之所以重要,就是因为它在“树的层级结构”和“关键字有序性”之间建立了一条非常漂亮的联系。

它的核心思想非常简单:

对于每个节点,左子树中的所有关键字都小于该节点,右子树中的所有关键字都大于该节点。

正是这条规则,让 BST 同时拥有了两种非常有价值的特性:

  • 它仍然是一棵树,适合分层组织和局部更新;
  • 它又带有全局有序性,使得查找、插入、删除都能沿着比较路径逐步缩小问题。

所以 BST 可以看成是数据结构里第一次非常明确地把“有序”嵌入树结构的尝试。后面的 AVL、红黑树、B 树、B+ 树,本质上都在继续围绕这个方向做优化。

一、什么是二叉搜索树

二叉搜索树,通常简称 BST,它满足以下性质:

  • 若左子树非空,则左子树上所有节点值都小于根节点值;
  • 若右子树非空,则右子树上所有节点值都大于根节点值;
  • 左右子树本身也分别是二叉搜索树。

这里有两个关键词非常重要。

1. 有序性是局部递归定义的

不是说整棵树“看起来差不多有序”就行,而是每个节点都必须满足左右子树的大小关系。

2. 子树本身仍是 BST

这使得查找、插入、删除都可以递归地进行。

二、为什么 BST 查找会更高效

因为每次比较都能决定下一步只往一个方向走。

例如查找值 x 时:

  • 如果 x == root.val,直接找到;
  • 如果 x < root.val,只需要去左子树找;
  • 如果 x > root.val,只需要去右子树找。

这意味着你不必像普通二叉树那样把左右子树都遍历一遍,而是可以利用有序性不断缩小搜索范围。

如果树比较平衡,那么每往下一层,问题规模都在显著收缩,于是查找复杂度就能接近 O(log n)

三、为什么中序遍历 BST 会得到有序序列

这是 BST 最经典、最值得记住的性质之一。

中序遍历顺序是:

1
左 -> 根 -> 右

而 BST 又保证:

  • 左子树全部小于根;
  • 右子树全部大于根。

于是中序遍历结果自然就是从小到大的有序序列。

这一点很重要,因为它说明 BST 不只是“查找方便”,它还把树结构和排序结果联系了起来。

四、BST 的查找过程怎么理解

BST 查找的本质其实很像“在树上做比较驱动的路径选择”。

和数组中的二分查找不同,BST 不是靠下标取中点,而是靠当前节点作为比较枢纽:

  • 小了就往左;
  • 大了就往右;
  • 相等就结束。

所以查找路径通常只会经过从根到某个节点的一条链,而不会遍历整个结构。

这也是 BST 相比普通二叉树最根本的优势。

五、BST 的插入为什么也很自然

插入一个新值时,逻辑和查找几乎一样:

  • 从根开始比较;
  • 小于当前节点就往左走;
  • 大于当前节点就往右走;
  • 一直走到空位置,把新节点挂上去。

之所以可行,是因为:

  • 查找路径本身就确定了新值应该落在哪个局部范围;
  • 只要放在最终空位上,就不会破坏 BST 的有序性质。

所以 BST 插入最大的优点是:

  • 不需要像顺序表那样整体搬移元素;
  • 只需要沿着一条路径往下,最后修改一个局部链接。

六、BST 删除为什么更复杂

删除是 BST 里最值得重点理解的一步,因为它不像查找和插入那样只靠一路往下就能自然结束。删除某个节点时,你不仅要把它去掉,还必须保证整棵树删完后仍然满足 BST 性质。

通常分三种情况。

1. 删除叶子节点

这是最简单的情况,直接删除即可,不会影响其他结构。

2. 删除只有一个孩子的节点

让它的孩子直接顶替它的位置即可。

3. 删除有两个孩子的节点

这是最关键的一种情况。通常做法是:

  • 找到该节点的中序前驱,或者中序后继;
  • 用它的值替换当前节点;
  • 再去删除那个前驱或后继节点。

为什么这样做可行?因为中序前驱是左子树中最大的值,中序后继是右子树中最小的值,它们都是最适合接替当前节点的候选者,不会破坏整体有序性。

七、BST 的性能为什么和树高密切相关

这一点非常重要。

BST 的查找、插入、删除,几乎都只沿着一条从根到叶的路径进行,所以它们的时间复杂度通常和树高 h 成正比,也就是 O(h)

于是问题就转化成:

  • 如果树高较小,操作就快;
  • 如果树高很大,操作就慢。

在理想情况下,BST 比较平衡,树高接近 log n,操作复杂度也就接近 O(log n)

八、BST 为什么会退化

这是普通 BST 最大的问题。

如果插入序列本身非常不均匀,例如按递增顺序依次插入:

1
1, 2, 3, 4, 5

那么树就会退化成一条链:

  • 每个节点都只有右孩子;
  • 树高接近 n
  • 查找、插入、删除都会退化成 O(n)

这说明 BST 虽然有序,但它并没有自动保证“形状平衡”。

也正因为这个缺陷,后面才会有 AVL 树、红黑树等平衡搜索树出现。

九、BST 和二分查找是什么关系

它们都利用了“有序性”来缩小搜索范围,但方式不同。

1. 二分查找
  • 基于有序数组;
  • 随机访问快;
  • 查找高效;
  • 但插入删除代价高。
2. BST
  • 基于有序树结构;
  • 查找同样能缩小范围;
  • 插入删除更灵活;
  • 但如果不平衡,性能可能退化。

可以说,BST 是一种在“有序性”和“动态更新”之间做平衡的结构尝试。

十、BST 在真实开发里有什么启发

即便你不直接手写一个 BST,它背后的思想仍然非常重要:

  • 有序结构能支持高效查找;
  • 局部调整比整体搬移更灵活;
  • 树高决定性能上界;
  • 结构有序不代表结构平衡。

这些认识会直接帮助你理解后面更工程化的结构,例如:

  • TreeMap
  • 红黑树
  • B+ 树索引
  • 有序集合和范围查询结构

十一、学习 BST 时最容易踩的坑

1. 只记定义,不理解递归约束

BST 的性质不是只对根节点成立,而是对每棵子树都必须成立。

2. 删除操作三种情况没分清

特别是有两个孩子的节点,很多人一开始最容易乱。

3. 误以为 BST 一定是 O(log n)

其实只有在树比较平衡时才接近这个复杂度。

4. 把 BST 和堆混淆

BST 强调左右子树全局有序,堆只强调局部堆序,二者不是一回事。

5. 忽略中序遍历的重要性

中序遍历输出有序序列,是 BST 最值得牢牢记住的特性之一。

总结

BST 的重要性,不只是因为它是一棵会比较大小的树,而是因为它第一次系统地把“有序查找”和“树形组织”结合在了一起。真正值得先建立起来的,是这些核心认识:

  • BST 通过左小右大的递归规则,把顺序性嵌入到树结构中;
  • 查找、插入、删除都能沿着比较路径逐步缩小问题;
  • 删除之所以复杂,是因为删完后仍要维持整棵树的搜索性质;
  • BST 的核心性能取决于树高,而树高又取决于结构是否平衡;
  • 中序遍历会得到有序序列,这是 BST 与遍历最重要的联系;
  • 真正学会 BST,不是只会写几个操作,而是理解“有序”与“平衡”其实是两回事。

把这一篇理解透之后,后面再去学 AVL 树和红黑树时,你会非常自然地看到:它们并不是推翻 BST,而是在保留 BST 搜索性质的基础上,进一步解决“退化”和“树高失控”的问题。

参考资源