二叉排序树
视频讲解请看王道考研
链接如下
【王道计算机考研 数据结构】 https://www.bilibili.com/video/BV1b7411N798/?p=75&share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
还有一个它动画做的不错,b站 蓝不过海呀
链接如下
【二叉搜索树(二叉排序树)(二叉查找树)】 https://www.bilibili.com/video/BV1uK421x7JJ/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
主要是学习数据结构,真的做什么请直接使用map、set等容器
二叉排序树定义和性质
定义:
二叉排序树,又称二叉查找树(BST,Binary Search Tree)
一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
左子树上所有结点的关键字均小于根结点的关键字.
右子树上所有结点的关键字均大于根结点的关键字。
左子树和右子树又各是一棵二又排序树
性质:
左子树结点值< 根结点值 < 右子树结点值
进行中序遍历(左根右),可以得到一个递增的有序序列
定义二叉排序树这一结构时,直接继承之前的二叉树
然后这里说一下,按理说排序是根据关键值key排序,我这里直接用data当关键值了,注意一下,我懒得改了
//二叉排序树
template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
}
二叉排序树的查找
我这里先说查找,再说插入和删除,因为插入和删除涉及到查找
二叉排序树中:左子树结点值< 根结点值< 右子树结点值,因此可以利用循环解决,不必使用递归
若树非空,目标值与根结点的值比较:
若相等,则查找成功;
若小于根结点,则在左子树上查找,否则在右子树上查找。
查找成功,返回结点指针;查找失败返回NULL
相对简单,直接上代码
template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
/*功能:在本二叉排序树中查找元素e
* 输入:
* e:要查找的元素
* 返回:
* 成功:元素的结点的指针
* 失败:NULL
* 其它:
*/
public:
BTNode<ElemType>* Search(ElemType e);
}
template<class ElemType>
BTNode<ElemType>* BST<ElemType>::Search(ElemType e)
{
BTNode<ElemType>* t = this->root;
//若等于此结点值(说明找到),或者是空结点(说明找完了也没有找到),则结束循环
//也就是说循环条件是,不等于此结点值并且不是空结点
while (t != NULL && e != t->data)//好像t!=NULL要写在前面,这样t==NULL的时候就不继续往后了,否则NULL的时候还会去访问t->data
{
if (e < t->data)//小于,则在左子树中查找
t = t->lchild;
else//大于,则在左子树中查找
t = t->rchild;
}
return t;
}
在后面的插入和删除中,我们需要找到“查找路径上的上一个结点”,以便于处理
将普通的查找简单改造即可实现
template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
protected:
/*功能:在本二叉排序树中查找元素e,并得到查找路径上的“上一个结点”
* 输入:
* e:要查找的元素
* f:用来指向"上一个结点"的指针
* 返回:
* 成功:e元素的结点的指针
* 失败:NULL
* 其它:如果最后f为NULL,说明二叉树为空,或者根节点为要找的
* 插入的时候要用
*/
BTNode<ElemType>* Search_f(ElemType e, BTNode<ElemType>*& f);
}
template<class ElemType>
BTNode<ElemType>* BST<ElemType>::Search_f(ElemType e, BTNode<ElemType>*& f)
{
BTNode<ElemType>* t = this->root;
f = NULL;
//若等于此结点值(说明找到),或者是空结点(说明找完了也没有找到),则结束循环
//也就是说循环条件是,不等于此结点值并且不是空结点
while (t != NULL && e != t->data)//好像t!=NULL要写在前面,这样t==NULL的时候就不继续往后了,否则NULL的时候还会去访问t->data
{
f = t;//赋值f,使其为“上一个”
if (e < t->data)//小于,则在左子树中查找
t = t->lchild;
else//大于,则在左子树中查找
t = t->rchild;
}
return t;
}
二叉排序树的插入
若原二叉排序树为空,则直接插入结点;
否则,
若关键字k小于此结点值,则插入到此结点左子树
若关键字k大于此结点值,则插入到此结点右子树
若关键字k等于此结点值,原本就存在,插入失败
基本就是查找的要求,只是要得到“查找路径上的“上一个”结点,也就是我前面在查找中提到的函数Search_f
template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
public:
/*功能:在本二叉排序树中插入元素e
* 输入:
* e:要插入的元素
* 返回:
* 成功:true
* 失败:false(原本存在元素e)
* 其它:
*/
bool Insert(ElemType e);
}
template<class ElemType>
bool BST<ElemType>::Insert(ElemType e)
{
if (this->root == NULL)//如果没有根结点,直接申请
{
this->root =new BTNode<ElemType>(e);
return true;
}
BTNode<ElemType>* t = NULL;
BTNode<ElemType>* p = NULL;//指向查找路径上访问的最后一个结点(目标的父亲)
//看看原本有没有,并确定插入位置
t = this->Search_f(e, p);
if (t != NULL)//原本存在元素e
return false;
else
{
BTNode<ElemType>* s = new BTNode<ElemType>(e);
if (e < p->data)
{
p->lchild = s;
s->father = p;
}
else
{
p->rchild = s;
s->father = p;
}
return true;
}
}
后面我们会需要,插入完了之后不光知道是否插入成功,还要得到插入的结点指针
简单改造上面的代码
template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
protected:
/*功能:在本二叉排序树中插入元素e
* 输入:
* e:要插入的元素
* 返回:
* 成功:形成的结点的指针
* 失败:NULL(原本存在元素e)
* 其它:
* 子类平衡二叉树中使用
*/
BTNode<ElemType>* Insert_p(ElemType e);
}
template<class ElemType>
BTNode<ElemType>* BST<ElemType>::Insert_p(ElemType e)
{
if (this->root == NULL)//如果没有根结点,直接申请
{
this->root = new BTNode<ElemType>(e);
return this->root;
}
BTNode<ElemType>* t = NULL;
BTNode<ElemType>* p = NULL;//指向查找路径上访问的最后一个结点(目标的父亲)
//看看原本有没有,并确定插入位置
t = this->Search_f(e, p);
if (t != NULL)//原本存在元素e
return NULL;
else
{
BTNode<ElemType>* s = new BTNode<ElemType>(e);
if (e < p->data)
{
p->lchild = s;
s->father = p;
}
else
{
p->rchild = s;
s->father = p;
}
return s;
}
}
二叉排序树的删除
现在开始,问题变得有点复杂了
先搜索找到目标结点:(被删除结点记为target)
if (this->root == NULL)//本来啥都没有直接返回false
return false;
//先尝试找到e的结点
BTNode<ElemType>* target = this->Search(e);
//原本就不存在元素e,直接返回错
if (target == NULL) return false;
然后
1、若被删除结点是叶结点
直接删除,不会破坏二叉排序树的性质
注意:
如果删除的时普通叶子节点
看它是他爹的左孩子还是右孩子,然后处理他爹的对应指针,再删除它
如果删除的是根节点
别忘了把类的root指针置为空
代码如下
//1、若被删除结点是叶结点,则直接删除
if (target->lchild == NULL && target->rchild == NULL)
{
if (target->father == NULL)//要删除的target是根结点,而且是叶子节点,就是只有target一个
{
this->root = NULL;//处理一下根的指针
}
else//target不是根结点,如果是根节点直接删除就行
{
if (target->father->rchild == target)//别忘了处理父节点
target->father->rchild = NULL;
else
target->father->lchild = NULL;
}
delete target;
return true;
}
2、若被删除结点只有一棵左子树或右子树
让target的子树成为target父结点的子树,替代target的位置。
同样的,请注意如果删除的就是根节点这种特殊情况
代码如下
//2、若结点target只有一棵左子树或右子树,则让target的子树成为target父结点的子树,替代target的位置。
//只有一棵左子树
else if (target->lchild != NULL && target->rchild == NULL)
{
if (target->father == NULL)//target本身是根结点
{
this->root = target->lchild;
target->lchild->father = NULL;
}
else
{
//如果目标是他爹的左孩子
if (target == target->father->lchild)
{
target->father->lchild = target->lchild;
target->lchild->father = target->father;
}
else//如果目标是他爹的右孩子
{
target->father->rchild = target->lchild;
target->lchild->father = target->father;
}
}
delete target;
return true;
}
//只有一棵右子树
else if (target->lchild == NULL && target->rchild != NULL)
{
if (target->father == NULL)//target本身是根结点
{
this->root = target->rchild;
target->rchild->father = NULL;
}
else
{
//如果目标是他爹的左孩子
if (target == target->father->lchild)
{
target->father->lchild = target->rchild;
target->rchild->father = target->father;
}
else//如果目标是他爹的右孩子
{
target->father->rchild = target->rchild;
target->rchild->father = target->father;
}
}
delete target;
return true;
}
3、若被删除结点有左、右两棵子树
令target的直接后继 (或直接前驱)替代target,然后从二叉排序树中删去这个直接后继 (或直接前驱),
这样就转换成了第一或第二种情况。
target的直接后继,就是target的右子树的中序遍历第一个,也就是target的右子树一直“往左走”
该节点一定没有左子树
target的直接前驱,就是target的左子树的中序遍历最后一个,也就是target的左子树一直“往右走”
该节点一定没有右子树
注意,这时target结点不会被删除,它的直接后继(或前驱)才会被删除
我的代码中使用的是直接后继
先找到target的直接后继(记为successor),就是在target的右子树中一直“往左走”,然后把successor的数据付给target
BTNode<ElemType>* successor = NULL;//目标的直接后继
//在target的右子树中一直“往左走”
successor = target->rchild;
while (successor->lchild != NULL)//一直“往左走”
{
successor = successor->lchild;
}
//令target的直接后继替代target
target->data = successor->data;
3.1直接后继为叶子结点
注意,直接后继可能是他爹的右孩子,这时,target右子树只有successor这一个结点,请分别处理,或删除前判断
if (successor->lchild==NULL && successor->rchild==NULL)//直接后继为叶子结点
{
if (successor->father == target)//特殊情况,目标右子树就一个,见后面注释2
{
target->rchild = NULL;
}
else
{
//删去这个直接后继
successor->father->lchild = NULL;//直接后继只可能是它爹的左孩子
}
}
3.2直接后继有右子树
同样的,直接后继可能是他爹的右孩子,这时,情况如图中所示,请分别处理,或删除前判断
代码如下
else//直接后继只有右子树
{
if (successor->father == target)//特殊情况,目标右子树根节点就是后继,见后面注释4
{
target->rchild = successor->rchild;
successor->rchild->father = target;
}
else
{
//删去这个直接后继
successor->father->lchild = successor->rchild;//见后面注释3
successor->rchild->father = successor->father;
}
}
删除的完整代码
template<class ElemType>
class BST :public BiTree<ElemType>
{
public:
/*功能:在本二叉排序树中删除元素e
* 输入:
* e:要删除的元素
* 返回:
* true:成功
* false:失败(原本就不存在元素e)
* 其它:会保证排序树结构不被破坏
*/
bool Delete(ElemType e);
}
template<class ElemType>
bool BST<ElemType>::Delete(ElemType e)
{
if (this->root == NULL)//本来啥都没有直接返回false
return false;
//先尝试找到e的结点
BTNode<ElemType>* target = this->Search(e);
//原本就不存在元素e,直接返回错
if (target == NULL) return false;
//1、若被删除结点是叶结点,则直接删除
if (target->lchild == NULL && target->rchild == NULL)
{
if (target->father == NULL)//要删除的target是根结点,而且是叶子节点,就是只有target一个
{
this->root = NULL;//处理一下根的指针
}
else//target不是根结点,如果是根节点直接删除就行
{
if (target->father->rchild == target)//别忘了处理父节点
target->father->rchild = NULL;
else
target->father->lchild = NULL;
}
delete target;
return true;
}
//2、若结点target只有一棵左子树或右子树,则让target的子树成为target父结点的子树,替代target的位置。
//只有一棵左子树
else if (target->lchild != NULL && target->rchild == NULL)
{
if (target->father == NULL)//target本身是根结点,见下面注释1
{
this->root = target->lchild;
target->lchild->father = NULL;
}
else
{
//如果目标是他爹的左孩子
if (target == target->father->lchild)
{
target->father->lchild = target->lchild;
target->lchild->father = target->father;
}
else//如果目标是他爹的右孩子
{
target->father->rchild = target->lchild;
target->lchild->father = target->father;
}
}
delete target;
return true;
}
//只有一棵右子树
else if (target->lchild == NULL && target->rchild != NULL)
{
if (target->father == NULL)//target本身是根结点,见下面注释1
{
this->root = target->rchild;
target->rchild->father = NULL;
}
else
{
//如果目标是他爹的左孩子
if (target == target->father->lchild)
{
target->father->lchild = target->rchild;
target->rchild->father = target->father;
}
else//如果目标是他爹的右孩子
{
target->father->rchild = target->rchild;
target->rchild->father = target->father;
}
}
delete target;
return true;
}
/*3、若结点target有左、右两棵子树,
* 则令target的直接后继(或直接前驱)替代target,然后从二叉排序树中删去这个直接后继(或直接前驱),
* 这样就转换成了第一或第二种情况。
* 我这里使用直接后继,也就是target的右子树的中序遍历第一个(在target的右子树中一直“往左走”)
* 该节点一定没有左子树
*/
else
{
BTNode<ElemType>* successor = NULL;//目标的直接后继
//在target的右子树中一直“往左走”
successor = target->rchild;
while (successor->lchild != NULL)//一直“往左走”
{
successor = successor->lchild;
}
//令target的直接后继替代target
target->data = successor->data;
if (successor->lchild==NULL && successor->rchild==NULL)//直接后继为叶子结点
{
if (successor->father == target)//特殊情况,目标右子树就一个,见后面注释2
{
target->rchild = NULL;
}
else
{
//处理相关指针
successor->father->lchild = NULL;//直接后继只可能是它爹的左孩子
}
}
else//直接后继只有右子树
{
if (successor->father == target)//特殊情况,目标右子树根节点就是后继,见后面注释4
{
target->rchild = successor->rchild;
successor->rchild->father = target;
}
else
{
//处理相关指针
successor->father->lchild = successor->rchild;//见后面注释3
successor->rchild->father = successor->father;
}
}
delete successor;//删去这个直接后继
return true;
}
}
类似的,后面会需要,删除后,知道真实删掉的结点的父结点,以及删掉的是左孩子还是右孩子
对上面的普通删除简单改造一下
template<class ElemType>
class BST :public BiTree<ElemType>
{
public:
/*功能:在本二叉排序树中删除元素e
* 输入:
* e:要删除的元素
* realDeleteFather:记录真实删掉的结点的父亲的指针
* isLeft:记录删掉的是左孩子(true)还是右孩子(false)
* 返回:
* true:成功
* false:失败(原本就不存在元素e)
* 其它:
* 会保证排序树结构不被破坏
* 子类平衡二叉树中使用
* 如果返回true,但realDeleteFather为NULL,说明删除的是根节点
*/
bool Delete_p(ElemType e, BTNode<ElemType>*& realDeleteFather, bool& isLeft);
}
template<class ElemType>
bool BST<ElemType>::Delete_p(ElemType e, BTNode<ElemType>*& realDeleteFather, bool& isLeft)
{
realDeleteFather = NULL;
isLeft = false;
if (this->root == NULL)//本来啥都没有直接返回false
return false;
//先尝试找到e的结点
BTNode<ElemType>* target = this->Search(e);
//原本就不存在元素e,直接返回错
if (target == NULL) return false;
//1、若被删除结点是叶结点,则直接删除
if (target->lchild == NULL && target->rchild == NULL)
{
if (target->father == NULL)//要删除的target是根结点,而且是叶子节点,就是只有target一个
{
this->root = NULL;//处理一下根的指针
}
else//target不是根结点,如果是根节点直接删除就行
{
if (target->father->rchild == target)//别忘了处理父节点
{
isLeft = false;
target->father->rchild = NULL;
}
else
{
isLeft = true;
target->father->lchild = NULL;
}
}
realDeleteFather = target->father;
delete target;//找到真实删除的它爹
return true;
}
//2、若结点target只有一棵左子树或右子树,则让target的子树成为target父结点的子树,替代target的位置。
else if (target->lchild != NULL && target->rchild == NULL)//只有一棵左子树
{
if (target->father == NULL)//target本身是根结点,见下面注释1
{
this->root = target->lchild;
target->lchild->father = NULL;
}
else
{
realDeleteFather = target->father;//找到真实删除的它爹
//如果目标是他爹的左孩子
if (target == target->father->lchild)
{
isLeft = true;
target->father->lchild = target->lchild;
target->lchild->father = target->father;
}
else//如果目标是他爹的右孩子
{
isLeft = false;
target->father->rchild = target->lchild;
target->lchild->father = target->father;
}
}
delete target;
return true;
}
//只有一棵右子树
else if (target->lchild == NULL && target->rchild != NULL)
{
if (target->father == NULL)//target本身是根结点,见下面注释1
{
this->root = target->rchild;
target->rchild->father = NULL;
}
else
{
realDeleteFather = target->father;//找到真实删除的它爹
//如果目标是他爹的左孩子
if (target == target->father->lchild)
{
isLeft = true;
target->father->lchild = target->rchild;
target->rchild->father = target->father;
}
else//如果目标是他爹的右孩子
{
isLeft = false;
target->father->rchild = target->rchild;
target->rchild->father = target->father;
}
}
delete target;
return true;
}
/*3、若结点target有左、右两棵子树,
* 则令target的直接后继(或直接前驱)替代target,然后从二叉排序树中删去这个直接后继(或直接前驱),
* 这样就转换成了第一或第二种情况。
* 我这里使用直接后继,也就是target的右子树的中序遍历第一个(在target的右子树中一直“往左走”)
* 该节点一定没有左子树
*/
else
{
BTNode<ElemType>* successor = NULL;//目标的直接后继
//在target的右子树中一直“往左走”
successor = target->rchild;
while (successor->lchild != NULL)//一直“往左走”
{
successor = successor->lchild;
}
//令target的直接后继替代target
target->data = successor->data;
if (successor->lchild == NULL && successor->rchild == NULL)//情况1,直接后继为叶子结点
{
if (successor->father == target)//特殊情况,目标右子树就一个,见后面注释2
{
isLeft = false;
realDeleteFather = target;
target->rchild = NULL;
}
else
{
isLeft = true;
realDeleteFather = successor->father;
//删去这个直接后继
successor->father->lchild = NULL;//直接后继只可能是它爹的左子树
}
}
else//情况2,直接后继有右子树(直接后继的左子树必为空)
{
isLeft = true;
realDeleteFather = successor->father;
if (successor->father == target)//特殊情况,目标右子树根节点就是后继,见后面注释4
{
target->rchild = successor->rchild;
successor->rchild->father = target;
}
else
{
//删去这个直接后继
successor->father->lchild = successor->rchild;//见后面注释3
successor->rchild->father = successor->father;
}
}
delete successor;
return true;
}
}