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

文章目录
- 👺前言
- [☠️ 一、二叉搜索树的概念](#☠️ 一、二叉搜索树的概念)
- 😾二、二叉搜索树的性能分析
- 😗三、二叉搜索树的插入
- 🤔四、二叉搜索树的查找
- ✌️五、二叉搜索树的删除
- 👋六、二叉搜索树的实现代码
- 🤢七、二叉搜索树key和key/value使用场景
- 🦴总结
👺前言
在数据结构部分,我们就已经学习了二叉树的相关概念,二叉搜索树就是在二叉树的基础上增加了搜索的功能,具体是怎么实现的呢?跟随着博主的脚步一起出发吧。
提示:以下是本篇文章正文内容,下面案例可供参考
☠️ 一、二叉搜索树的概念
二叉搜索树又叫二叉排序树,它或者是一颗空树,或者是具有以下性质的二叉树
①若它的左子树不为空,则左子树上所有的结点的值都小于等于根节点的值
②若它的右子树不为空,则右子树上所有结点的的值都大于等于根节点的值
③它的左右子树也分别为二叉搜索树
④二叉搜索树可以支持插入相等的值,也可以不支持插入相等的值,具体看场景使用。

😾二、二叉搜索树的性能分析
最优情况下,二叉搜索树为完全二叉树,其高度为: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、红黑树的学习打下一个良好的基础。
