1. 概念
二叉搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,它具有以下特点:
- 每个节点最多有两个子节点,分别称为左子节点和右子节点。
- 对于每个节点,其左子树中的所有节点的值都小于该节点的值,而其右子树中的所有节点的值都大于该节点的值。
- 二叉搜索树中不存在重复的节点。
- 二叉搜索树又称排序二叉树,因为根据上述特点,中序遍历这棵树就得到升序序列。
2. 操作
2.1 查找
先从根开始比较,如果查找的值大于根,根据该树特点,那么要查找的值肯定在右子树,然后再查找右子树,否则相反。
根据上述思想,我们只需要查找高度次就能查到,否则查不到,时间复杂度为O(log n)。
2.2 插入
如果树为空,直接插入。
先从根开始比较,如果要插入的值大于根,就插入右子树,继续比较,直到为空,然后插入该值。
2.3 删除
删除比较麻烦,我们先查找要删除的值是不是在树里,如果在树里面,要分四种情况讨论。
- 删除的值为叶子节点。直接删除就行。
- 删除的值有左孩子。将左孩子连接到要删除节点的父亲节点。
- 删除的值有右孩子。将右孩子连接到要删除节点的父亲节点。
- 删除的值有两个孩子。在它的左子树里面找到最大节点,或右子树里面找到最小节点,替换掉要删除的值。然后删除左子树的最大节点或右子树最小节点,因为这种节点是满足上述三种情况的节点,按照其方法删掉就行。
3. 模拟实现
template<class K, class V>
struct BSTreeNode
{
typedef struct BSTreeNode<K, V> Node;
Node* _left;
Node* _right;
K _key;
V _val;
BSTreeNode(const K& key,const V& val)
:_left(nullptr)
,_right(nullptr)
,_key(key)
,_val(val)
{}
};
template<class K, class V>
class BSTree
{
typedef struct BSTreeNode<K, V> Node;
public:
bool Insert(const K& key, const V& value)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(key, value);
if (!parent)
_root = cur;
else if (cur->_key > parent->_key)
parent->_right = cur;
else if (cur->_key < parent->_key)
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;
}
cout << "找不到" << endl;
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
break;
}
if (!cur)
return false;
//没孩子/一个孩子
if (!cur->_left)
{
Node* tmp = cur;
if (cur == _root)
{
_root = cur->_right;
}
else
{
cur = cur->_right;
if (cur->_key > parent->_key)
parent->_right = cur;
else if (cur->_key < parent->_key)
parent->_left = cur;
}
delete tmp;
return true;
}
else if (!cur->_right)
{
Node* tmp = cur;
if (cur == _root)
{
_root = cur->_left;
}
else
{
cur = cur->_left;
if (cur->_key > parent->_key)
parent->_right = cur;
else if (cur->_key < parent->_key)
parent->_left = cur;
}
delete tmp;
return true;
}
else //两个孩子,替换法,找左边最大,或右边最小替换
{
Node* parent = cur;
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
parent->_key = rightMin->_key;
parent->_val = rightMin->_val;
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
return true;
}
return false;
}
void _InOrder(Node* root)
{
if (!root)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
return;
}
private:
Node* _root = nullptr;
};
4. 应用
- K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:小区的汽车栏杆,只将你车的车牌为关键码,在树里面查找,如果你是该小区的就自动抬杆,否则就进不去。 - KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
比如:- 汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对。
- 统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
- 商场的汽车栏杆,你进入,系统就将你的<车牌号码,入库时间>作为键值对插入到树里,出去的时候就查找,然后按时间收费。
5. 性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
如果对于同一个关键值的集合,插入的顺序不同,那查找的效率就不同,如果插入完之后,形状和满二叉树一样,那查找效率最高为O(log n),但如果插入完之后,形状是竖直的,那么查找效率就退化到O(n),那么这种结构就没有用了。AVL树和红黑树就是解决这种情况的。