一、二叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它可以是⼀棵空树,或者是具有以下性质的⼆叉树:
- 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
- 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
- 它的左右⼦树也分别为⼆叉搜索树
如下:
⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义。
二、二叉搜索树性能
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:O(logN)
最差情况下,⼆叉搜索树退化为单⽀树(类似链表),其⾼度为:O(N)
所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为:O(N)
如果要追求较高的查找、插入和删除效率,通常会选择使用平衡二叉搜索树(如AVL树、红黑树等),这些树通过特定的旋转操作来保持树的平衡,从而确保操作的时间复杂度保持在O(log n)。
另外需要说明的是,⼆分查找也可以实现O(logN) 级别的查找效率,但是⼆分查找有两⼤缺陷:
-
需要存储在⽀持下标随机访问的结构中,并且有序。
-
插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
这⾥也就体现出了平衡⼆叉搜索树的价值。
三、二叉搜索树的增删查
1.插入
1.树为空树:直接新增节点赋值给root
2.树不为空:按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。
- 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛。)
如下:
cpp
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* ky = new Node(key);
Node* cur = _root, *parent = nullptr;
while (cur)
{
parent = cur;
if (key < cur->_key) cur = cur->left;
else if (key > cur->_key) cur = cur->right;
else return false;
}
if (key < parent->_key) parent->left = ky;
else parent->right = ky;
return true;
}
2.查找
-
从根开始⽐较,查找key,key⽐根的值⼤则往右边⾛查找,key⽐根值⼩则往左边⾛查找。
-
最多查找⾼度次,⾛到空还没找到,这个值不存在。
-
如果不⽀持插⼊相等的值,找到key即可返回。
-
如果⽀持插⼊相等的值,意味着有多个key存在,⼀般要求查找中序的第⼀个key。
查找与插入的逻辑类似如下:
cpp
Node* Find(const K& key)
{
Node* cur=_root;
while (cur)
{
if (key < cur->_key) cur = cur->left;
else if (key > cur->_key) cur = cur->right;
else return cur;
}
return nullptr;
}
3.删除
对于二叉搜索树的删除操作是比较麻烦的因为不仅要把对应的节点删除还要保持它原有的性质不变,那么比如要删除key我们来分两种情况来讨论:
1.key的还子小于两个(即叶子节点或单枝)
对于该情况可以直接让key的父节点指向它的孩子,然后删除key就行。因为这种情况只有一个孩子或没有孩子比较好管理。
2.key有两个孩子
因为key有两个孩子,所以把key删除后如何管理它的孩子是个问题,很容易把整颗树的结构和性质改变。这里的解决方法是不直接把key对应的这块空间删除,而是找到key左树上的最大值或者右树上的最大值(记为cur)。然后把cur的值与key的值做交换(或者不用交换,把cur的值存储在key里面),然后删除cur这块空间。
cpp
bool Erase(const K& key)
{
Node* det = _root, parent = _root;
while (det)
{
parent = det;
if (key < det->_key) det= det->left;
else if (key > det->_key) det = det->right;
else break;
}
if (det == nullptr) return true;
if (det->left == nullptr || det->right == nullptr)
{
if (det->left == nullptr)
{
if (parent->left == det) parent->left = det->right;
else parent->right = det->right;
}
else
{
if (parent->left == det) parent->left = det->left;
else parent->right = det->left;
}
delete det;
}
else
{
Node* cur = det->left;
while (cur->right)
{
cur = cur->right;
}
det->_key = cur->_key;
delete cur;
cur = nullptr;
}
四、key和key/valued
二叉搜索树里面除了储存key以外,还可以同时储存key和valued,每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树结构了,可以修改value。
五、源码
cpp
template<class K, class V>
class BSTreeNode
{
public:
K _key;
V _val;
BSTreeNode* left;
BSTreeNode* right;
BSTreeNode(K key,V val)
:_key(key)
,_val(val)
,left(nullptr)
,right(nullptr){}
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
bool Insert(const K& key, const V& val)
{
if (_root == nullptr)
{
_root = new Node(key, val);
return true;
}
Node* ky = new Node(key, val);
Node* cur = _root, *parent = nullptr;
while (cur)
{
parent = cur;
if (key < cur->_key) cur = cur->left;
else if (key > cur->_key) cur = cur->right;
else return false;
}
if (key < parent->_key) parent->left = ky;
else parent->right = ky;
return true;
}
Node* Find(const K& key)
{
Node* cur=_root;
while (cur)
{
if (key < cur->_key) cur = cur->left;
else if (key > cur->_key) cur = cur->right;
else return cur;
}
return nullptr;
}
bool Erase(const K& key)
{
Node* det = _root, parent = _root;
while (det)
{
parent = det;
if (key < det->_key) det= det->left;
else if (key > det->_key) det = det->right;
else break;
}
if (det == nullptr) return true;
if (det->left == nullptr || det->right == nullptr)
{
if (det->left == nullptr)
{
if (parent->left == det) parent->left = det->right;
else parent->right = det->right;
}
else
{
if (parent->left == det) parent->left = det->left;
else parent->right = det->left;
}
delete det;
}
else
{
Node* cur = det->left;
while (cur->right)
{
cur = cur->right;
}
det->_key = cur->_key;
det->_val = cur->_val;
delete cur;
cur = nullptr;
}
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->left);
cout << root->_key << ":" << root->_val;
_InOrder(root->right);
}
void InOrder()
{
_InOrder(_root);
}
~BSTree()
{
Free(_root);
_root = nullptr;
}
void Free(Node* root)
{
if (root == nullptr) return;
Free(root->left);
Free(root->right);
delete root;
}
private:
Node* _root = nullptr;
};