⼆叉搜索树
- 二叉搜索树的概念
- 二叉搜索树的性能分析
- 查找性能
- 插入性能
- 删除性能
- 二叉搜索树的插入
- 二叉搜索树的查找
- 二叉搜索树的删除
- ⼆叉搜索树的实现代码
- 测试代码
- 二叉搜索树key和key/value使⽤场景
- key搜索场景
- key/value搜索场景
- key/value⼆叉搜索树代码实现
- 测试代码
二叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树:
- 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
- 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
- 它的左右⼦树也分别为⼆叉搜索树
- ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我们学习
map/set/multimap/ multiset
系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等值,multimap/multiset⽀持插⼊相等值
注意: map和set是平衡二叉搜索树,底层结构是红黑树
二叉搜索树的性能分析
查找性能
- 在最佳情况下,二叉搜索树的高度为log(n+1),其查找效率为O(logn),近似于折半查找.
- 如果二叉搜索树不平衡,其深度可能达到 n,查找效率退化为O(n),即顺序查找.
插入性能
- 插入操作通常涉及找到正确的插入位置,然后根据需要进行树的调整以保持平衡。在平衡二叉搜索树中,插入操作的时间复杂度为O(logn).
- 如果树不平衡,插入操作可能需要更多的时间,尤其是在树的同一侧连续插入时,可能导致树的退化。
删除性能
- 删除操作需要找到被删除节点,然后进行适当的替换和树的调整。在平衡二叉搜索树中,删除操作的时间复杂度也是O(logn).
- 与插入类似,如果树不平衡,删除操作可能需要更多的时间,尤其是在树的同一侧连续删除时.
总结: 所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为: O(N)那么这样的效率显然是⽆法满⾜我们需求的,后续还会继续讲解⼆叉搜索树的变形,平衡⼆叉搜索树AVL树和红⿊树,才能适⽤于我们在内存中存储和搜索数据。
另外需要说明的是,⼆分查找也可以实现 O(logN) 级别的查找效率,但是⼆分查找有两⼤缺陷:
- 需要存储在⽀持下标随机访问的结构中,并且有序。
- 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
这⾥也就体现出了平衡⼆叉搜索树的价值。
二叉搜索树的插入
插⼊的具体过程如下:
- 树为空,则直接新增结点,赋值给root指针
- 树不空,按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到 空位置,插⼊新结点。
- 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右走,⼀会往左走)
二叉搜索树的查找
- 从根开始⽐较,查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找。
- 最多查找⾼度次,⾛到到空,还没找到,这个值不存在。
- 如果不⽀持插⼊相等的值,找到x即可返回
- 如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找3,要找到1的右孩⼦的那个3返回
二叉搜索树的删除
⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回false。
如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)
- 要删除结点N左右孩⼦均为空
- 要删除的结点N左孩⼦位空,右孩⼦结点不为空
- 要删除的结点N右孩⼦位空,左孩⼦结点不为空
- 要删除的结点N左右孩⼦结点均不为空
对应以上四种情况的解决方案:
- 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)
- 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点
- 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点
- ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤ 替换法删除。找N左⼦树的值最⼤结点R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满⾜⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转⽽变成删除R结点,R结点符合情况2或情况3,可以直接删除。
⼆叉搜索树的实现代码
namespace key
{
//节点
template<class K>
struct BSTNode
{
K _key;
BSTNode* _left;
BSTNode* _right;
BSTNode(const K& key)
:_key(key)
, _left(nullptr)
,_right(nullptr)
{}
};
//二叉搜索树
template<class K>
class BSTree
{
using Node = BSTNode<K>;
public:
bool Insert(const K& key)
{
//如果是根节点,直接插入
if (_root == nullptr)
{
_root = new Node(key);
}
Node* cur = _root;
Node* parent = nullptr;
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* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else //查找到与key相等的地方
{
//左为空
if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
//右为空
else if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else
{
//左右子树都不为空
Node* r = cur->_right;
Node* rp = cur;
while (r->_left)
{
rp = r;
r = r->_left;
}
cur->_key = r->_key;
if (rp->_left == r)
{
rp->_left = r->_right;
}
else
{
rp->_right = r->_right;
}
delete r;
}
return true;
}
}
return false;
}
private:
Node* _root = nullptr;
};
}
测试代码
测试插入
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
key::BSTree<int> st;
for (auto e : a)
{
st.Insert(e);
}
测试删除
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
key::BSTree<int> st;
for (auto e : a)
{
st.Insert(e);
}
for (auto e : a)
{
st.Erase(e);
//打印数据
}
二叉搜索树key和key/value使⽤场景
key搜索场景
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是 不⽀持修改,修改key破坏搜索树结构了。
- 检查⼀篇英⽂⽂章单词拼写是否正确,将词库中所有单词放⼊⼆叉搜索树,读取⽂章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提⽰。(VS的检查与这个也很类似)
key/value搜索场景
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树结构了,可以修改value。
- 统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。
key/value⼆叉搜索树代码实现
和key⼆叉搜索树代码实现类似,需要在节点中增加value这个变量。
namespace key_value
{
template<class K,class V>
struct BSTNode
{
K _key;
V _value;
BSTNode* _left;
BSTNode* _right;
BSTNode(const K& key,const V& value)
:_key(key)
,_value(value)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K,class V>
class BSTree
{
public:
using Node = BSTNode<K, V>;
bool Insert(const K& key,const V& value)
{
if (_root == nullptr)
{
_root = new Node(key,value);
}
Node* cur = _root;
Node* parent = nullptr;
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,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)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else
{
//左右子树都不为空
Node* r = cur->_right;
Node* rp = cur;
while (r->_left)
{
rp = r;
r = r->_left;
}
cur->_key = r->_key;
if (rp->_left == r)
{
rp->_left = r->_right;
}
else
{
rp->_right = r->_right;
}
delete r;
}
return true;
}
}
return false;
}
~BSTree()
{
Destory(_root);
}
BSTree() = default;//C++11 强制生成默认构造
BSTree(const BSTree<K, V>& st)
{
_root = Copy(st._root);
}
BSTree<K, V>& operator=(BSTree tmp)
{
swap(_root, tmp._root);
return *this;
}
protected:
//前序遍历复制二叉搜索树
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newnode = new Node(root->_key, root->_value);
newnode->_left = Copy(root->_left);
newnode->_right = Copy(root->_right);
return newnode;
}
//后序遍历销毁二叉搜索树
void Destory(Node* root)
{
if (root == nullptr)
{
return;
}
Destory(root->_left);
Destory(root->_right);
delete root;
}
private:
Node* _root = nullptr;
};
}
测试代码
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
key_value::BSTree<string, int> st;
for (auto& e : arr)
{
auto ret = st.Find(e);
if (ret != nullptr)
{
ret->_value++;
}
else
{
st.Insert(e, 1);
}
}