二叉搜索树介绍
所谓二叉搜索树首先它是一棵二叉树,其次满足以下的规则:
每一个结点的左子树中的任意结点比这个结点小,右子树的任意结点比这个结点大。举个栗子。
5的左子树全比5小,右子树全比5大,3的左子树全比3小,右子树全比3大,所有结点都满足这个规则。
一、insert
搜索二叉树的插入非常easy,就按照规则,比当前结点大则往右边走,比当前结点小则往左走。
//二叉搜索树框架
template<class T>
struct BSTreeNode
{
typedef BSTreeNode<T> node;
node* _left;
node* _right;
T _key;
BSTreeNode(const T& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template<class T>
class BSTree
{
typedef BSTreeNode<T> node;
public:
//成员函数
private:
node* _root = nullptr;
};
bool insert(const T& key)
{
if(_root == nullptr) //空树
{
_root = new node(key);
return true;
}
node* parent = nullptr;//记录cur的父节点,为了连接cur
node* cur = _root; //cur找到插入的合适位置
while(cur)
{
if(key < cur->_key)//往左边走
{
parent = cur;
cur = cur->_left;
}
else if(key > cur->_key)//往右边走
{
parent = cur;
cur = cur->_right;
}
else //相等不插入
{
return false;
}
}
//找到了位置,判断是在parent左边还是右边
cur = new node(key);
if(key < parent->_key) //在parent左边
{
parent->_left = cur;
}
else //在cur右边
{
parent->_right = cur;
}
return true;
}
二、erase
erase是我们的重头戏,这个玩意不好写。
我们先分类讨论一下,假设要删除的结点是cur,有下面这几种情况。
- cur的右边为空。
- cur的左边为空且右边不为空。
- cur的左右子树都不为空。
我们逐个情况进行分析。情况1,
情况2,
情况3有点意思,如果一个结点左右子树都非空,那怎么搞?我直接干掉它吗?这不太好,所以我们想了个办法,用另外一个结点去替代它,然后再干掉那个替代结点。首先替代结点应该是容易删除,不然这种方法毫无意义,其次,**替代完成后,仍然满足二叉搜索树的所有性质。**综合以上考虑,我们可以使用cur的左子树的最右结点 or 右子树的最左结点。
这里我选择右子树的最左结点来完成这个operation。
接下来我们来实现代码。
void erase(const T& key)
{
//首先找到要删除的结点
node* parent = nullptr;
node* cur = _root;
while(cur)
{
if(key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if(key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
//找到了
//1. cur的右边为空。
//2. cur的左边为空且右边不为空。
// 3. cur的左右子树都不为空。
//但是还有一点要注意,parent可能为空,防止根结点就是要删除的结点。
if(cur->_right == nullptr)
{
if(parent == nullptr)//父节点为空
{
_root = cur->_left;
}
else
{
if(cur == parent->_left)//cur是父节点的左边
{
parent->_left = cur->_left;
}
else //cur是父节点的右边
{
parent->_right = cur->_left;
}
}
delete cur;
}
else if(cur->_left == nullptr)
{
if(parent == nullptr) //父节点为空
{
_root = cur->_right;
}
else
{
if(cur == parent->_left)//cur是父节点的左边
{
parent->_left = cur->_right;
}
else //cur是父节点的右边
{
parent->_right = cur->_right;
}
}
delete cur;
}
else
{
//rLMin是右子树的最小结点
//rLMinParent是rLMin的父节点
node* rLMin = cur->_right;
node* rLMinParent = cur; //这里不能给nullptr,
//否则下面解引用可能报错
while(rLMin->_left) //找到rLMin
{
rLMinParent = rLMin;
rLMin = rLMin->_left;
}
cur->_key = rLMin->_key;
if(rLMin == rLMinParent->_right)//rLMin就是cur的右节点
{
rLMinParent->_right = rLMin->_right;
}
else
{
rLMinParent->_left = rLMin->_right;
}
delete rLMin;
}
return;
}
}
return ;
}
三,find
find就更easy了,找到返回true,没找到返回false。
bool find(const T& key)
{
node* cur = _root;
while(cur)
{
if(key < cur->_key){
cur = cur->_left;
}
else if(key > cur->_key){
cur = cur->_right;
}
else{
return true;
}
}
return false;
}
四,中序遍历
二叉搜索树除了便于查找,它的中序遍历还是有序的,中序遍历代码如下:
void _InOrder(node* root) //这是InOrder的子函数
{
if(root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " " ;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
中序遍历的递归为什么要这样写呢?因为我需要参数root,但在类外部无法访问根节点,所以写一个子函数去递归。
五,验证代码
验证的代码:
void test_bstree()
{
BSTree<int> bst;
int a[] = { 5,3,7,1,4,6,8,0,2,9 };
cout << "insert的检验结果 :" << endl;
for (auto e : a)
{
bst.insert(e);
}
cout << "InOrder的检验结果 :" << endl;
bst.InOrder();
cout << "find的检验结果 :" << endl;
for (auto e : a)
{
cout << bst.find(e) << " ";
}
cout << "erase的检验结果 :" << endl;
for (auto e : a)
{
bst.erase(e);
bst.InOrder();
}
}
在VS上的result:
(全文完)