二叉搜索树完全解析:从概念到实现与应用场景

引言

二叉搜索树(Binary Search Tree,BST),也称为二叉排序树,是一种特殊的二叉树结构。它要么是一棵空树,要么满足以下性质:

  • 若左子树非空,则左子树上所有结点的值小于等于根结点的值;

  • 若右子树非空,则右子树上所有结点的值大于等于根结点的值;

  • 左右子树本身也都是二叉搜索树。

二叉搜索树是后续学习平衡二叉搜索树(AVL树、红黑树)以及标准库中 setmapmultisetmultimap 等关联式容器底层实现的基础。本文将从 BST 的基本概念出发,系统讲解其插入、查找、删除操作的具体实现与代码细节,分析其性能优缺点,并深入阐述 key 搜索模型与 key/value 搜索模型的应用场景及完整代码实现。


目录

引言

一、二叉搜索树的概念与性质

二、二叉搜索树的性能分析

三、二叉搜索树的插入

四、二叉搜索树的查找

五、二叉搜索树的删除

[六、二叉搜索树的代码实现(key 模型)](#六、二叉搜索树的代码实现(key 模型))

[七、二叉搜索树的 key/value 使用场景](#七、二叉搜索树的 key/value 使用场景)

[7.1 key 搜索场景](#7.1 key 搜索场景)

[7.2 key/value 搜索场景](#7.2 key/value 搜索场景)

[7.3 key/value 二叉搜索树代码实现](#7.3 key/value 二叉搜索树代码实现)

总结


一、二叉搜索树的概念与性质

二叉搜索树的核心性质是有序性:中序遍历 BST 可以得到一个有序序列(升序)。正是这种性质,使得 BST 在查找数据时能够通过比较大小快速缩小搜索范围。

关于相等值的处理:

  • 某些 BST 实现不支持插入相等值 (如 set/map),即所有结点的值唯一。

  • 另一些实现支持插入相等值 (如 multiset/multimap),此时可以约定相等值统一向右(或向左)插入,保持逻辑一致性。


二、二叉搜索树的性能分析

最优情况 下,BST 为完全二叉树或接近完全二叉树,高度为 log⁡2Nlog2​N,此时增删查改的时间复杂度为 O(log⁡N)O(logN)。

最差情况下,BST 退化为单支树(如插入已排序的数据),高度为 NN,时间复杂度退化为 O(N)O(N)。

与二分查找的对比

  • 二分查找也能达到 O(log⁡N)O(logN) 的查找效率,但它要求数据存储在支持随机访问且有序的数组中。

  • 二分查找的致命缺陷是插入和删除效率极低,因为需要挪动大量元素(平均 O(N)O(N))。

  • 而 BST(尤其是平衡 BST)可以在 O(log⁡N)O(logN) 时间内完成插入和删除,弥补了数组的不足。

由于原生 BST 在最坏情况下性能较差,后续将引入 AVL 树和红黑树等平衡二叉搜索树,以保证树的高度始终保持在 O(log⁡N)O(logN)。


三、二叉搜索树的插入

插入过程:

  1. 若树为空,则直接创建新结点作为根结点。

  2. 若树非空,从根开始比较:

    • 插入值大于当前结点值,向右走;

    • 插入值小于当前结点值,向左走;

    • 如果支持插入相等值,可以统一向右(或向左)走到空位置。

  3. 走到空位置后,创建新结点并链接到父结点的对应孩子指针。

注意:不支持相等值时,遇到相同 key 应插入失败(返回 false)。

示例 :向空树依次插入 {8, 3, 1, 10, 6, 4, 7, 14, 13} 后,得到的 BST 形状如课件图示。


四、二叉搜索树的查找

查找过程:

  1. 从根开始比较,若查找值等于当前结点值,则查找成功。

  2. 若查找值大于当前结点值,则向右子树继续查找;若小于,则向左子树继续查找。

  3. 若走到空结点仍未找到,则查找失败。

对于支持相等值的 BST(如 multiset),查找时通常要求返回中序遍历中第一个 等于 key 的结点。例如树中有多个 3,中序序列为 1, 3, 3, 4, ...,应返回第一个 3。


五、二叉搜索树的删除

删除是 BST 最复杂的操作。首先查找待删除结点 N,若不存在则返回 false;若存在,分四种情况处理:

情况 描述 处理方式
1 N 左右孩子均为空 直接删除,父结点对应指针置空(可合并到情况2/3)
2 N 左空右非空 父结点指向 N 的右孩子,删除 N
3 N 右空左非空 父结点指向 N 的左孩子,删除 N
4 N 左右均非空 替换法:找 N 左子树的最大结点(最右)或右子树的最小结点(最左),交换两者的值,然后转而删除那个替代结点(替代结点必然属于情况2或3,可安全删除)

关键细节

  • 删除根结点时,父结点为 nullptr,需单独更新 _root

  • 替换法中,若右子树的最小结点就是 cur->right(即右子树的根没有左孩子),需特殊处理父指针的指向。


六、二叉搜索树的代码实现(key 模型)

以下是仅存储 key 的 BST 核心代码(不支持重复值):

cpp

复制代码
template<class K>
struct BSTNode {
    K _key;
    BSTNode<K>* _left;
    BSTNode<K>* _right;
    BSTNode(const K& key) : _key(key), _left(nullptr), _right(nullptr) {}
};

template<class K>
class BSTree {
    typedef BSTNode<K> Node;
public:
    bool Insert(const K& key) {
        if (_root == nullptr) {
            _root = new Node(key);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur) {
            if (cur->_key < key) {
                parent = cur;
                cur = cur->_right;
            } else if (cur->_key > key) {
                parent = cur;
                cur = cur->_left;
            } else {
                return false;  // 不允许重复
            }
        }
        cur = new Node(key);
        if (parent->_key < key) parent->_right = cur;
        else parent->_left = cur;
        return true;
    }

    bool Find(const K& key) {
        Node* cur = _root;
        while (cur) {
            if (cur->_key < key) cur = cur->_right;
            else if (cur->_key > key) cur = cur->_left;
            else return true;
        }
        return false;
    }

    bool Erase(const K& key) {
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur) {
            if (cur->_key < key) {
                parent = cur;
                cur = cur->_right;
            } else if (cur->_key > key) {
                parent = cur;
                cur = cur->_left;
            } else {
                // 处理左空或右空的情况
                if (cur->_left == nullptr) {
                    if (parent == nullptr) _root = cur->_right;
                    else if (parent->_left == cur) parent->_left = cur->_right;
                    else parent->_right = cur->_right;
                    delete cur;
                } else if (cur->_right == nullptr) {
                    if (parent == nullptr) _root = cur->_left;
                    else if (parent->_left == cur) parent->_left = cur->_left;
                    else parent->_right = cur->_left;
                    delete cur;
                } else {
                    // 左右均非空:找右子树最小结点
                    Node* rightMinP = cur;
                    Node* rightMin = cur->_right;
                    while (rightMin->_left) {
                        rightMinP = rightMin;
                        rightMin = rightMin->_left;
                    }
                    cur->_key = rightMin->_key;  // 交换值
                    if (rightMinP->_left == rightMin) rightMinP->_left = rightMin->_right;
                    else rightMinP->_right = rightMin->_right;
                    delete rightMin;
                }
                return true;
            }
        }
        return false;
    }

    void InOrder() { _InOrder(_root); cout << endl; }
private:
    void _InOrder(Node* root) {
        if (root == nullptr) return;
        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }
    Node* _root = nullptr;
};

七、二叉搜索树的 key/value 使用场景

7.1 key 搜索场景

仅存储 key,用于判断某个值是否存在。典型应用:

  • 车库门禁系统:已登记的车牌号作为 key,车辆进入时扫描车牌,若 key 存在则抬杆。

  • 英文拼写检查:将词库中所有单词存入 BST,读取文章单词并查找是否存在,不存在则标红提示。

7.2 key/value 搜索场景

每个 key 关联一个 value,查找 key 的同时获取对应的 value。value 可以是任意类型。支持修改 value,但不允许修改 key(会破坏 BST 结构)。典型应用:

  • 英汉词典:key 为英文单词,value 为中文释义。

  • 商场停车场计费:入口扫描车牌(key)并记录入场时间(value);出口扫描车牌查找入场时间,计算费用。

  • 统计单词出现次数 :遍历文档,若单词(key)不存在,则插入 (单词, 1);若存在,则将其 value(次数)加 1。

7.3 key/value 二叉搜索树代码实现

cpp

复制代码
template<class K, class V>
struct BSTNode {
    K _key;
    V _value;
    BSTNode<K, V>* _left;
    BSTNode<K, V>* _right;
    BSTNode(const K& key, const V& value) : _key(key), _value(value), _left(nullptr), _right(nullptr) {}
};

template<class K, class V>
class BSTree {
    typedef BSTNode<K, V> Node;
public:
    bool Insert(const K& key, const V& value) {
        if (_root == nullptr) {
            _root = new Node(key, value);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur) {
            if (cur->_key < key) {
                parent = cur;
                cur = cur->_right;
            } else if (cur->_key > key) {
                parent = cur;
                cur = cur->_left;
            } else {
                return false;  // key 已存在,不允许重复(可根据需求修改为更新 value)
            }
        }
        cur = new Node(key, value);
        if (parent->_key < key) parent->_right = cur;
        else parent->_left = cur;
        return true;
    }

    Node* Find(const K& key) {
        Node* cur = _root;
        while (cur) {
            if (cur->_key < key) cur = cur->_right;
            else if (cur->_key > key) cur = cur->_left;
            else return cur;
        }
        return nullptr;
    }

    bool Erase(const K& key) {
        // 实现与 key 模型相同,仅需注意交换时同时交换 _key 和 _value
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur) {
            if (cur->_key < key) { parent = cur; cur = cur->_right; }
            else if (cur->_key > key) { parent = cur; cur = cur->_left; }
            else {
                if (cur->_left == nullptr) {
                    if (parent == nullptr) _root = cur->_right;
                    else if (parent->_left == cur) parent->_left = cur->_right;
                    else parent->_right = cur->_right;
                    delete cur;
                } else if (cur->_right == nullptr) {
                    if (parent == nullptr) _root = cur->_left;
                    else if (parent->_left == cur) parent->_left = cur->_left;
                    else parent->_right = cur->_left;
                    delete cur;
                } else {
                    Node* rightMinP = cur;
                    Node* rightMin = cur->_right;
                    while (rightMin->_left) {
                        rightMinP = rightMin;
                        rightMin = rightMin->_left;
                    }
                    cur->_key = rightMin->_key;
                    cur->_value = rightMin->_value;  // 同时替换 value
                    if (rightMinP->_left == rightMin) rightMinP->_left = rightMin->_right;
                    else rightMinP->_right = rightMin->_right;
                    delete rightMin;
                }
                return true;
            }
        }
        return false;
    }

    void InOrder() { _InOrder(_root); cout << endl; }

    // 拷贝构造、赋值重载、析构等(参见课件完整代码)
private:
    void _InOrder(Node* root) {
        if (root == nullptr) return;
        _InOrder(root->_left);
        cout << root->_key << ":" << root->_value << endl;
        _InOrder(root->_right);
    }
    void Destroy(Node* root) {
        if (root == nullptr) return;
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }
    Node* Copy(Node* root) {
        if (root == nullptr) return nullptr;
        Node* newRoot = new Node(root->_key, root->_value);
        newRoot->_left = Copy(root->_left);
        newRoot->_right = Copy(root->_right);
        return newRoot;
    }
    Node* _root = nullptr;
};

使用示例:统计水果出现次数

cpp

复制代码
int main() {
    string arr[] = {"苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉"};
    BSTree<string, int> countTree;
    for (const auto& str : arr) {
        auto ret = countTree.Find(str);
        if (ret == nullptr) countTree.Insert(str, 1);
        else ret->_value++;
    }
    countTree.InOrder();
    return 0;
}

总结

二叉搜索树是一种基于二分思想的数据结构,其核心操作(插入、查找、删除)在平均情况下复杂度为 O(log⁡N)O(logN),但最坏情况会退化到 O(N)O(N)。本文详细阐述了 BST 的定义、性能瓶颈、插入查找删除的完整流程及代码实现,并区分了 key 模型与 key/value 模型的实际应用场景。

掌握 BST 是理解更高级平衡树(AVL、红黑树)的基础,也是掌握 C++ 标准库关联容器底层原理的关键一步。在实际工程中,若数据量较大且操作频繁,应优先使用平衡二叉搜索树或哈希表,但对于学习数据结构的内在逻辑和递归/迭代操作,BST 依然是不可替代的经典范例。

相关推荐
努力努力再努力FFF1 小时前
别再乱学PS、Python了,普通大学生该看懂的技能趋势
开发语言·python
鱼跃厂长1 小时前
这份skill,能将你的简历提升到字节的水平!
c++·ai·ai编程
05候补工程师1 小时前
深度解构 ROS 2:如何手动调通 Nav2 A* 路径规划引擎
linux·人工智能·经验分享·算法·机器人
上弦月-编程2 小时前
【C语言逻辑题】谋杀案凶手是谁?——经典矛盾推理题详解
算法
天若有情6732 小时前
逆向玩家狂喜!用C++野生写法一键破解线性加密(不规范但巨好用)
开发语言·c++·算法
XiYang-DING2 小时前
JavaScript
开发语言·javascript·ecmascript
咸鱼翻身小阿橙2 小时前
Qt QML调用C++注册类
java·c++·qt
skywalk81632 小时前
代码高尔夫(Code Golf)是一种以“用最少的字符数实现特定功能”为核心目标的编程挑战或风格。
开发语言
xyq20242 小时前
MySQL 安装配置
开发语言