Skip to content

二叉排序树(BST)

为什么需要二叉排序树

顺序查找太慢(O(n)),折半查找虽快但依赖有序顺序表、插入删除代价高。我们需要一种既能高效查找,又能灵活插入删除的数据结构——二叉排序树(Binary Search Tree, BST)应运而生。

BST 将查找与树形结构结合,在理想情况下可以实现 O(log₂n) 的查找、插入和删除,是构造动态查找表的重要方法,也是理解平衡二叉树(AVL)、红黑树等高级结构的基础。

核心思想

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

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

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

结点定义:

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 一定没有左子树,转化为情况一或情况二)。

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 是动态结构,折半查找依赖静态有序表