STL源码解析之二叉搜索树

什么是二叉搜索树?

二叉搜索树(Binary Search Tree,BST) 是一种特殊的二叉树,它满足以下性质:

  • 若左子树不空,则左子树上所有节点的值 < 根节点的值

  • 若右子树不空,则右子树上所有节点的值 > 根节点的值

  • 左右子树也分别是二叉搜索树

注意:通常不允许有重复值(或规定重复值放左/右均可,但需统一)。

8

/ \

3 10

/ \ \

1 6 14

/ \ /

4 7 13

节点定义

cpp 复制代码
struct TreeNode {
    int key;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int k) : key(k), left(nullptr), right(nullptr) {}
};
  • 每个节点存储一个整型键值 key

  • leftright 分别指向左、右孩子,初始化为 nullptr

二叉搜索树类声明

cpp 复制代码
class BST {
private:
    TreeNode* root;

    // 递归辅助函数声明
    TreeNode* insertRec(TreeNode* node, int key);
    TreeNode* deleteRec(TreeNode* node, int key);
    TreeNode* findMin(TreeNode* node);
    bool searchRec(TreeNode* node, int key);
    void inorderRec(TreeNode* node, std::vector<int>& res);
    void destroyRec(TreeNode* node);

public:
    BST() : root(nullptr) {}
    ~BST() { destroyRec(root); }  // 释放所有节点

    void insert(int key) { root = insertRec(root, key); }
    void remove(int key) { root = deleteRec(root, key); }
    bool search(int key) { return searchRec(root, key); }
    std::vector<int> inorder();
};
  • root 是整棵树的入口。

  • 公开接口封装了对递归私有函数的调用,用户无需关心根节点指针传递。

  • 析构函数负责递归销毁所有节点,防止内存泄漏。

插入(Insert)

cpp 复制代码
TreeNode* BST::insertRec(TreeNode* node, int key) {
    if (node == nullptr)
        return new TreeNode(key);    // 找到空位,创建新节点

    if (key < node->key)
        node->left = insertRec(node->left, key);
    else if (key > node->key)
        node->right = insertRec(node->right, key);
    // 若相等,不插入(也可选择更新值)

    return node;                     // 返回当前子树根节点,维持链接
}
  • 递归向下寻找插入位置。

  • nullptr 时创建节点并返回,由上层 node->leftnode->right 接收。

  • 返回 node 使整条路径上的链接保持不变(若未创建新节点,原样返回)。

  • 时间复杂度:平均 O(log n),退化为链表时 O(n)。

查找(Search)

cpp 复制代码
bool BST::searchRec(TreeNode* node, int key) {
    if (node == nullptr)
        return false;
    if (key == node->key)
        return true;
    else if (key < node->key)
        return searchRec(node->left, key);
    else
        return searchRec(node->right, key);
}

删除(Delete)

cpp 复制代码
TreeNode* BST::deleteRec(TreeNode* node, int key) {
    if (node == nullptr)
        return nullptr;

    // 1. 查找待删除节点
    if (key < node->key) {
        node->left = deleteRec(node->left, key);
    } else if (key > node->key) {
        node->right = deleteRec(node->right, key);
    } else {
        // 2. 已找到,执行删除
        // 情况1/2:只有一个孩子或无孩子
        if (node->left == nullptr) {
            TreeNode* temp = node->right;
            delete node;
            return temp;          // 用右孩子替换
        } else if (node->right == nullptr) {
            TreeNode* temp = node->left;
            delete node;
            return temp;          // 用左孩子替换
        }

        // 情况3:有两个孩子 -> 用右子树最小节点(后继)替换
        TreeNode* successor = findMin(node->right);
        node->key = successor->key;                     // 复制后继的值
        node->right = deleteRec(node->right, successor->key); // 删除后继
    }
    return node;
}

TreeNode* BST::findMin(TreeNode* node) {
    while (node->left != nullptr)
        node = node->left;
    return node;
}
  • 查找路径 :通过递归在树中寻找 key,到达要删除的节点。

  • 单子/无子

    • node->left == nullptr,无论右孩子是否存在,直接返回右孩子指针,并 delete node。该返回被上层链接接收,相当于用右子树取代当前节点。

    • 若只有左孩子同理,返回左孩子。

    • 这些情况覆盖了叶子节点(左右都空,返回 nullptr)。

  • 双子节点

    • 调用 findMin 找到右子树中最小节点(后继),它一定没有左孩子。

    • 把后继的 key 拷贝到当前节点,相当于"替换"当前节点的数据。

    • 然后递归从右子树中删除那个后继节点(此时后继节点的删除必为情况1或2,简单)。

  • 返回 node 保证了路径上指针链接的正确更新。

注意:这里显式 delete 释放了被删节点的内存。在寻找后继并拷贝值后,实际上删除的是原后继节点(那个节点被 deleteRec 中的 delete 释放)。

核心操作与复杂度

操作 平均时间复杂度 最坏时间复杂度(退化成链表)
查找 O(log n) O(n)
插入 O(log n) O(n)
删除 O(log n) O(n)

为什么最坏是 O(n)?

  1. 当插入的数据本身有序时,树会退化成一根斜线(完全偏左或偏右),此时与链表无异。

例如依次插入 1, 2, 3, 4, 5:

1

\

2

\

3

\

4

\

5

2)经过某些插入删除操作,二叉树可能会失去平衡

中序遍历(返回有序结果)

cpp 复制代码
void BST::inorderRec(TreeNode* node, std::vector<int>& res) {
    if (node == nullptr) return;
    inorderRec(node->left, res);
    res.push_back(node->key);
    inorderRec(node->right, res);
}

std::vector<int> BST::inorder() {
    std::vector<int> result;
    inorderRec(root, result);
    return result;
}

内存释放(析构辅助函数)

cpp 复制代码
void BST::destroyRec(TreeNode* node) {
    if (node == nullptr) return;
    destroyRec(node->left);
    destroyRec(node->right);
    delete node;
}

如何避免树会退化成一根斜线→ 平衡二叉搜索树

  • AVL树:每个节点维护高度差,插入/删除后通过旋转恢复平衡。

  • 红黑树 :通过颜色和旋转维持近似平衡,C++ STL 的 std::mapstd::set 均基于红黑树。

  • 两者均保证查找、插入、删除的 最坏时间复杂度为 O(log n)