一、二叉搜索树的基本性质
二叉搜索树又称二叉排序树,它具有一下的特点:
1. 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
2.若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
3.它的左右⼦树也分别为⼆叉搜索树
4. ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等值,multimap/multiset⽀持插⼊相等值给出个样例如下图:
二、二叉搜索树的性能分析
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:logN
最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为: N中和情况来说:时间复杂度为O(N)
另外需要说明的是,⼆分查找也可以实现O(log N)级别的查找效率,但是⼆分查找有两⼤缺陷:
1.需要存储在⽀持下标随机访问的结构中,并且有序。
2.插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数
据。
这⾥也就体现出了平衡⼆叉搜索树的价值
三、二叉搜索树的实现
3.1.创建叶子结点
cpp
template <class K>
struct BSTNode
{
K _key;
BSTNode<K>* left;
BSTNode<K>* right;
BSTNode(const K& key)
:_key(key)
, left(nullptr)
, right(nullptr)
{}
};
3.2.创建根结点
cpp
//二叉搜索树的建立
template <class K>
class BSTree
{
typedef BSTNode<K> node;
public:
.............//一些成员函数
private:
node* _root=nullptr;
};
四、实现成员函数
4.1.插入函数:非递归版本
cpp
bool insert(const K& key)
{
if (_root == nullptr)
{
_root= new node(key);
}
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;
}
}
if (key<parent->_key)
parent->left = new node(key);
else
parent->right = new node(key);
return true;
}
代码的简要解释:
1.为空树的话就插入的结点就是根结点
2.while循环的条件里:比根结点小那就往左结点继续走,比结点大那就往右结点走,我这里不实现相等的数据,所以这里相等就不插入。
4.2.插入函数:递归版本
cpp
bool insert(node*& root, const K& key)
{
if (root == nullptr)
{
root = new node(key);
return true;
}
if (key < root->_key)
return insert(root->left, key);
if (key > root->_key)
return insert(root->right, key);
return false;
}
代码的简要解释:
这里node & root 加引用的目的是当递归到左孩子或右孩子为空的的父结点时对空结点的结点new一个结点就相当于在前一次递归中为它的左孩子或是右孩子插入这个数据,比它小就是插入它的左孩子,比它大就是插入它的右孩子,因为加了引用对它的空结点的改变也就是对它的左右孩子的改变,就省去了要多递归一个找父结点的参数*
4.3.查找函数
cpp
bool 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 true;
}
}
return false;
}
4.4.中序遍历
cpp
void _Inorder(node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->left);
cout << root->_key << ' ';
_Inorder(root->right);
}
4.5.删除函数
思路讲解:
1.当要删除的结点它的左右孩子为空时,就直接删除就行
2.当删除的结点它的左孩子不为空右孩子为空,可以让要删除的结点的父结点连接要删除的结点的左孩子,然后删除这个结点
3.当删除的结点它的左孩子为空右孩子不为空,可以让要删除的结点的父结点连接要删除的结点的右孩子,然后删除这个结点
4.当删除的结点左右孩子都不为空时,那可以让删除结点的左孩子那边的最大的结点与删除结点的数据交换后,再删除那个被交换数据的最大结点,如果它还有左孩子那就让它的父结点连接它; 或那可以让删除结点的右孩子那边的最小的结点与删除结点的数据交换后,再删除那个被交换数据的最小结点,如果它还有右孩子那就让它的父结点连接它;
cpp
bool erase(const K& key)
{
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 {
//开始删除指定数据
////第一种情况:被删除结点没有孩子
//if (cur->left == nullptr && cur->right == nullptr)
//{
// if (parent->left == cur)
// {
// parent->left = nullptr;
// }
// else {
// parent->right = nullptr;
// }
// delete cur;
// break;
//}
// //第一种情况可以包含在第二种情况中
//第二种情况: 被删除结点只有一个孩子
//那就可以让它的父结点帮忙带它的孩子
if (cur->left == nullptr)
{
if (cur != _root) {
if (parent->left == cur)
{
parent->left = cur->right;
}
else {
parent->right = cur->right;
}
}
else {
_root = cur->right;
}
delete cur;
return true;
}
else if (cur->right == nullptr)
{
if (cur != _root) {
if (parent->left == cur)
{
parent->left = cur->left;
}
else {
parent->right = cur->left;
}
}
else {
_root = cur->left;
}
delete cur;
return true;
}
else {
//第三种情况: 被删除的结点有两个孩子
//用它的父结点左孩子的最大值或右孩子的最小值替换父结点然后在删除
node* minright = cur->right;
node* minparent = cur;
while (minright->left)
{
minparent = minright;
minright = minright->left;
}
swap(cur->_key, minright->_key);
if(minparent->right==minright)
minparent->right = minright->right;
else {
minparent->left = minright->right;
}
delete minright;
return true;
}
}
}
return false;
}

