BST

  • 二叉搜索树(Binary Search Tree,简称 BST),也常被称为二叉查找树或二叉排序树。它是二叉树中最常用的一种变体,专门为快速查找、插入和删除数据而设计。


    1. BST 的核心性质

    一棵 BST 必须满足以下三个性质:

    1. 左子树上所有结点的值均小于它的根结点的值。

    2. 右子树上所有结点的值均大于它的根结点的值。

    3. 左、右子树也分别为二叉搜索树。

    4. (通常情况下)树中不包含重复的节点。

    重要结论: 对一棵 BST 进行中序遍历(Left -> Root -> Right),得到的结果一定是一个升序排列的序列。这是检验一棵树是否为 BST 的最直观方法。


    2. 核心操作详解

    A. 查找 (Search)

    从根节点开始:

    • 如果目标值等于当前节点值,查找成功。

    • 如果目标值小于当前节点值,则进入左子树继续查找。

    • 如果目标值大于当前节点值,则进入右子树继续查找。

    • 如果走到空节点,说明目标值不存在。

    B. 插入 (Insertion)

    插入操作总是从根节点开始,逻辑与查找类似。新节点最终会被插入到某个叶子节点的位置,从而保持 BST 的性质。

    C. 删除 (Deletion) —— 最复杂的操作

    删除节点需要分三种情况讨论,以确保删除后树依然满足 BST 性质:

    1. 被删节点是叶子节点: 直接删除,将其父节点指向它的指针置为空。

    2. 被删节点只有一个孩子: 将该节点的父节点直接指向它的那个孩子(类似链表删除)。

    3. 被删节点有两个孩子: * 找到该节点的中序后继(右子树中最小的节点)或中序前驱(左子树中最大的节点)。

      • 用后继(或前驱)节点的值替换掉要删除节点的值。

      • 最后删除那个多余的后继(或前驱)节点。


    3. 性能分析

    BST 的效率极大地依赖于树的高度 (h)

    操作 平均时间复杂度 最坏时间复杂度 (退化为链表)
    查找 O(\log n) O(n)
    插入 O(\log n) O(n)
    删除 O(\log n) O(n)
    • 理想状态: 树是平衡的(接近满二叉树),高度 h = \log n

    • 最坏状态: 插入的数据是有序的(如 1, 2, 3, 4, 5),树会退化成一条斜树(链表),此时查找效率降为 O(n)


    4. 代码实现

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    // 定义节点结构
    struct TreeNode {
        int val;
        TreeNode *left;
        TreeNode *right;
        
        TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    };
    
    class BST {
    public:
        BST() : root(nullptr) {}
    
        // 插入接口
        void insert(int val) {
            root = insert(root, val);
        }
    
        // 查找接口
        bool search(int val) {
            return search(root, val) != nullptr;
        }
    
        // 删除接口
        void remove(int val) {
            root = remove(root, val);
        }
    
        // 中序遍历(打印排序结果)
        void inorder() {
            inorder(root);
            cout << endl;
        }
    
    private:
        TreeNode* root;
    
        // 递归插入
        TreeNode* insert(TreeNode* node, int val) {
            if (node == nullptr) return new TreeNode(val);
            
            if (val < node->val)
                node->left = insert(node->left, val);
            else if (val > node->val)
                node->right = insert(node->right, val);
                
            return node;
        }
    
        // 递归查找
        TreeNode* search(TreeNode* node, int val) {
            if (node == nullptr || node->val == val) return node;
            
            if (val < node->val) return search(node->left, val);
            return search(node->right, val);
        }
    
        // 查找最小值节点(删除操作辅助函数)
        TreeNode* findMin(TreeNode* node) {
            while (node && node->left) node = node->left;
            return node;
        }
    
        // 递归删除(最核心部分)
        TreeNode* remove(TreeNode* node, int val) {
            if (node == nullptr) return nullptr;
    
            if (val < node->val) {
                node->left = remove(node->left, val);
            } else if (val > node->val) {
                node->right = remove(node->right, val);
            } else {
                // 找到目标节点了!
                
                // 情况 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* temp = findMin(node->right);
                // 替换值
                node->val = temp->val;
                // 删除掉右子树中那个多余的后继节点
                node->right = remove(node->right, temp->val);
            }
            return node;
        }
    
        // 递归中序遍历
        void inorder(TreeNode* node) {
            if (node == nullptr) return;
            inorder(node->left);
            cout << node->val << " ";
            inorder(node->right);
        }
    };
    
    // 测试代码
    int main() {
        BST tree;
        
        // 插入测试
        int nums[] = {50, 30, 20, 40, 70, 60, 80};
        for (int x : nums) tree.insert(x);
    
        cout << "中序遍历结果(应为升序): ";
        tree.inorder();
    
        // 查找测试
        cout << "查找 40: " << (tree.search(40) ? "存在" : "不存在") << endl;
        cout << "查找 90: " << (tree.search(90) ? "存在" : "不存在") << endl;
    
        // 删除测试
        cout << "删除 20 (叶子节点)..." << endl;
        tree.remove(20);
        tree.inorder();
    
        cout << "删除 30 (有一个孩子)..." << endl;
        tree.remove(30);
        tree.inorder();
    
        cout << "删除 50 (有两个孩子,根节点)..." << endl;
        tree.remove(50);
        tree.inorder();
    
        return 0;
    }

    5. 进阶:如何解决“退化”问题?

    由于普通 BST 在极端情况下会退化为链表,为了保证 O(\log n) 的性能,工程师们开发了自平衡二叉搜索树

    • AVL 树: 严格平衡,任何节点的左右子树高度差不超过 1。

    • 红黑树 (Red-Black Tree): 弱平衡,但在插入和删除时旋转次数较少。它是 Java 的 TreeMapTreeSet 以及 C++ STL 中 map 的底层实现。

发表评论