跳转至

二叉搜索树

二叉搜索树(Binary Search Tree,BST),也叫二叉排序树,是一种特殊的二叉树,它在 查找、插入、删除 操作上都能保持较高的效率,是众多高级数据结构(AVL、红黑树)的基础

二叉搜索树满足以下 递归性质

对于任意节点:

  • 左子树 中所有节点的值都 小于 该节点的值
  • 右子树 中所有节点的值都 大于 该节点的值
  • 左右子树本身也都是二叉搜索树
graph TD
    8((8)) --> 3((3))
    8 --> 10((10))
    3 --> 1((1))
    3 --> 6((6))
    6 --> 4((4))
    6 --> 7((7))
    10 --> 14((14))
    14 --> 13((13))

由定义可直接推出一个重要性质:中序遍历一棵 BST 得到的是升序序列。上图的中序遍历结果是 1 3 4 6 7 8 10 13 14

1 查找(Search)

利用"左小右大"的性质,每次比较后可以 排除一半 的节点,类似二分查找:

// 递归版
TreeNode* search(TreeNode* root, int key) {
    if (!root || root->val == key) return root;
    return key < root->val ? search(root->left, key)
                           : search(root->right, key);
}

// 迭代版(推荐,省递归栈空间)
TreeNode* search(TreeNode* root, int key) {
    while (root && root->val != key) {
        root = key < root->val ? root->left : root->right;
    }
    return root;   // 找到返回节点指针,找不到返回 nullptr
}

2 插入(Insert)

插入的核心思路和查找一样:先按大小关系走到空位,再把新节点挂上去

TreeNode* insert(TreeNode* root, int val) {
    if (!root) return new TreeNode(val);   // 到达空位,新建节点

    if (val < root->val)
        root->left = insert(root->left, val);
    else if (val > root->val)
        root->right = insert(root->right, val);
    // val == root->val 时,按需求决定:这里选择不插入重复值

    return root;
}

重复值处理

不同实现可以约定不同策略:

  • 不允许重复:val == root->val 时直接返回(如上)
  • 允许重复:可以约定"等于的放左子树"或"放右子树",或者给节点加 count 计数

3 删除(Delete)—— 最复杂的操作

删除节点需要分 三种情况 处理,同时保持 BST 性质:

// 找以 root 为根的子树中的最小值节点(最左边的节点)
TreeNode* findMin(TreeNode* root) {
    while (root && root->left) root = root->left;
    return root;
}

TreeNode* remove(TreeNode* root, int key) {
    if (!root) return nullptr;

    // 先递归找到要删除的节点
    if (key < root->val)
        root->left = remove(root->left, key);
    else if (key > root->val)
        root->right = remove(root->right, key);
    else {
        // 情况 1:叶子节点 —— 直接删除
        if (!root->left && !root->right) {
            delete root;
            return nullptr;
        }

        // 情况 2:只有一个子节点 —— 用子节点顶替自己
        if (!root->left) {
            TreeNode* tmp = root->right;
            delete root;
            return tmp;
        }
        if (!root->right) {
            TreeNode* tmp = root->left;
            delete root;
            return tmp;
        }

        // 情况 3:有两个子节点 —— 用"后继"顶替
        // 后继 = 右子树中最小的节点(保证大于左子树全部、小于右子树其余节点)
        TreeNode* succ = findMin(root->right);
        root->val = succ->val;                    // 拷贝后继的值
        root->right = remove(root->right, succ->val);  // 递归删除后继节点
    }
    return root;
}
  • 情况 1:叶子节点,直接 delete 并返回 nullptr
  • 情况 2:只有一个孩子,用孩子"接管"位置
  • 情况 3:有两个孩子,不能直接删,改用 后继(右子树最小值)或 前驱(左子树最大值)的值覆盖自己,再递归删除那个后继/前驱节点(它必然落入情况 1 或 2)

为什么情况 3 要用后继/前驱替换?

因为删除有两个孩子的节点后,需要一个新的值填补空缺,且必须满足"大于左子树所有值、小于右子树所有值"。右子树的最小值(后继)恰好满足:它大于左子树全部节点(因为在右子树里),又小于右子树其余节点(因为它是右子树最小的)。左子树的最大值(前驱)同理

4 遍历

中序遍历 是 BST 最核心的遍历方式,输出天然有序:

1
2
3
4
5
6
void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);          // 左
    std::cout << root->val << ' '; // 根
    inorder(root->right);         // 右
}
  • 前序遍历(根左右):可用于 序列化/重建 BST
  • 后序遍历(左右根):常用于"先处理子节点"的场景(如释放内存)

5 复杂度分析

操作 平均复杂度 最坏复杂度
查找 \(O(\log N)\) \(O(N)\)
插入 \(O(\log N)\) \(O(N)\)
删除 \(O(\log N)\) \(O(N)\)
中序遍历 \(O(N)\) \(O(N)\)

其中 \(O(\log N)\) 成立的前提是树 接近平衡。如果插入的序列本身有序,BST 会退化成一条链

此时查找退化为 \(O(N)\),失去了二分查找的优势。为解决退化问题,人们发明了 平衡二叉搜索树

结构 平衡策略
AVL 树 严格平衡(左右子树高度差 ≤ 1),旋转调整
红黑树 近似平衡(最长路径 ≤ 2× 最短路径),std::map/std::set 底层
Treap 随机优先级 + 堆性质
Splay 树 访问后旋转到根

和 STL 的联系

C++ 标准库的 std::setstd::map 底层就是**红黑树**(一种自平衡的 BST),因此它们才有 \(O(\log N)\) 的查找/插入/删除保证和 有序遍历 能力。

6 其他常用操作

6.1 找最小值 / 最大值

1
2
3
4
5
6
7
8
TreeNode* findMin(TreeNode* root) {
    while (root && root->left) root = root->left;   // 一直往左
    return root;
}
TreeNode* findMax(TreeNode* root) {
    while (root && root->right) root = root->right; // 一直往右
    return root;
}

6.2 验证一棵树是否为 BST

利用"中序遍历必须严格递增"或"每个节点值在合法区间内":

1
2
3
4
5
6
7
bool isValid(TreeNode* root, long long low, long long high) {
    if (!root) return true;
    if (root->val <= low || root->val >= high) return false;
    return isValid(root->left, low, root->val)
        && isValid(root->right, root->val, high);
}
// 调用:isValid(root, LLONG_MIN, LLONG_MAX)

7 典型应用

  1. 有序集合/字典std::setstd::map(红黑树)
  2. 范围查询:查找某个区间内的所有元素(BST 中序遍历一段即可)
  3. 前驱/后继查询:比某值小的最大值 / 比某值大的最小值
  4. 排名查询:给节点加 size 字段可查"第 k 小"(LeetCode 230)
  5. 数据库索引:B 树/B+ 树是 BST 的磁盘友好版推广

评论区

欢迎在评论区指出文档错误,为文档提供宝贵意见,或写下你的疑问