二叉搜索树概念
二叉搜索树又称为二叉排序,它或者是一个空树,或者是具有已下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
它的左右子树也分别为二叉搜索树
二叉搜索树的构建
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
它的特别之处在于中序遍历:1,3,4,6,7,8,10,13,14.
基本框架
template<class K>
struct BSTreeNode
{
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
BSTreeNode<k> _left;
BSTreeNode<K> _right;
K _key;
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//查找,删除,插入...
private:
Node* _root = nullptr;
};
插入
1.空直接插入
2.非空找插入位置,大的右边找,小的左边找
bool insert(const K& key)
{
if(_root == nullptr)//为空
{
_root = new Node(key);
return true;
}
//不为空
Node* parent = nullptr;//记录父节点,为了连接起来
Node* cur = _root;
while(cur)
{
if(cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else//有相同的值
return false;
}
//找到,准备插入
cur = new Node(key);
if(parent->_key > key)//需要判断插入位置在左边还是右边
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
查找
bool 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 true;
}
return false;
}
删除(难点)
删除需要分三种情况:
1.删除的结点为叶子结点(直接删除就可以) ,但是其实可以并入2中。
比如删除4,7,13,1;
2.删除结点的左子树or右子树为空(将这个结点有的子树给连接上去)
3.删除的左右子树都不为空(找出左子树的最大结点or右子树的最小结点来替换删除的结点就可以)
左子树最大结点:左子树最右结点
右子树最小结点:右子树最左结点
bool Erase(const K& key)
{
if(_root == nullptr)
return false;
//先找到要删除的结点
Node* cur = _root;
Node* parent = nullptr;
while(cur)
{
if(cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if(cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else //找到了,准备删除
{
//为了更好理解,我们在判断2.左子树为空or右子树为空的过程中
//可以加上判断左右子树都为空,皆1的情况
if(cur->_left == nullptr)
{
//判断是不是特殊结点:根节点,根节点parent == nullptr,->会报错
if(cur == _root)
{
//当右子树也为空时候,为1.情况但是也让_root=nullptr,都一样
_root = cur->_right;
delete cur;
}
else//不为特殊结点
{
//依旧要判断cur是parent的左还是右
if(cur == parent->_right)
{
parent->_right = cur->_right;
delete cur;
}
else
{
parent->_left = cur->_right;
delete cur;
}
}
}
else if(cur->_right == nullptr)
{
//跟上面判断相同的方法
if(cur == _root)
{
_root = cur->_left;
delete cur;
}
else
{
if(cur == parent->_right)
{
parent->_right = cur->_left;
delete cur;
}
else
{
parenn->_left = cur->_left;
delete cur;
}
}
}
else//左子树和右子树都不为空,替换法删除
{
//找右子树的最小结点(最左结点)
Node* subleft = cur->_right;
Node* parent = cur;//为空的话到后面解引用会出现问题,头节点情况
while(subleft->_left)
{
parent = subleft;
subleft = subleft->_left;
}
swap(cur->_key,subleft->_key);
//交换完删除,但是不能递归删,因为已经交换完了,不再是BST树了
//你甚至找不到你删除的结点
//error : parent->_left = subleft->_right;
//头结点的情况也要考虑
if(subleft == parent->_left)
{
parent->_left = subleft->_right;
delete subleft;
}
else
{
parent->_right = subleft->_right;
delete subleft;
}
}
return true;
}
}
return false;
}
1和2. 删除的节点如果是根节点,父节点为nullptr,要特殊处理,要不然会报错!!
3.交换后也要判断跟结点的情况
二叉搜索树功能的递归实现
查找的递归
public:bool FindR(const K& key)
{
return _FindR(_root,key);
}
private:bool _FindR(Node* root,const K& key)
{
if(root == nullptr)
return false;
if(root->_key > key)
{
return _FindR(root->_left,key);
}
else if(root->_key < key)
{
return _FindR(root->_right,key);
}
else return true;
}
插入递归
public:bool InsertR(const K& key)
{
return _InsertR(_root,key);
}
private:bool _InsertR(Node* & root,const K& key)
{
if(root == nullptr)
{
root = new Node(key);
return true;
}
if(root->_key > key)
{
return _InsertR(root->_left,key);
}
else if(root->_key < key)
{
return _InsertR(root->_right,key);
}
else return false;
}
为什么这里是Node* &root,用上了引用呢?
在传root的时候,使用传引用传参,可以做到自动链接新建的结点。
在我们当前函数root = new Node(key)创建新的结点,因为用了引用,这里的root就是上一个函数栈帧里面的root->_right;所以结点创建完成,返回的过程就相当于完成了链接。
删除递归
public:bool EraseR(const K& key)
{
return _EraseR(_root,key);
}
private:bool _EraseR(Node* & root,const K& key)
{
if(root == nullptr)
return false;
if(root->_key > key)
{
return _EraseR(root->_left,key);
}
else if(root->_key < key)
{
return _EraseR(root->_right,key);
}
else
{
//找到了开始删除,递归成删除根节点了
if(root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
return true;
}
else if(root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete del;
return true;
}
else
{
Node* subleft = root->_left;
while(subleft->_left)
{
subleft = subleft->_left;
}
swap(root->_key,subleft->_key);
return _EraseR(root->_right,key);
}
}
}
二叉搜索树部分默认成员函数实现
构造函数
BSTree(){};
拷贝构造也是构造,默认构造的说法是:只要我们写了构造,它就不会再自动生成,
拷贝构造也是构造,没写默认构造也不会自动生成,会报没有默认拷贝构造错误。
C++11的新特性:default
在函数声明后加=default,将该函数声明为 default 函数,编译器将为显式声明的default函数自动生成函数体。
拷贝构造函数
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
析构函数
~BSTree()
{
Destroy(_root);
}
void Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
//左-右-根
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
赋值运算符重载
//t1 = t3
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
二叉搜索树的性能分析
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?
那么我们后续章节学习的AVL树和红黑树就可以上场了。