二叉搜索树
1.二叉搜索树的概念
二叉搜索树也叫二叉排序树,是二叉树的进阶且具有以下性质:
- 若左子树不为空,左子树上所有节点的值都小于根节点的值
- 若右子树不为空,右子树上所有节点的值都大于根节点的值
- 左右子树也分别为二叉搜索树
大概就长成这样:
2.二叉搜索树的操作
1).二叉搜索树的查找
查找根据性质很简单啊。
从root开始比较,比root大往右查找,比root小往左查找
最多查找高度次,走到空没找到说明没有这个值。
2).二叉搜索树的插入
插入也不难。
- 如果root为空,直接新增节点赋值给root。
- 如果root不为空,再按照性质找到新根应属的位置,再插入新节点
3).**二叉搜索树的删除
ok兄弟们来活了,这个删除是个重点嗷。
还是这个树,我们可以看到删除是分为多种情况的。
首先先找找要删的值是不是在这个树里,如果不存在直接false,存在就继续分情况:
- 要删除的是叶子节点
- 要删除的节点只有左孩子节点
- 要删除的节点只有右孩子节点
- 要删除的节点有俩孩子节点
在实现的时候我们可以,把叶子节点和只有一个孩子节点的情况融到一起。
ok,我们实现一下上面几个操作
3).二叉搜索树的实现
namespace key
{
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree() = default;//指定强制生成默认构造
bool Insert(const K& key)
{
//为空
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
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为空 parent是最后要链接的节点
//链接
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->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
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->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
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 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//俩孩子(要删的是个根),找子树中最符合的当新的根
//也就是左子树的最右,或者右子树的最左
{
//找左子树的最大
Node* pMaxLeft = cur;
Node* MaxLeft = cur->_left;
while (MaxLeft->_right)
{
pMaxLeft = MaxLeft;
MaxLeft = MaxLeft->_right;
}
//找到左子树的最大值
cur->_key = MaxLeft->_key;
if (pMaxLeft->_left == MaxLeft)
{
pMaxLeft->_left = MaxLeft->_left;
}
else
{
pMaxLeft->_right = MaxLeft->_left;
}
delete MaxLeft;
}
return true;
}
}
return false;
}
private:
Node* _root = nullptr;
};
}
代码中着重讲解了Erase,值得多次理解
4).二叉树的应用
1.K模型:K模型只有key作为关键码,结构中只需要储存Key。关键码:需要搜索到的值。
一对一关系(有没有):汉译英、打卡系统(有这个人的信息打卡成功,没有则失败)
2.KV模型:每一个关键码Key都有与之对应的Value,<Key,Value>
关联:统计出现的次数。
这个宽泛的讲解一下,还需要大家自己多多理解应用。直接上代码吧。
namespace key_value
{
template<class K,class V>
struct BSTreeNode
{
BSTreeNode<K,V>* _left;
BSTreeNode<K,V>* _right;
K _key;
V _value;
BSTreeNode(const K& key,const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
,_value(value)
{}
};
template<class K,class V>
class BSTree
{
typedef BSTreeNode<K,V> Node;
public:
BSTree() = default;//指定强制生成默认构造
BSTree(const BSTree<K,V>& t)
{
_root = Copy(t._root);
}
BSTree<K,V>& operator=(BSTree<K,V> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destroy(_root);
}
bool Insert(const K& key,const V& value)
{
//为空
if (_root == nullptr)
{
_root = new Node(key,value);
return true;
}
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为空 parent是最后要链接的节点
//链接
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->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
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 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//俩孩子(要删的是个根),找子树中最符合的当新的根
//也就是左子树的最右,或者右子树的最左
{
//找左子树的最大
Node* pMaxLeft = cur;
Node* MaxLeft = cur->_left;
while (MaxLeft->_right)
{
pMaxLeft = MaxLeft;
MaxLeft = MaxLeft->_right;
}
//找到左子树的最大值
cur->_key = MaxLeft->_key;
if (pMaxLeft->_left == MaxLeft)
{
pMaxLeft->_left = MaxLeft->_left;
}
else
{
pMaxLeft->_right = MaxLeft->_left;
}
delete MaxLeft;
}
return true;
}
}
return false;
}
//因为_InOrder要传参是_root,但是f(这里不能用_root)所以写一个函数,在内部给_InOrder传_root
void InOrder()
//void _InOrder(Node* root)
{
_InOrder(_root);
cout << endl;
}
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
protected:
void Destroy(Node*& root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root == nullptr;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " "<<root->_value<<" "<<endl;
_InOrder(root->_right);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key == key)
return true;
if (root->_key < key)
{
_FindR(root->_right, key);
}
else
{
_FindR(root->_left, key);
}
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
{
_InsertR(root->_right, key);
}
else if (root->_key > key)
{
_InsertR(root->_left, key);
}
else
{
return false;
}
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
{
_EraseR(root->_right, key);
}
else if (root->_key > key)
{
_EraseR(root->_left, key);
}
else
{
Node* del = root;
//开始删除
if (root->_right == nullptr)
{
root = root->_left;
}
else if (root->_left == nullptr)
{
root = root->_right;
}
else
{
//找左子树最大/右子树最小
Node* MinRight = root->_right;
while (MinRight->_left)
{
MinRight = MinRight->_left;
}
swap(root->_key, MinRight->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
private:
Node* _root = nullptr;
};
}
5).二叉搜索树的性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
因为我们root的选择是根据,输入次序选择的。所以不能保证我们的二叉搜索树是均衡的。
可能出现这种情况:
所以最好情况下时间复杂度为O(logN),最会情况下为O(n),总体来说他还是一个O(n)的数据结构