二叉搜索树的那些事儿

博主的博客主页------>Cinema KI

博主的gitee主页------>llirving

文章目录


👺前言

在数据结构部分,我们就已经学习了二叉树的相关概念,二叉搜索树就是在二叉树的基础上增加了搜索的功能,具体是怎么实现的呢?跟随着博主的脚步一起出发吧。


提示:以下是本篇文章正文内容,下面案例可供参考

☠️ 一、二叉搜索树的概念

二叉搜索树又叫二叉排序树,它或者是一颗空树,或者是具有以下性质的二叉树

①若它的左子树不为空,则左子树上所有的结点的值都小于等于根节点的值

②若它的右子树不为空,则右子树上所有结点的的值都大于等于根节点的值

③它的左右子树也分别为二叉搜索树

④二叉搜索树可以支持插入相等的值,也可以不支持插入相等的值,具体看场景使用。


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

最优情况下,二叉搜索树为完全二叉树,其高度为:log2N

最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为:N

所以综合而言二叉搜索树增删查改时间复杂度为:O(N)

那么这样的效率显然是难以满足我们的要求,后续博主会更新AVL树与红黑树,它们的效率更高,是我们存储数据与搜索数据的首选。

左边就类似于完全二叉树。最差查找情况类似于查找7,log26约等于2.5,所以查找三次就够了。(每次查找就排除一半的选项,7比8小,排除8的右子树,比5大,排除5的左子树,最终找到7。)

右边就是典型的单支树,我们在查找1的时候,查找效率就很低了,得查找4次。


😗三、二叉搜索树的插入

插入的情况有两种

1.树为空,则直接新增结点,让_root指向这个新结点

2.树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位,插入新节点。

代码示例,如下

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 == nullptr;
           return true;
       }
       Node* parent;
       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;
    }
};

🤔四、二叉搜索树的查找

1.从根开始比较,查找x,x比跟的值还大则往右边走,小就往左边走

2.最多查找高度次,走到空 ,还没找到,这个值不存在

3.如果支持插入相等的值,意味着有多个x,一班车要求查找中序下的第一个x

代码示例,如下

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

✌️五、二叉搜索树的删除

首先查找要删除的元素在树中存不存在,不存在,则返回false

如果查找元素存在则分以下四种情况:

①被删除结点N左右孩子均为空

②被删除结点N左孩子为空,右孩子不为空

③被删除结点N右孩子为空,左孩子不为空

④被删除结点N左右孩子均不为空

相应的解决方法如下:

1.把N结点父亲对应孩子指针指向空,直接删除N结点

2.把N结点父亲的孩子指针指向N的右孩子,直接删除N结点

3.把N结点父亲的孩子指针指向N的左孩子,直接删除N结点

4.无法直接删除N结点,只能用替换法删除。找N结点左子树最大结点或者右子树的最小结点替代N ,因为这两个结点中任意一个,放到N位置上,都满足二叉搜索树的规则。替代N的意思就是N和R两个结点的值交换,转而变成删除R结点。


代码示例,如下

cpp 复制代码
bool erase(const K& key)
{
   Node* parent = nullptr;
   Node* cur = _root;
   while(cur)
   {
      if(key < cur->_key)
      {
         parent = cur;
         cur = cur->_left;
      }
      else if(key > cur->_key)
      {
         parent = cur;
         cur = cur->_right;
      }
      else//走进这里,cur指向的就是我们要删除的结点
      {
         if(cur->_left == nullptr)
         {
             if(parent == nullptr)//此种情况对应删除的结点是根节点,但其没有左子树
             {
                cur = _root;
             }
             else
             {
                if(parent->_left == cur)
                   parent->_left = cur->_right;
                else
                   parent->_right = cur->_right;
             }
             delete cur;
             return true;
         }
         else if(cur->_right == nullptr)//左孩子不为空
         {
             if(parent == nullptr)
             {
                cur = _root;
             }
             else
             {
                if(cur == parent->_left)
                   parent->_left = cur->_left;
                else
                   parent->_right = cur->_left;
             }
             delete cur;
             return true;
         }
         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;
      }
   }
}

👋六、二叉搜索树的实现代码

