《数据结构:二叉搜索树(Binary Search Tree)》

文章目录

作者的个人gitee​​

作者的算法讲解主页▶️

每日一言:"**人生如逆旅,我亦是行人。**🌸🌸"


🔴一、二叉搜索树的概念

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

  • 若左子树不为空,则左子树上所有结点的值都小于等于根结点的值。
  • 若右子树不为空,则右子树上所有结点的值都大于等于根结点的值。
  • 左右子树也分别为二叉搜索树。
  • 二叉搜索树中可以支持插入相等的值,也可以不支持,具体取决于使用场景的定义。如,在C++标准模板库中,mapset不支持插入相等值,而multimapmultiset支持插入相等值。

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

二叉搜索树的性能主要取决于其高度。以下是不同情况下的性能分析:

情况 高度 增删查改时间复杂度
最优 logN O(logN)
最差 N O(N)
平均 取决于插入顺序 取决于插入顺序
  • 最优情况 :当二叉搜索树为完全二叉树或接近完全二叉树时,其高度为log2N。此时增删查改的时间复杂度为 O(log2N)。
  • 最差情况 :当二叉搜索树退化为单支树或类似单支时,其高度为N。此时增删查改的时间复杂度为O(N)。
  • 平均情况 :在实际应用中,二叉搜索树的高度取决于插入数据的顺序。如果插入数据是随机 的,那么平均情况下二叉搜索树的性能接近最优 情况;但如果插入数据是有序 的,那么二叉搜索树可能会退化为单支树,性能接近最差情况。

🔴三、二叉搜索树的操作

(一)插入

插入操作的步骤如下:

  1. 树为空:如果二叉搜索树为空,则直接创建一个新的结点,并将其赋值给根指针。
  2. 树不为空 :从根结点开始,按照二叉搜索树的性质进行比较:
    • 如果插入值小于当前结点的值,则向左子树移动。
    • 如果插入值大于当前结点的值,则向右子树移动。
    • 如果插入值等于当前结点的值(支持插入相等值的情况),可以选择向左或向右移动,但需要保持逻辑一致性。
  3. 找到空位置:当找到一个空位置时,创建一个新的结点,并将其插入到该位置。

(二)查找

查找操作的步骤如下:

  1. 从根开始:从根结点开始,将目标值与当前结点的值进行比较。
  2. 比较并移动
    • 如果目标值小于当前结点的值,则向左子树移动。
    • 如果目标值大于当前结点的值,则向右子树移动。
  3. 查找结果
    • 如果在某个结点找到目标值,则返回该结点。
    • 如果走到空结点仍未找到目标值,则说明目标值不存在。

(三)删除

删除操作相对复杂,需要分情况处理:

情况 描述 解决方案
1 要删除结点N左右孩子均为空 把N结点的父母对应孩子指针指向空,直接删除N结点
2 要删除的结点N左孩子为空,右孩子结点不为空 把N结点的父母对应孩子指针指向N的右孩子,直接删除N结点
3 要删除的结点N右孩子为空,左孩子结点不为空 把N结点的父母对应孩子指针指向N的左孩子,直接删除N结点
4 要删除的结点N左右孩子结点均不为空 无法直接删除N结点,用替换法删除。找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,然后变成删除R结点,R结点符合情况2或情况3,可以直接删除

🔴四、二叉搜索树的实现代码

(一)结构体 BSTNode

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

(二)类 BSTree

cpp 复制代码
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
            {
                // 情况1:左右孩子均为空
                if (cur->_left == nullptr && cur->_right == nullptr)
                {
                    if (parent == nullptr)
                    {
                        _root = nullptr;
                    }
                    else
                    {
                        if (parent->_left == cur)
                            parent->_left = nullptr;
                        else
                            parent->_right = nullptr;
                    }
                    delete cur;
                    return true;
                }
                // 情况2:左孩子为空,右孩子不为空
                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;
                    return true;
                }
                // 情况3:右孩子为空,左孩子不为空
                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;
                    return true;
                }
                // 情况4:左右孩子均不为空
                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搜索场景

在key搜索场景中,二叉搜索树仅存储关键码(key),用于判断某个值是否存在。

  • 小区无人值守车库:将买了车位的业主车牌号录入后台系统,车辆进入时扫描车牌号,判断其是否存在于系统中,从而决定是否抬杆。
  • 英文单词拼写检查:将词库中的所有单词放入二叉搜索树,读取文章中的单词,查找其是否存在于二叉搜索树中,若不存在则提示拼写错误。

(二)key/value搜索场景

在key/value搜索场景中,二叉搜索树的每个结点不仅存储关键码(key),还存储与之对应的值(value)。搜索时以key为关键字进行比较,可以快速找到key对应的value。

  • 中英互译字典:在树的结点中存储英文单词(key)和对应的中文翻译(value),搜索时输入英文单词,即可找到其对应的中文翻译。
  • 商场无人值守车库:入口进场时扫描车牌号,记录车牌号和入场时间(key/value),出口离场时扫描车牌号,查找入场时间,计算停车时长和费用。
  • 统计文章中单词出现次数:读取一个单词,查找其是否存在于二叉搜索树中,若不存在则插入该单词并将其出现次数初始化为1,若存在则将其对应的出现次数加1。

🔴六、总结

二叉搜索树是一种重要的数据结构,具有插入、查找、删除等操作。其性能在最优情况下接近O(log2N),但在最差情况下会退化为O(N)。为了提高二叉搜索树的性能,后续可以学习其进阶,如平衡二叉搜索树(AVL树)、B树和红黑树。


如果有帮助的话麻烦点个赞和关注吧,秋梨膏QAQ!

相关推荐
Mi Manchi262 分钟前
力扣热题100之反转链表
算法·leetcode·链表
n33(NK)7 分钟前
【算法基础】选择排序算法 - JAVA
数据结构·算法·排序算法
CS创新实验室20 分钟前
408考研逐题详解:2009年第6题
数据结构·考研·算法·408·真题·计算机考研·408计算机
虾球xz1 小时前
游戏引擎学习第263天:添加调试帧滑块
c++·学习·游戏引擎
努力努力再努力wz1 小时前
【c++深入系列】:万字详解vector(附模拟实现的vector源码)
运维·开发语言·c++·c
.YM.Z1 小时前
C语言——操作符
c语言·开发语言·算法
江畔柳前堤2 小时前
信息论12:从信息增益到信息增益比——决策树中的惩罚机制与应用
运维·深度学习·算法·决策树·机器学习·计算机视觉·docker
yxc_inspire2 小时前
基于Qt的app开发第六天
开发语言·c++·qt
ouliten2 小时前
《C++ Templates》:有关const、引用、指针的一些函数模板实参推导的例子
c++
阳洞洞2 小时前
leetcode 142. Linked List Cycle II
数据结构·leetcode·链表·双指针