Skip to content

二叉排序树(BST)

兼得查找与插入的优势

数组查找快(O(1) 随机访问)但插入慢(O(n) 移动元素),链表插入快(O(1))但查找慢(O(n))。BST 试图兼得两者的优点:平均情况下查找和插入都是 O(log n)——前提是树不会退化成链表。

BST 是 408 考研查找章节的核心内容,也是理解 AVL 树、红黑树、B 树等高级结构的基础。

核心思想

二叉排序树是一棵二叉树,它满足以下性质(递归定义):

  • 左子树上所有结点的值 < 根结点的值
  • 右子树上所有结点的值 > 根结点的值
  • 左、右子树本身也分别是二叉排序树

由此可得一个重要推论:对 BST 进行中序遍历,可以得到一个递增的有序序列。

⚠️ 易错:BST 的中序遍历一定是递增序列。这意味着:给定一棵 BST,对它做中序遍历可以得到排序结果——这是 408 判断题的常考点。

结点定义:

c
typedef struct BSTNode {
    int key;
    struct BSTNode *lchild, *rchild;
} BSTNode, *BSTree;

交互可视化

通过下方的交互动画,你可以逐步观察二叉排序树的执行过程:

加载可视化中...

操作详解

查找操作

从根结点出发,将目标值 key 与当前结点比较:

  1. 若 key == 当前结点值,查找成功
  2. 若 key < 当前结点值,在左子树中继续查找
  3. 若 key > 当前结点值,在右子树中继续查找
  4. 若走到空结点,查找失败
c
BSTNode *BST_Search(BSTree T, int key) {
    while (T != NULL && key != T->key) {
        if (key < T->key)
            T = T->lchild;
        else
            T = T->rchild;
    }
    return T;
}

查找过程实质上是走了一条从根到目标结点的路径,比较次数 = 路径上的结点数 = 结点所在层次

插入操作

BST 的插入基于查找:先查找 key 是否已存在,若不存在,则在查找失败的位置插入新结点。

关键步骤:

  1. 若树为空,创建新结点作为根
  2. 若 key < 当前结点值,递归插入左子树
  3. 若 key > 当前结点值,递归插入右子树
  4. 若 key == 当前结点值,不插入(BST 中不允许重复关键字)
c
int BST_Insert(BSTree *T, int key) {
    if (*T == NULL) {
        *T = (BSTree)malloc(sizeof(BSTNode));
        (*T)->key = key;
        (*T)->lchild = (*T)->rchild = NULL;
        return 1;  // 插入成功
    }
    if (key == (*T)->key)
        return 0;  // 已存在,插入失败
    else if (key < (*T)->key)
        return BST_Insert(&((*T)->lchild), key);
    else
        return BST_Insert(&((*T)->rchild), key);
}

插入的新结点一定是叶子结点,这是 BST 插入的重要特征。

不同的插入顺序会构造出不同形态的 BST。例如,序列 {50, 30, 70, 20, 40}{20, 30, 40, 50, 70} 包含相同元素,但前者构造出较平衡的树,后者退化为单支树(类似链表)。

删除操作

BST 的删除较复杂,需要分三种情况讨论(设待删除结点为 z):

情况一:z 是叶子结点 直接删除,将其父结点对应指针置空。

情况二:z 只有一棵子树 让 z 的子树替代 z 的位置,即用 z 的子结点顶替 z。

情况三:z 有两棵子树 ⭐ 找到 z 的中序后继(即 z 的右子树中最左下的结点 s),用 s 的值替换 z 的值,然后转而删除 s(s 一定没有左子树,转化为情况一或情况二)。

⚠️ 易错:BST 删除度为 2 的结点时,用中序前驱(左子树最大值)或中序后继(右子树最小值)替换均可。但两种方式得到的 BST 不同。408 真题一般会指定用哪种,如果没指定则默认用中序前驱。

