1. 二叉搜索树概念
二叉搜索树(BST,Binary Search Tree)又称二叉排序树,它或者是一颗空树,或者是具有以下性质的二叉树。
若他的左子树不为空,则左子树上所有节点的值都小于根节点
若它的右子树不为空,则右子树上所有节点的值都小于根节点
它的左右子树也分别为二叉搜索树
如果我们使用中序去遍历这颗二叉搜索树的话,出来的结果就是有序的。因为中序遍历总是先访问完根的左子树,再访问根,最后访问根的右子树,也就是说先访问比根小的,再访问根,最后访问比根大的。
在一颗二叉搜索树中,左子树的终点是本身最小值,右子树的终点是本身最大值
2. 二叉搜索树操作
又到手搓二叉树的美好时光了,首先做好节点的结构体设置
二叉搜索树的增删查功能接口,拷贝构造、析构、赋值运算符重载这几个操作都是需要我们手动进行深拷贝或清理的,但是本节就不弄了。
二叉搜索树不支持改,因为随便改的话就会破坏掉二叉搜索树的结构,导致其失去基本的搜索功能。
我们把中序遍历的接口也随便实现了一下。
2.1 搜索
搜索功能最简单了,没什么好说的,如果当前节点的值小于查找值就向右树找,如果大于就向左树找。
2.2 插入
插入的时候因为光用cur找到应该插入的位置还不够,需要一个parent指针来确定插入位置的父节点,要不然插不进树中去,插入到父节点的左侧还是右侧也是需要知道的。
最后要注意特殊处理空树的情况,就是插入第一个节点的时候应该怎么插。
2.3 删除
//删除
template<class K>
bool BSTree<K>::Erase(const K& x)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了,开始删除
goto Select_Deletion_Method_cur;
}
}
return false;
//删除的情况选择
Select_Deletion_Method_cur:
if (cur->_left == nullptr)
{
//第一种陷阱
if (parent == nullptr)
{
_root = cur->_right;
delete cur;
return true;
}
if (cur->_data < parent->_data)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if(cur->_right == nullptr)
{
//第一种陷阱
if (parent == nullptr)
{
_root = cur->_left;
delete cur;
return true;
}
if (cur->_data < parent->_data)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else //有两个孩子
{
//用右子树的最小节点作为替代节点
Node* rightMinP = nullptr;
Node* rightMin = cur->_right;
int flag = 0;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
++flag;
}
cur->_data = rightMin->_data;
//第二种陷阱
if (flag == 0)
{
cur->_right = rightMin->_right;
delete rightMin;
return true;
}
rightMinP->_left = rightMin->_right;
delete rightMin;
return true;
}
}
删除代码太长了,没办法截图,只能这么看了,可以先看我下面的解释再看代码。
先说以下删除的基本思路,第一步还是用cur指针先找要删除的目标节点,同时用parent指针记录目标节点的父节点。
接下来目标删除目标节点可能会出现3种情况:
1. 目标节点没有子节点
2. 目标节点只有一个子节点
3. 目标节点有两个子节点
针对前两种情况我们可以统一处理,就是当目标节点只有左子树的时候就把左子树接到parent节点上,如果目标节点只有右子树就把右子树接到parent节点上,然后把目标节点delete掉。这个逻辑的话即使目标节点没有子树也会把nullptr接到parent上。
如果目标节点有两个子节点,我们可以用左树的最大值,或右树的最小值来代替目标节点
左树的最大值就在左树的最右侧节点,右树的最小值在右树的最左侧节点,我上面这个图是想用层序遍历后的结果解释下为什么要使用左树的最大值和右树的最小值代替目标节点,实际上树的结构不是顺序表嗷。
我在实现的时候采用右树的最小值来代替。
此时的基本逻辑完成了,但细节上还存在两个陷阱:
第一种陷阱结构如下
此时我们想删除 节点8 ,此时的parent节点还是空的,此时不能贸然访问parent节点,需要进行特殊处理,直接将 _root 接到 8 的下一个节点上。
第二种陷阱结构如下
此时想删除 节点3 ,因为3有两颗子树,我们选择用右树的最小值来代替3,也就是6,但是因为右树找小时一次就找到了6,因此右树最小值的父节点 rightMinP 此时的状态还是 nullptr 因此它不能被贸然访问,需要做特殊处理。
3. 二叉搜索树的应用
二叉搜索树由两种模型,key(K)模型 和 key-value(KV)模型
1. K模型
K模型即只有key做为关键码值,结构中只需要存储key即可,关键码即为需要搜索到的值
比如给一个单词word判断拼写是否正确,那就以词库中所有单词构建一个二叉搜索树,每个单词作为key。在搜索树中检索这个word是否存在,存在则拼写正确,反之拼写错误。
2. KV模型
每一个关键码key都有一个对应的值value,即<key,value>的键值对。这种方案在实际应用中是常见的
比如一个词典,用单词作为key,中文就作为value,构建二叉搜索树后就可以通过给定单词很快给出其翻译了。
从K模型转化到KV模型,很简单,只需要让树的节点多存一份_value就好了,然后把后面的功能函数稍为改一改就可以了。
完整的代码我放在最后了,这里要注意的是,因为我是进行声明和定义分离写的,typedef的东西是不能在类外用的,就是说如果想让find返回节点指针的时候,在声明中返回值类型可以写成 Node* ,但是在类外的定义中,不能用这个,必须要写它的原始类型也就是 BSTNode<K, V>*
4. 二叉搜索树的性能分析
最优情况下,二叉搜索树为完全二叉树,其平均比较次数就时其深度,即为logN
最差情况下,二叉搜索树退化为单支树,其平均比较次数为N
后续学习中我们会使用AVL树和红黑树优化二叉搜索树的性能。
4. 完整代码
namespace key
{
template<class K>
struct BSTNode
{
BSTNode(const K& x)
:_data(x)
, _left(nullptr)
, _right(nullptr)
{}
K _data;
BSTNode<K>* _left;
BSTNode<K>* _right;
};
template<class K>
class BSTree
{
typedef BSTNode<K> Node;
public:
//插入
bool Insert(const K& x);
//搜索
bool Find(const K& x);
//删除
bool Erase(const K& x);
//中序遍历
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
//中序遍历(子函数)
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
//插入
template<class K>
bool BSTree<K>::Insert(const K& x)
{
//链表为空特殊处理
if (_root == nullptr)
{
_root = new Node(x);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(x);
if (cur->_data < parent->_data)
parent->_left = cur;
else
parent->_right = cur;
return true;
}
//搜索
template<class K>
bool BSTree<K>::Find(const K& x)
{
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
cur = cur->_right;
}
else if (cur->_data > x)
{
cur = cur->_left;
}
else
return true;
}
return false;
}
//删除
template<class K>
bool BSTree<K>::Erase(const K& x)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了,开始删除
goto Select_Deletion_Method_cur;
}
}
return false;
//删除的情况选择
Select_Deletion_Method_cur:
if (cur->_left == nullptr)
{
//第一种陷阱
if (parent == nullptr)
{
_root = cur->_right;
delete cur;
return true;
}
if (cur->_data < parent->_data)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
//第一种陷阱
if (parent == nullptr)
{
_root = cur->_left;
delete cur;
return true;
}
if (cur->_data < parent->_data)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else //有两个孩子
{
//用右子树的最小节点作为替代节点
Node* rightMinP = nullptr;
Node* rightMin = cur->_right;
int flag = 0;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
++flag;
}
cur->_data = rightMin->_data;
//第二种陷阱
if (flag == 0)
{
cur->_right = rightMin->_right;
delete rightMin;
return true;
}
rightMinP->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}
namespace key_value
{
template<class K, class V>
struct BSTNode
{
BSTNode(const K& x, const K& v)
:_data(x)
, _value(v)
, _left(nullptr)
, _right(nullptr)
{}
K _data;
V _value;
BSTNode<K,V>* _left;
BSTNode<K,V>* _right;
};
template<class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
public:
//插入
bool Insert(const K& x, const V& v);
//搜索
Node* Find(const K& x);
//删除
bool Erase(const K& x);
//中序遍历
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
//中序遍历(子函数)
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_data << ":" << root->_value << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
//插入
template<class K, class V>
bool BSTree<K, V>::Insert(const K& x, const V& v)
{
//链表为空特殊处理
if (_root == nullptr)
{
_root = new Node(x, v);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(x,v);
if (cur->_data < parent->_data)
parent->_left = cur;
else
parent->_right = cur;
return true;
}
//搜索
template<class K, class V>
BSTNode<K, V>* BSTree<K, V>::Find(const K& x)
{
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
cur = cur->_right;
}
else if (cur->_data > x)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
//删除
template<class K, class V>
bool BSTree<K, V>::Erase(const K& x)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了,开始删除
goto Select_Deletion_Method_cur;
}
}
return false;
//删除的情况选择
Select_Deletion_Method_cur:
if (cur->_left == nullptr)
{
//第一种陷阱
if (parent == nullptr)
{
_root = cur->_right;
delete cur;
return true;
}
if (cur->_data < parent->_data)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
//第一种陷阱
if (parent == nullptr)
{
_root = cur->_left;
delete cur;
return true;
}
if (cur->_data < parent->_data)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else //有两个孩子
{
//用右子树的最小节点作为替代节点
Node* rightMinP = nullptr;
Node* rightMin = cur->_right;
int flag = 0;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
++flag;
}
cur->_data = rightMin->_data;
//第二种陷阱
if (flag == 0)
{
cur->_right = rightMin->_right;
delete rightMin;
return true;
}
rightMinP->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}