携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情 >>
一、平衡二叉树是什么?
1、定义
平衡二叉树:左右子树高度差的绝对值不超过1的二叉查找树 特殊地,空树也是平衡二叉树
2、失衡
- 平衡因子=该节点的左子树-右子树的高度差
- 当平衡二叉树中存在某个结点的平衡因子的绝对值大于1,则该平衡树失衡。
- 平衡因子大于2,或者小于-2的结点称为失衡结点
- 最小失衡子树:从新插入结点开始向上寻找第一个失衡结点,以该结点为根的子树就称为最小失衡子树。
二、相关操作
1.平衡二叉树的结构
typedef struct BBSTNode{
//平衡二叉树的结点,包括数据域、平衡因子、左右孩子
RcdType data;
int bf; //平衡因子
struct BBSTNode *lchild,rchild;
}*BBSTree; //平衡二叉树
// BBSTree lc;定义了一个BBSTree类型的指针
2、失衡的四种情况及基本操作
在这里,我们首先需要明确一个点:就整棵树而言调整平衡的方法可能不唯一,哪怕是同一种类型都可能存在多种调整平衡的方法,但是: 一棵平衡二叉树是因为新插入结点而导致不平衡的,所以我们只需要对最小失衡子树进行平衡调整,整棵树也会跟着平衡(插入结点导致高度改变,平衡调整是使得高度复原),所以并不需要整棵树进行调整。因为如果一个树很大,调整方法很多,就很繁琐,也很容易乱。
-
"LL型":在最小失衡子树的左孩子的左子树上插入了新结点。
右旋调整:
//对最小失衡子树p进行右旋调整
void R_Rotate(BBSTree &p) {
BBSTree lc = p->lchild;
p->lchild = lc->rchild;
lc->rchild = p;
p = lc;
}
"LL型":
最小失衡子树的根节点A的平衡因子 2 → 0
其左孩子B的平衡因子 1 → 0
2. "RR型":在最小失衡子树的右孩子的右子树上插入了新结点。
左旋调整:
//对最小失衡子树p进行左旋调整
void L_Rotate(BBSTree &p) {
BBSTree rc = p->rchild; //
p->rchild = rc->rchild;
rc->lchild = p;
p = rc;
}
"RR型":
最小失衡子树的根节点A的平衡因子 -2 → 0
其左孩子B的平衡因子 -1 → 0
3. "LR型":在最小失衡子树的左孩子的右子树上插入新结点。
先左旋,再右旋:
"LR型":
最小失衡子树的根节点A的平衡因子 2 → 2 → -1
其左孩子B的平衡因子 -1 → 0 → 0
其左孩子的右子树C的平衡因子 1 → 2 → 0
4. "RL型":在最小失衡子树的右孩子的左子树上插入结点。
先右旋,在左旋
"RL型":
最小失衡子树的根节点A的平衡因子 -2 → -2 → 0
其左孩子B的平衡因子 -1 → -1 → -1
其左孩子的右子树C的平衡因子 1 → -1 → 0
对于平衡因子的变化,其实也不需要硬要记,可以适当举出些简单例子,即可知道,如下图:
对于“LR型”和“RL型”是比较有意思的,从上图可以对比出来,“LR型”的两种情况,分别进行平衡调整之后,插入结点的位置都是一样的。相信很多小伙伴们,可能会问在第二种情况中,第一次调整的时候已经平衡了,为什么还需要第二次调整呢?嘿嘿,我想这应该是一种方法归纳的原因吧,你们想想,我们在写代码的过程中可能会遇到各种各样的类型,如果每一种类型都用不同的调整方法,那还怎么递归,怎么调用呢?所以啊,这是将“LR型“的两种情况的调整方法统一成一个了。
不知道有没有小伙伴会疑惑为什么只有四种情况呢(LL、RR、LR、RL)? 对于这个问题,小姐姐建议各位去看看这篇文章,分析、总结都挺到位的。
3、左、右平衡处理的操作
在进行左、右平衡处理操作介绍之前,我觉得挺有必要先总结以下,以上四种类型的最小失衡子树在平衡调整前后平衡因子的变化: 不管是“LL型”还是“RR型”,最终平衡因子发生变化的都是孩子和子树,并且最终平衡因子都是变成0. 另外,“LR型”和“RL型”就特殊一点,这两种类型不仅涉及到孩子和子树的平衡因子的变化,还有子树的孩子的变化。 “LR型”:可以看作是一次“左旋”
//左平衡处理
#define LH +1 //左高
#define RH -1 //右高
#define EH 0 //等高
void LeftBalance (BBSTree &T) {
BBSTree lc,rd;
lc = T->lchild;
switch(lc->bf) {
case LH: T->bf = lc->bf = EH; R_Rotate(T),break;
case RH:
rd = lc->rchild;
switch(rd->bf) {
case LH: T->bf = RH; lc->bf = EH;break;
case EH: T->bf = lc->bf = EH;break;
case RH: T->bf = EH; lc->bf = LH;break;
}
rd->bf = EH;
L_Rotate(T->lchild);
R_Rotate(T);
break;
}
}
右平衡:不同情况下,平衡因子变化有些不一样,但是只要属于这种类型则调整平衡方法可统一,即为:先左旋,再右旋。
//对因在右子树上新插入结点导致失衡的最小失衡子树T进行右平衡处理
void RightBalance(BSTree &T) //对以指针T所指结点为根的二叉树作右平衡旋转处理
{
BSTree rc,ld;
rc=T->rchild;
//树是由平衡到失衡的,即T->bf=-2,右边插入,故不可能为2
//所以可以直接有T的左右孩子的平衡因子来判断是何种类型
switch(rc->bf)
{
/*
当右孩子的平衡因子为-1时,-2,-1属于“RR型”
右孩子的平衡因子为1时,-2,1属于“RL型”
右孩子的平衡因子不可能为0,如果右孩子平衡因子为0,T树并没有失衡
*/
case RH:
T->bf=rc->bf=EH;
L_Rotate(T);
break;
case LH:
//到这已经可以1知道是“RL型”了,但是“RL型”的插入有三种情况
//这三种情况只有平衡因子的变化有些不同,而调整平衡的方法统一为先右旋后左旋
ld=rc->lchild;
switch(ld->bf)
{
case RH:T->bf=LH;rc->bf=EH;break;
case EH:T->bf=rc->bf=EH;break;
case LH:T->bf=EH;rc->bf=RH;break;
}
ld->bf=EH;
R_Rotate(T->rchild);
L_Rotate(T);
}
}
4、平衡二叉树的删除操作详解
提醒:
在这里,给大家提个醒吧,一般“LL型”、“RR型”是比较固定的就是不管树是什么样的形状,只需要挑最左、最右插入即可,可是“RL型”和“LR 型”就不一样了,比如“RL型”它是在右孩子的左子树上插入结点是没错的,可是到底是在左子树的哪里插入(左、右)?左子树的原来状态是如何(-1,01)?也正因为这样,所以在右平衡处理的时候要对三种情况进行分情况讨论。