二叉排序树(BST)
构造一棵二叉排序树的目的并不是排序,而是提高查找、插入和删除关键字的速度。
这是教材开篇的第一句,值得原样记住。它回答了「既然中序遍历能得到有序序列,为什么不直接排好序放数组里」——因为数组插一个元素要移动
BST 是本章后面三节的共同祖先:平衡二叉树 是「加了平衡因子约束的 BST」,红黑树 是「加了红黑性质的 BST」,B 树 是「多路化的 BST」。它们的插入删除全都以 BST 的插入删除为第一步,再补一段调整。
机制
定义
二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:
- 若左子树非空,则左子树上所有结点的值均小于根结点的值
- 若右子树非空,则右子树上所有结点的值均大于根结点的值
- 左、右子树也分别是一棵二叉排序树
「所有结点」这三个字是定义的全部重量。 只保证「左孩子 < 根 < 右孩子」的树不是 BST——这是最经典的判断题陷阱。
由定义得 左子树结点值 < 根结点值 < 右子树结点值,因此对二叉排序树进行中序遍历,可以得到一个递增的有序序列。
边界辨析:
中序遍历有序是 BST 的充要条件,可以拿来当判据用: 给一棵二叉树,中序走一遍,序列递增则是 BST,否则不是。 但反过来不能推形态——中序序列相同的二叉树有很多棵(5.3.1), 所以「已知中序序列」不能唯一确定一棵 BST,还要知道插入顺序。
查找
从根结点开始,沿某个分支逐层向下比较。非递归算法:
BSTNode *BST_Search(BiTree T, ElemType key){
while(T != NULL && key != T->data){ //若树空或等于根结点值,则结束循环
if(key < T->data) T = T->lchild; //小于,则在左子树上查找
else T = T->rchild; //大于,则在右子树上查找
}
return T;
}非递归版本的空间复杂度是
插入
BST 作为一种动态树表,其特点是树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字值等于给定值的结点时再进行插入的。
- 若原树为空,则直接插入
- 若关键字
小于根结点值,则插入到左子树;大于则插入到右子树 - 新插入的结点一定是一个叶结点,且是查找失败时查找路径上访问的最后一个结点的左孩子或右孩子
int BST_Insert(BiTree &T, KeyType k){
if(T == NULL){ //原树为空,新插入的记录为根结点
T = (BiTree)malloc(sizeof(BSTNode));
T->data = k;
T->lchild = T->rchild = NULL;
return 1; //返回 1,插入成功
}
else if(k == T->data) //树中存在相同关键字的结点,插入失败
return 0;
else if(k < T->data) //插入 T 的左子树
return BST_Insert(T->lchild, k);
else //插入 T 的右子树
return BST_Insert(T->rchild, k);
}BiTree &T 是引用参数——因为要在树为空时修改调用者的指针本身。这是 408 算法设计题里最容易写漏的一个符号。
相同关键字插入失败返回 0,不是插入到某一侧。教材构造例子用的序列是
构造
从一棵空树出发,依次输入元素,将它们插入 BST 中的合适位置。
void Creat_BST(BiTree &T, KeyType str[], int n){
T = NULL; //初始时 T 为空树
int i = 0;
while(i < n){ //依次将每个关键字插入二叉排序树
BST_Insert(T, str[i]);
i++;
}
}同一组关键字,插入顺序不同会生成不同的 BST。 这是 BST 与折半查找判定树最本质的区别,也是它性能不稳定的根源(见下面「查找效率」)。
删除:三种情况
删除时不能把以该结点为根的子树上的结点都删除,必须先把被删除结点从链表上摘下,重新链接,同时确保二叉排序树的性质不会丢失。按 3 种情况处理:
| 情况 | 处理 |
|---|---|
| ① 被删结点 | 直接删除,不会破坏 BST 性质 |
| ② | 让 |
| ③ | 令 |
flowchart TD Z{"被删结点 z"} --> L["① 叶结点<br/>直接删"] Z --> O["② 只有一棵子树<br/>子树上移顶替"] Z --> T["③ 左右子树都有"] T --> S["找中序直接后继<br/>(右子树中最小的结点)"] S --> R["用它的值替换 z"] R --> D["转化为删除那个后继<br/>→ 回到 ① 或 ②"] classDef easy fill:#c8e6c9,stroke:#1b5e20 classDef hard fill:#ffcdd2,stroke:#b71c1c classDef mid fill:#e3f2fd,stroke:#1565c0 class L,O easy class T,S hard class R,D mid
情况 ③ 为什么一定能化归:直接后继是右子树中最小的结点,它必然没有左孩子,所以至多只有一棵子树——正好是情况 ① 或 ②。
边界辨析:
「直接后继」和「直接前驱」都可以,教材两个都列了。 选后继就是右子树的最左结点, 选前驱就是左子树的最右结点。题目不指定时两个答案都对,但同一道题里要前后一致。 阅卷时若中途换了口径,得到的树会自相矛盾。
查找效率分析
二叉排序树的查找效率,主要取决于树的高度。
- 若左、右子树的高度之差的绝对值不超过 1(即平衡二叉树,下一节),平均查找长度和
成正比 - 最坏情况下,即构造 BST 的输入序列是有序的,则会形成一个只有右孩子的单支树,树的高度为
,此时
最坏情况的
教材图 7.8 用同一组关键字给出了两棵 BST:
与折半查找的四点对比
| 折半查找 | 二叉排序树 | |
|---|---|---|
| 平均时间性能 | ||
| 形态是否唯一 | 判定树唯一 | 不唯一,取决于插入顺序 |
| 维护有序性的代价 | 插入删除要移动元素, | 无须移动结点,只改指针,平均 |
| 适用 | 有序表是静态查找表时,用顺序表 + 折半 | 有序表是动态查找表时,用 BST 作为逻辑结构 |
这张表就是教材 7.3.1 末尾那一整段的压缩,也是 7.2 与 7.3 之间的接缝。
手算模板
构造 BST:逐个插入,每个新元素从根开始按大小走到空位挂上去。重复关键字直接丢弃。
删除结点
- 数
的孩子个数 → 落到 ①/②/③。 - 若为 ③:走
的右子树,一路向左到底,得到直接后继 。 - 把
的值写进 ,然后在原位置删除 ( 无左孩子,必为 ① 或 ②)。
算
边界
| 说法 | 判断 | 说明 |
|---|---|---|
| 「只要左孩子 < 根 < 右孩子就是 BST」 | ❌ | 要求整棵左子树都小于根,不只是左孩子 |
| 「BST 的中序遍历是递增序列」 | ✅ | 且是充要判据 |
| 「已知中序序列可唯一确定一棵 BST」 | ❌ | 中序序列就是排好序的那一列,任何 BST 都给出同一个中序序列 |
| 「BST 的目的是排序」 | ❌ | 是提高查找、插入、删除的速度(教材首句) |
| 「插入相同关键字会插到右子树」 | ❌ | 插入失败,返回 0 |
| 「新插入的结点可能是内部结点」 | ❌ | 一定是叶结点 |
| 「删除有两个孩子的结点必须用后继」 | ❌ | 后继或前驱都可以 |
| 「删除再插入同一个结点,树不变」 | ❌ | 教材 2013 命题追踪的思考题:一般会变。删除时用后继顶替改变了形态,再插入只会挂到叶子上 |
| 「BST 查找的时间复杂度是 | ⚠️ | 平均是;最坏退化成单支树,为 |
| 「BST 是平衡的」 | ❌ | 不保证。要平衡得靠 AVL 或 红黑树 |
口径差异:
算法竞赛里用 BST 一般直接上
std::map/set(底层是红黑树),从不手写朴素 BST, 因为朴素 BST 会被有序数据卡成。所以「退化成单支树」在算竞里是要避开的坑, 在 408 里却是必考的结论——题目专门爱问「关键字有序输入时 BST 的 是多少」,答案 。 算竞的直觉是「别用它」,考试的要求是「算清楚它有多差」。
对照速查
| 操作 | 平均 | 最坏 |
|---|---|---|
| 查找 | ||
| 插入 | ||
| 删除 | ||
| 最坏 ASL | — |
| 删除情况 | 判据 | 动作 |
|---|---|---|
| ① | 无孩子 | 直接删 |
| ② | 一个孩子 | 孩子顶替 |
| ③ | 两个孩子 | 后继(或前驱)顶替,转 ① 或 ② |
考点
- 定义里的「所有结点」,以及中序遍历有序这个充要判据。
- 构造过程(2020 命题追踪),重复关键字被丢弃。
- 删除的三种情况,尤其 ③ 的化归理由。
- 删除后再插入,树是否与原来相同(2013 命题追踪的思考题)。
- 最坏退化为单支树,
。 - 与折半查找的四点对比,尤其「判定树唯一 vs BST 不唯一」。
BiTree &T引用参数——算法设计题的高频扣分点。
链接
- 🏠 返回总览:数据结构第 7 章:查找总览
- ⬅️ 上一节:7.2.2 折半查找
- ➡️ 下一节:7.3.2 平衡二叉树
- 🔗 加上平衡约束:7.3.2 平衡二叉树
- 🔗 放宽平衡约束:7.3.3 红黑树
- 🔗 多路化:7.4.1 B 树
- 🔗 中序遍历:5.3.1 二叉树的遍历
- 📖 名词库:第 7 章名词库