c
void BST_Delete(BSTree *T, int key) {
    if (*T == NULL) return;
    if (key < (*T)->key) {
        BST_Delete(&((*T)->lchild), key);
    } else if (key > (*T)->key) {
        BST_Delete(&((*T)->rchild), key);
    } else {  // 找到待删除结点
        if ((*T)->lchild == NULL) {
            // 情况一、二:无左子树(含叶子结点)
            BSTNode *temp = *T;
            *T = (*T)->rchild;
            free(temp);
        } else if ((*T)->rchild == NULL) {
            // 情况二:无右子树
            BSTNode *temp = *T;
            *T = (*T)->lchild;
            free(temp);
        } else {
            // 情况三:左右子树都存在,找中序后继
            BSTNode *s = (*T)->rchild;
            while (s->lchild != NULL)
                s = s->lchild;
            (*T)->key = s->key;
            BST_Delete(&((*T)->rchild), s->key);
        }
    }
}

复杂度分析

操作最好时间复杂度最坏时间复杂度说明
查找O(log₂n)O(n)取决于树的高度
插入O(log₂n)O(n)先查找再插入,查找决定时间
删除O(log₂n)O(n)先查找再删除,查找决定时间

查找长度分析(ASL)

  • 最好情况:BST 接近完全二叉树,树高 h ≈ log₂n,ASL ≈ O(log₂n)
  • 最坏情况:BST 退化为单支树(输入序列有序时),树高 h = n,ASL ≈ (n+1)/2 = O(n),与顺序查找相同
树形ASL等价于
平衡 BSTO(log₂n)折半查找判定树
单支树O(n)顺序查找

空间复杂度:O(1),查找、插入、删除操作只需常数级辅助空间(递归实现需 O(h) 栈空间)。

考研高频考点

  • ⭐ BST 的性质:中序遍历得到递增序列(选择题/判断题高频)
  • ⭐ 根据插入序列构造 BST(画图题必考)
  • ⭐ 删除操作三种情况的处理方式,特别是有两棵子树时用中序后继替换(大题高频)
  • ⭐ ASL 的计算:给定 BST 求查找成功/失败的 ASL(计算题高频)
  • ⭐ 不同插入顺序 → 不同树形 → 不同查找效率(概念题)
  • 相同关键字集合,不同插入顺序得到不同 BST,但中序遍历结果相同
  • BST 与折半查找的对比:BST 是动态结构,折半查找依赖静态有序表

相关知识

  • BST 在最坏情况下退化为链表,为此引入了平衡二叉树(AVL)红黑树两种自平衡方案
  • BST 的查找过程与折半查找的判定树完全对应——折半查找的判定树就是一棵平衡 BST
  • BST 是 B 树的特例(2 阶 B 树等价于 BST),理解 BST 的操作是学习 B 树的基础

真题练习

相关真题(10题)

2026Q41综合题13分

算法设计:BST 中查找与 K 差的绝对值最小的所有结点

2024Q3选择题2分

二叉树中序遍历:v 有两个孩子且中序序列为 ...p,v,q...,判断 p 和 q 的孩子情况

2024Q7选择题2分

BST 性质:子树 T 中任意结点 X 与 K1、K2、K3 的大小关系

2022Q41综合题8分

算法设计:顺序存储的二叉树判断是否为二叉搜索树(中序遍历递增性)

2020Q5选择题2分

BST 构建:判断哪个输入序列能生成给定的二叉排序树结构

2018Q6选择题2分

BST 中序性质:中序遍历得到递增序列,判断结点大小关系

2017Q8选择题2分

BST 与折半查找:折半查找判定树的构造与验证

2013Q6选择题2分

BST 删除与插入:删除再插入同一结点后树结构可能不同

2013Q42综合题9分

查找性能对比:有序表顺序存储与链式存储的平均查找长度计算

2011Q7选择题2分

BST 性质:二叉排序树的结构与查找效率的关系