cpp 复制代码
template<class K>
struct BSTNode//Binary Search Tree Node
{
    K _key;
    BSTNode<K>* _left;
    BSTNode<K>* _right;
    BSTNode(const K& key)
          :_key(key)
          ,_left(nullptr)
          ,_right(nullptr)
    {}
};
//
template<class K>
class BSTree//Binary Search Tree
{
    typedef BSTNode<K> Node;
public:
    bool insert(const K& key)
    {
       if(_root == nullptr)//如果是一颗空树
       {
           _root = new Node(key);
           return true;
       }
       Node* cur = _root;
       Node* parent = nullptr;
       while(cur)
       {
           if(key > cur->_key)
           {
              cur = cur->_right;
              parent = cur;
           }
           else
           {
              cur = cur->_left;
              parent = cur;
           }
       }
       cur = new Node(key);
       if(cur == parent->_left)
          parent->_left = cur;
       else
          parent->_right = cur;
    }
    bool Find(const K& key)
    {
       Node* cur = _root;
       while(cur)
       {
          if(key > cur->_key)
          {
              cur = cur->right;
          }
          else if(key < cur->_key)
          {
              cur = cur->_left;
          }
          else
          {
              return true;//这里就是key于cur指向的_key相等,说明找到了,返回true
          }
       }
       //出来就是说明cur为空,说明找不到
       return false;
    }
    bool Erase(const K& key)
    {
       Node* cur = _root;
       Node* parent = nullptr;
       while(cur)//如果删除的是根节点呢?
       {
          if(key > cur->_key)
          {
             parent = cur;
             cur = cur->_right;
          }
          else if(key < cur->_key)
          {
             parent = cur;
             cur = cur->_left;
          }
          else//cur指向的就是被删除的结点
          {
             if(cur->_left == nullptr && cur->_right == nullptr)
             {
                 Node* MinrightP = cur;
                 Node* Minright = cur->_right;
                 while(Minright->_left)
                {
                   MinrightP = Minright;
                   Minright = Minright->_left;
                }
                swap(cur->_key,Minright->_key);//交换两者的_key
                if(Minright == MinrightP->_right)
                {
                   MinrightP->_right = Minright->_right;
                }
                else
                {
                   MinrightP->_left = Minright->_right;
                }
                delete Minright;
                return true;
             }
             else if(cur->_right == nullptr)//右为空和叶子结点合并
             {
                if(parent == nullptr)//被删除的是根节点
                {
                   _root == cur->_left;
                }
                else
                {
                  if(cur == parent->_right)
                     parent->_right = cur->_left
                  else
                     parent->_left = cur->_left;
                  delete cur;
                  return true;
                }
             }
             else//左为空
             {
                if(parent == nullptr)//被删除的是根节点
                {
                   _root = cur->_right;
                }
                else
                {
                   if(cur == parent->_right)
                      parent->_right = cur->_right;
                   else
                      parent->_left = cur->_right;
                }
                delete cur;
                return true;
             }
          }
       }
       return false;
    }
private:
    Node* _root;
}

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

7.1key搜索场景

前面我们对二叉搜索树的实现进行了一个剖析,明白其存在的意义,那接下来我们把这种数据结构与我们的生活场景融合起来。

(只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在,因为key是无法修改的,修改key,那么这颗树就失去意义了)

场景1 :小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,车辆进入是扫描车牌在不在系统种,在则抬杆,不在则提示非小区车辆,无法进入

场景二:检查一篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树,读取文章中的单词,查找是否在二叉搜索树中,不在则波浪线标红提示。

简单讲讲车牌或者单词放入搜索树的好处。你想,假设你把车牌只是放入文件里面去找,1w个车牌你就要找1w次,假设你放入搜索树,log(2)8192才等于13,1w个车牌也只不过需要查找14次而已,大大提高了效率。(当然这必须保证这棵树得是平衡二叉树才行,后面的AVL树和红黑树均是平衡二叉树)
注意:key搜索场景只能判断在与不在,无法修改key的值。

7.2key/value搜索场景

每一个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增删查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value支持修改,但不支持修改key,修改key就破坏了搜索树的性质了

场景1:简单中英互译字典,key存储英文单词,value存储其对应的中文意思
场景2:商场无人值守车库,入口进场时扫描车牌,记录车牌和入场实践,出口离场时,扫描车牌,查找入场时间,用当前实践-入场实践计算出停车时长,计算出停车费用,缴费后抬杠,车辆离场。

key/value关键点就是一个结点要不仅要存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 T& value)
        :_key(key)
        ,_value(value)
        ,_left(nullptr)
        ,_right(nullptr)
    {}
};

有关插入的比较逻辑就是比较_key的大小,与_value无关。其余代码大致跟第五点相差无几

🦴总结

本文对二叉搜索树作了一个详解,为后续的AVL、红黑树的学习打下一个良好的基础。

相关推荐
LYFlied2 小时前
【每日算法】LeetCode 62. 不同路径(多维动态规划)
前端·数据结构·算法·leetcode·动态规划
Trouvaille ~2 小时前
【C++篇】C++11新特性详解(一):基础特性与类的增强
c++·stl·c++11·类和对象·语法·默认成员函数·初始化列表
HUST2 小时前
C 语言 第九讲:函数递归
c语言·开发语言·数据结构·算法·c#
yaoh.wang2 小时前
力扣(LeetCode) 119: 杨辉三角 II - 解法思路
数据结构·python·算法·leetcode·面试·职场和发展·跳槽
客梦2 小时前
数据结构--最小生成树
数据结构·笔记
CSDN_RTKLIB2 小时前
【类定义系列一】C++ 头文件 / 源文件分离
开发语言·c++
CoderCodingNo2 小时前
【GESP】C++五级真题(埃氏筛思想考点) luogu-B3929 [GESP202312 五级] 小杨的幸运数
数据结构·c++·算法
charlee442 小时前
C++中JSON序列化和反序列化的实现
c++·json·序列化·结构体·nlohmann/json
bbq粉刷匠2 小时前
Java--二叉树概念及其基础应用
java·数据结构·算法