BST 基础

观前提示:本文章重在对二叉搜索树的基本定义与性质进行讲解,方便后续各种平衡树的讲解顺利进行。

BST 原理

二叉查找树(Binary Search Tree,BST),又称为二叉搜索树、二叉排序树,是一种对查找和排序都有用的特殊二叉树。
二叉查找树的特性:\(左子树<根<右子树\),即二叉查找树的中序遍历是一个递增序列。

给你二叉一颗树,树上的节点都有权值,权值总满足:\(左儿子权值<当前权值<右儿子权值\)。这就是二叉搜索树。

因为对所有节点有效,所以显然有:
在 BST 上,以任意结点为根结点的一棵子树,仍然是 BST。

比如:

这就是个 BST。而且它的每个子树都是 BST。

BST 操作

插入

因为二叉查找树的性质,插入的思想很好理解:

因为要满足\(左<中<右\),我们看当前节点的权值,如果当前节点大于插入值,说明是插入一个小值,小的应在左边,所以我们访问左儿子继续查。反之,如果当前节点小于插入值,则访问右儿子。如果你发现你当前访问的节点不存在,你就新建这个节点,然后就完成了插入。

因为是二叉树,树的高度不超过 \(\log_{2} N\) 层,因此插入操作平均时间复杂度为 \(O(\log N)\)

查询

你连插入都会了,自然就会查询。只需将新建操作改为查询即可。如果查询到了不存在节点,就说明没有这个数。时间复杂度与插入一样。

删除

先根据查询方式找到要删的数,
可分为3种情况:

  1. 左子树为空:令其右子树子承父业代替其位置。
  2. 右子树为空:令其左子树子承父业代替其位置。
  3. 左子树和右子树均不空:令其直接前驱(或直接后继)代替其位置,然后删除其直接前驱(或直接后继)。
  4. 叶子节点:直接删了完事。

什么是直接前驱?就是比当前权值小的权值中最大的。什么是直接后继?就是比当前权值大的权值中最小的。

如图:

B 是 A 的直接前驱,C 是 A 的直接后继。

怎么删?还是如图:

时间复杂度:就是先查一次删除的数,再查一次直接前驱,(或直接后继),然后再根据拿去替换前驱找前驱的前驱……直到找到树的底部,平均 \(O(\log N)\)

::::info[如何简单删除]
这个删除也太麻烦了,居然要写递归,我们可以换个思路:删除时,不真的删除,而是用标记来表示。我们常规操作时就假装它还活着就行,只需要查询时的判断中多判断一下存不存在就好了,正确性也可以保证。

Q:你这样不会浪费空间吗?

A:会,但不会超。这种标记删除的特点就是:假删,删除不会减少空间占用。但对于一道题,它的查询操作数一般在 \(10^6\)\(10^7\) 之间(一般是 \(10^6\)),就算全是插入,也不会爆空间。

Q:多了许多“不存在的节点”,不会 TLE 吗?

A:确实可能降低效率,但就像空间一样,因为节点数不超过操作数 \(M\),所以树的高度不会超过 \(\log_{2} M\),查询等操作的时间就基本上是 \(O(\log M)\)这要也能超就重开吧

而且,这个操作实用性还挺广,即使是 AVL,红黑树之类的也可以这么搞(除非你追求效率),理念很简单:就假装所有删除的节点还在,一般操作把这些点都算上,只要查询特判就行。

那么他什么情况下不适应呢?比如有变态开高了时间限制,提高了操作数,就可能 MLE。目前我们应该相信 CCF 是不会这么变态的。
::::

创建

就是从空树开始,一个个插入。

需要注意的是,因为各种各样的原因空间和后续操作的影响,BST 基本上是动态开点。我们可以用一个随便哪个数据结构保存可以使用的节点编号(就是树中不存在的节点编号),新建时把随便一个编号弹出并初始化节点,删除就把那个节点编号丢进数据结构里,因为节点编号可以乱赋跟 BST 性质没有关系。

总结

BST 是个好东西。

这个时候就有神犇要问了:这不是很容易逝吗,如果建树时发现建出来或删除后是条链怎么办?

如果是链,就有整整节点数的层数,时间原地起飞,这就是为什么我写了这么多理论而没贴代码。正常情况下,没人会写这么朴素的 BST 去做题的(太好卡了)。

在接下来的四组讲解中,主要内容就是 BST 算法。什么是 BST 算法?就是用各种方法,使我们建出来的 BST 尽量维持在 \(\log_{2} N\) 层,让对应的 BST 更平衡,也就是我们常说的平衡树了。

posted @ 2026-03-17 14:56  绪风ﺴﻬৡ  阅读(3)  评论(0)    收藏  举报
当前时间: