一.右旋和左旋
1.右旋(有右孩子则抛弃右孩子)
<1>平平无奇的右旋
上图就是一个平平无奇的右旋,简单来说就是:
- 断开D和B的联系
-
将B的右子树指向D,即D作为B的右子树(因为D比B大)
-
用B代替原先D的位置
<2>鸠占鹊巢的右旋(B本身有右子树)
若B本身有右子树,如:
此时B本身是有右子树的,对以D为结点的此最小平衡树进行右旋:
- 将D的左子树指向B的右子树(B和D断开,把B的右子树作为D的左子树)
- 将B的右子树指向D(B和C断开,把D作为B的右子树)
- 用B代替原先D的位置
<3>通用右旋操作
<1>和<2>右旋合并到一种情况:
- Step1:将D的左子树指向B的右子树(B的右子树成为D的左子树)
①对于平平无奇的右旋,B的右子树为NULL,则D的左子树指向NULL
②对于鸠占鹊巢的右旋,B的右子树为C,则D的左子树指向C
- Step2:将B的右子树指向D(D成为B的右子树)
①对于平平无奇的右旋,B的右子树指向D
②对于鸠占鹊巢的右旋,B的右子树指向D
- Step3:将B顶替原先D的位置(B成为新的中心)
两者完全可以归并到一种情况,因此代码为:
//AVL树通用右旋操作
void R_Rotate(BiTree *P)
{
BiTree L;
L = (*P)->lchild; //定义L为P的左结点
(*P)->lchild = L->rchild; //Step1:*P的左子树指向L的右子树(将L的右子树挂为*P的左子树)
L->rchild = (*P); //Step2:L的右子树指向*P(将P挂为L的右子树)
*P = L; //Step3:代替原有*P的位置
}
2.左旋(有左孩子则抛弃左孩子)
<1>平平无奇的左旋
上图就是一个平平无奇的左旋,简单来说就是:
- 断开B和D的联系
-
将D的左子树指向B,即B作为D的左子树(因为B比D小)
-
用D代替原先B的位置
<2>鸠占鹊巢的左旋(D本身有左子树)
若D本身有左子树,如:
此时D本身是有左子树的,对以B为结点的此最小平衡树进行右旋:
- 将B的右子树指向D的左子树(B和D断开,把D的左子树作为B的右子树)
- 将D的左子树指向B(D和C断开,把B作为D的左子树)
- 用D代替原先B的位置
<3>通用左旋操作
<1>和<2>左旋合并到一种情况:
- Step1:将B的右子树指向D的左子树(D的左子树成为B的右子树)
①对于平平无奇的左旋,D的左子树为NULL,则B的右子树指向NULL
②对于鸠占鹊巢的左旋,D的左子树为C,则B的右子树指向C
- Step2:将D的左子树指向B(B成为D的左子树)
①对于平平无奇的左旋,D的左子树指向B
②对于鸠占鹊巢的左旋,D的左子树指向B
- Step3:将D顶替原先B的位置(D成为新的中心)
两者完全可以归并到一种情况,因此代码为:
//AVL树通用左旋操作
void L_Rotate(BiTree *P)
{
BiTree R;
R = (*P)->rchild; //定义R为P的右结点
(*P)->rchild = R->lchild; //Step1:*P的右子树指向R的左子树(将R的左子树挂为P的右子树)
R->lchild = (*P); //Step2:R的左子树指向*P(将P挂为R的左子树)
(*P) = L; //Step3:代替原有*P的位置
}
二.AVL有且只有的四种情况(LL,LR,RL,RR)
1.LL(Left-Left):单右旋
LL即Left-Left,这时候要进行:单右旋
LL型顾名思义,即插入A结点的L结点的L结点下(可以是LL的左或右都行,下图New为LL下的左子树)引起的不平衡,这种情况,要进行单右旋操作
此时的单右旋操作因为B本身有右结点,因此属于在一中介绍的右旋之鸠占鹊巢右旋
拓展:
问题Q:
LL类型本质上是在中心结点(A)Left--Left后的结点(上图结点7)后插入一个New结点,导致二叉树失衡
解决A:
只需要对以结点(A)为中心的二叉树进行一次(单次)右旋操作,即可恢复平衡性
FAQ:
那么,上图中是在结点7之后的左结点插入了New,构成了LL-L,导致了二叉树的失衡,
实际上,在结点7之后的右结点插入New,构成LL-R,也会也会导致二叉树的失衡。
但是,我们都将其归结到LL型,因为这两种类型都是其子类型,且这两种类型都可以通过对:以结点(A)为中心的二叉树进行一次(单次)右旋操作恢复平衡性。
Summary总结:只要是LL型,那么对中心结点二叉树进行一次单右旋操作就可恢复平衡性
2.RR(Right-Right):单左旋
RR即Right-Right,这时候要进行:单左旋
RR型顾名思义,即插入A结点的R结点的R结点下(可以是RR的左或右都行,下图New为RR下的右子树)引起的不平衡,这种情况,要进行单左旋操作
此时的单左旋操作因为B本身有左结点,因此属于在一中介绍的左旋之鸠占鹊巢左旋
拓展:
问题Q:
RR类型本质上是在中心结点(A)Right-Right后的结点(上图结点7)后插入一个New结点,导致二叉树失衡
解决A:
只需要对以结点(A)为中心的二叉树进行一次(单次)左旋操作,即可恢复平衡性
FAQ:
那么,上图中是在结点7之后的右结点插入了New,构成了RR-R(下图),导致了二叉树的失衡
实际上,在结点7之后的左结点插入New,构成RR-L(下图),也会也会导致二叉树的失衡。
但是,我们都将其归结到RR型,因为这两种类型都是其子类型,且这两种类型都可以通过对:以结点(A)为中心的二叉树进行一次(单次)左旋操作恢复平衡性。
Summary总结:只要是RR型,那么对中心结点二叉树进行一次单左旋操作就可恢复平衡性
3.LR(Left-Right):双旋(先L后R)
LR即Left-Right型,这时候要进行:双旋(先对A的左子树进行L旋转,后对A为中心的全部R旋转)
LR型顾名思义,即插入A结点的L结点的R结点下(可以是LR下的L结点或R结点,也可以在LR下接着拓展L/R等,上图New为LR下的左子树)引起的不平衡,这种情况,要进行双旋操作
第一旋:对以A的左子树为根节点的树(即L后面结点,B-9-New)进行左旋;
第二旋:再对以A为根节点的树(即全部结点)进行右旋;
以上图为例,详细展开来说即为:
第一旋:对A的左子树(L结点后面的树B)进行左旋,因为结点9本身有左结点,因此这属于鸠占鹊巢的左旋;
旋转后就从L-R型变为了L-L型
第二旋:第一旋后的结果为L-L型,因此以A为根节点,进行单右旋,旋转后二叉树平衡
拓展:
问题Q:
LR类型本质上是在中心结点(A)Left-Right后的结点后插入一个New结点,导致二叉树失衡
解决A:
只需要对以结点A的左子树为根节点的二叉树进行一次左旋操作,再对A为根节点的二叉树进行一次右旋操作,即可恢复平衡性
FAQ:
那么,上图中是在结点9之后的左结点插入了New,构成了LR-L(下图),导致了二叉树的失衡
实际上,在结点9之后的右结点插入New,构成LR-R(下图),也会也会导致二叉树的失衡。
其实可以更复杂一点,甚至可以衍生出:
LR-LL类(LR-LL,LR-LLL,LR-LLLL.....)
LR-LR类(LR-LR,LR-LRR,LR-LRRR...)
LR-RR类(LR-RR.LR-RRR,LR-RRRR...)
LR-RL类(LR-RL.LR-RLL,LR-RLLL...)
等等等等各种类型
但是我们都将其归结到LR型,因为都是其子类型,要明白:
-
只要是LR型,不论怎么衍生,其本质是不变的;
-
只要是LR型,就一定可以通过:Step1对中心节点左子树进行左旋,Step2对中心结点整体进行右旋恢复平衡;
Summary总结:只要是LR型,那么对中心结点左子树的二叉树进行一次左旋,再对中心结点二叉树进行一次右旋操作就可恢复平衡性
4.RL(Right-Left):双旋(先R后L)
RL即Right-Left型,这时候要进行:双旋(先对A的右子树进行R旋转,后对A为中心的二叉树进行L旋转)
RL型顾名思义,即插入A结点的R结点的L结点下(可以是RL下的L结点或R结点,也可以在RL下接着拓展L/R等,上图New为RL下的左子树)引起的不平衡,这种情况,要进行双旋操作
第一旋:对以A的右子树为根节点的树(即R后面结点,B-13-10-New)进行右旋;
第二旋:再对以A为根节点的树(即全部结点)进行左旋;
以上图为例,详细展开来说即为:
第一旋:对A的右子树(R结点后面的树B)进行单右旋,因为结点10本身无右结点,因此这属于平平无奇的右旋;
旋转后就从R-L型变为了R-R型
第二旋:第一旋后的结果为R-R型,因此以A为根节点,进行单左旋,旋转后二叉树平衡
拓展:
问题Q:
RL类型本质上是在中心结点(A)Right-Left后的结点后插入一个New结点,导致二叉树失衡
解决A:
只需要对以结点A的右子树为根节点的二叉树进行一次右旋操作,再对A为根节点的二叉树进行一次左旋操作,即可恢复平衡性
FAQ:
其实可以更复杂一点,甚至可以衍生出:
RL-LL类
RL-LR类
RL-RR类
RL-RL类
等等等等各种类型
但是我们都将其归结到RL型,因为都是其子类型,要明白:
-
只要是RL型,不论怎么衍生,其本质是不变的;
-
只要是RL型,就一定可以通过:Step1对中心节点右子树进行右旋,Step2对中心结点整体进行左旋恢复平衡;
Summary总结:只要是RL型,那么对中心结点右子树的二叉树进行一次右旋,再对中心结点二叉树进行一次左旋操作就可恢复平衡性
三.代码
#define LH +1 /* 左高 */
#define EH 0 /* 等高 */
#define RH -1 /* 右高 */
/* 对以指针T所指结点为根的二叉树作左平衡旋转处理 */
/* 本算法结束时,指针T指向新的根结点 */
void LeftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild; /* L指向T的左子树根结点 */
switch (L->bf)
{ /* 检查T的左子树的平衡度,并作对应平衡处理 */
case LH: /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
(*T)->bf = L->bf = EH;
R_Rotate(T);
break;
case RH: /* 新结点插入在T的左孩子的右子树上。要作双旋处理 */
Lr = L->rchild; /* Lr指向T的左孩子的右子树根 */
switch (Lr->bf)
{ /* 改动T及其左孩子的平衡因子 */
case LH: (*T)->bf = RH;
L->bf = EH;
break;
case EH: (*T)->bf = L->bf = EH;
break;
case RH: (*T)->bf = EH;
L->bf = LH;
break;
}
Lr->bf = EH;
L_Rotate(&(*T)->lchild); /* 对T的左子树作左旋平衡处理 */
R_Rotate(T); /* 对T作右旋平衡处理 */
}
}
/* 对以指针T所指结点为根的二叉树作右平衡旋转处理, */
/* 本算法结束时,指针T指向新的根结点 */
void RightBalance(BiTree *T)
{
BiTree R, Rl;
R = (*T)->rchild; /* R指向T的右子树根结点 */
switch (R->bf)
{ /* 检查T的右子树的平衡度。并作对应平衡处理 */
case RH: /* 新结点插入在T的右孩子的右子树上。要作单左旋处理 */
(*T)->bf = R->bf = EH;
L_Rotate(T);
break;
case LH: /* 新结点插入在T的右孩子的左子树上,要作双旋处理 */
Rl = R->lchild; /* Rl指向T的右孩子的左子树根 */
switch (Rl->bf)
{ /* 改动T及其右孩子的平衡因子 */
case RH: (*T)->bf = LH;
R->bf = EH;
break;
case EH: (*T)->bf = R->bf = EH;
break;
case LH: (*T)->bf = EH;
R->bf = RH;
break;
}
Rl->bf = EH;
R_Rotate(&(*T)->rchild); /* 对T的右子树作右旋平衡处理 */
L_Rotate(T); /* 对T作左旋平衡处理 */
}
}
/* 若在平衡的二叉排序树T中不存在和e有同样关键字的结点,则插入一个 */
/* 数据元素为e的新结点。并返回1,否则返回0。若因插入而使二叉排序树 */
/* 失去平衡,则作平衡旋转处理。布尔变量taller反映T长高与否。 */
Status InsertAVL(BiTree *T, int e, Status *taller)
{
if (!*T)
{ /* 插入新结点。树“长高”,置taller为TRUE */
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e; (*T)->lchild = (*T)->rchild = NULL; (*T)->bf = EH;
*taller = TRUE;
}
else
{
if (e == (*T)->data)
{ /* 树中已存在和e有同样关键字的结点则不再插入 */
*taller = FALSE; return FALSE;
}
if (e < (*T)->data)
{ /* 应继续在T的左子树中进行搜索 */
if (!InsertAVL(&(*T)->lchild, e, taller)) /* 未插入 */
return FALSE;
if (*taller) /* 已插入到T的左子树中且左子树“长高” */
switch ((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高。须要作左平衡处理 */
LeftBalance(T); *taller = FALSE; break;
case EH: /* 原本左、右子树等高,现因左子树增高而使树增高 */
(*T)->bf = LH; *taller = TRUE; break;
case RH: /* 原本右子树比左子树高,现左、右子树等高 */
(*T)->bf = EH; *taller = FALSE; break;
}
}
else
{ /* 应继续在T的右子树中进行搜索 */
if (!InsertAVL(&(*T)->rchild, e, taller)) /* 未插入 */
return FALSE;
if (*taller) /* 已插入到T的右子树且右子树“长高” */
switch ((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高。现左、右子树等高 */
(*T)->bf = EH; *taller = FALSE; break;
case EH: /* 原本左、右子树等高,现因右子树增高而使树增高 */
(*T)->bf = RH; *taller = TRUE; break;
case RH: /* 原本右子树比左子树高,须要作右平衡处理 */
RightBalance(T); *taller = FALSE; break;
}
}
}
return TRUE;
}
int main(void)
{
int i;
int a[10] = { 30,22,34,7,36,28,29,26,24};
BiTree T = NULL;
Status taller;
for (i = 0; i < 10; i++)
{
InsertAVL(&T, a[i], &taller);
}
printf("本例子建议断点跟踪查看平衡二叉树结构");
return 0;
}
1.AVL插入代码(InsertAVL)
注意1:*taller是个指针,一处改,处处改;
因为上层树是否长高取决于下层树是否长高,所以下层树若长高;
比如:如果下层某树结点C仅仅从RH(插入左结点后)变为了EH(没长高),那么此时就会令:*taller=FALSE;
那么因为是递归调用,故C的上层所有树在接收递归返回taller时,接收的全为FALSE,也就是说:底层树没长高,那么依赖于此底层树的上层树全部不会长高!
![](https://i-blog.csdnimg.cn/blog_migrate/8ed452fa19f9126fdcc48b25b544a5e8.png)
注意2:插入key时递归调用InsertAVL,层层返回,每回*T要注意是哪层;
因为是递归调用,每次发现比现有值大(小)时,就递归调用InsertAVL进入其右(左)子树,直到:
①进入不存在的叶子节点执行插入操作
②找到已有值与插入值相等,返回FALSE,FALSE
执行插入操作后,需要层层返回,每返回上一层:
对该层原先的bf进行检查并修改(因插入一个New结点带来的变化)->并决定该层是否长高,再返回到上上一层
如此循环!
注意3:调用左右平衡函数时,bf值一定是已经更新后的值
因为是递归调用,所以插入一个结点后变的不平衡,需要调用左平衡处理函数或右平衡处理函数时,一定是:
①对其进行操作的一定是最小不平衡树(因为是递归调用,层层返回,若发现不平衡需要调用左右平衡函数时,一定是从下往上刚刚发现的,也就是最小的那个不平衡树)
②该不平衡树下的每个结点的bf值都已经更新为插入New结点后的bf值(因为递归调用到不平衡树根节点时,其下层一定递归调用完了,且进行了bf检查和修改)
2.左平衡处理函数(LeftBalance)
根据前面的代码,可以知道以下问题:
Q1:什么时候会调用左子树?
A1:调用左平衡处理的T结点之前就是LH,现在又往其左子树的下方(左或右都可以)插入了一个New结点导致左子树长高了(返回*taller=true),从而T结点子树也长高了(上层树长高取决于下层树),T本来就是LH,它的左结点又长高了,那肯定不平衡了,故需要调用左平衡处理函数。
Q2:调用左平衡处理函数一定是New结点插入在左边吗?
A2:当然不是,调用左平衡处理函数的本质是因为T的左结点L长高了,T的左结点L长高可以是插入L的右结点让它长高,也可以插入左结点让它长高;
只要是L长高了,不论哪侧高,都是T的左结点(左侧)高了,因此T的bf都会左边加一(左高了)。
如插入以下右结点也会调用LeftBalance函数:
![](https://i-blog.csdnimg.cn/blog_migrate/cb49c7480e04afa1525cf384b62d94ba.png)
![](https://i-blog.csdnimg.cn/blog_migrate/daf3ad339970cd867e832f97da944cfc.png)
Q3:调用左平衡处理函数传入的不平衡树各结点的bf值是插入New结点后的更新值,对吗?
A3:对,因为我们是递归调用,当需要调用左平衡处理函数时,说明已经递归返回到了T结点,因此T结点以下的结点的bf值全部都在递归返回时已经检查并作出更新了。
只要是L长高了,不论哪侧高,都是T的左结点(左侧)高了,因此T的bf都会左边加一(左高了)。
明白了以上三个问题,AVL树的左平衡处理函数的理解简直不要太简单!
情况一:插入New结点后,T的左结点L(T->lchild)为LH,从而导致T不平衡
即:
![](https://i-blog.csdnimg.cn/blog_migrate/5fe14131f2c76749e75f169e55560500.png)
![](https://i-blog.csdnimg.cn/blog_migrate/eb7ac5804afb0f29b6a2f4bad581282f.png)
可以看出,不论是哪种情况,只要是其T的左子树LH=1,其本质都是LL型,因此,一个单右旋操作即可重新恢复平衡性。
对应代码:
//L=(*T)->lchild; //将T的左子树赋为L
//LH=1,RH=-1,EH=0; //三种平衡因子bf
//检查T的左子树bf值,若为1,则做单右旋处理
switch(L->bf)
{
//T->lchild->bf若为1,则插入到了T的左子树的左子树
//本质为LL型,因此做单右旋处理
case LH:
R_Rotate(T); //单右旋处理
(*T)->bf=L->bf=EH; //恢复相应的bf值
break;
}
情况二:T的左子树平衡因子是RH
插入New结点后,T的左子树L的平衡因子是RH,那么其New结点一定是插入到了L的右侧。
那么:新的New结点插入其本质是LR型插入,需要进行双旋处理
双旋处理后,我们需要恢复相对应的bf值,令Lr=L->rchild,因此又可细分为:
①Lr->bf=LH;
![](https://i-blog.csdnimg.cn/blog_migrate/4e03d4bb70b1ccc2bb28136686bf1014.png)
进行双旋调整后,相应的:
T->bf为RH;
L->bf为EH;
Lr->bf为RH;
![](https://i-blog.csdnimg.cn/blog_migrate/e8d64dfc8ce0bbd040e595d6b565162f.png)
对应代码:
//检查T的左子树bf值,若为-1,则做双旋处理
switch(L->bf)
{
case RH:
Lr=L->rchild; //L的右子树赋为Lr
//检查Lr的bf情况,作相应的bf恢复调整
switch(Lr->bf)
{
case LH:
(*T)->bf=RH; //调整T的bf值
L->bf=EH; //调整L的bf值
}
Lr->bf =EH; //调整Lr的bf值
L_Rotate(&(*T)->lchild); //双旋第一旋:对T的左子树左旋处理
R_Rotate(T); //双旋第二旋:对T进行右旋处理
}
②Lr->bf=RH;
![](https://i-blog.csdnimg.cn/blog_migrate/d33bf8c4dd66a69d18fadf28ec2e1ba2.png)
对应代码:
//检查T的左子树bf值,若为-1,则做双旋处理
switch(L->bf)
{
case RH:
Lr=L->rchild; //L的右子树赋为Lr
//检查Lr的bf情况,作相应的bf恢复调整
switch(Lr->bf)
{
case RH:
L->bf =LH; //调整T的bf值
(*T)->bf=EH; //调整L的bf值
break;
}
Lr->bf =EH; //调整Lr的bf值
L_Rotate(&(*T)->lchild); //双旋第一旋:对T的左子树左旋处理
R_Rotate(T); //双旋第二旋:对T进行右旋处理
}
③Lr->bf=EH;
![](https://i-blog.csdnimg.cn/blog_migrate/431869dd829fa3164efad89d7ffe9343.png)
对应代码:
//检查T的左子树bf值,若为-1,则做双旋处理
switch(L->bf)
{
case RH:
Lr=L->rchild; //L的右子树赋为Lr
//检查Lr的bf情况,作相应的bf恢复调整
switch(Lr->bf)
{
case EH:
L->bf=(*T)->bf=EH; //调整T、L的bf值
break;
}
Lr->bf =EH; //调整Lr的bf值
L_Rotate(&(*T)->lchild); //双旋第一旋:对T的左子树左旋处理
R_Rotate(T); //双旋第二旋:对T进行右旋处理
}
完整代码:
//检查T的左子树bf值,若为1,则进行单右旋处理;若为-1,则做双旋处理;
switch(L->bf)
{
//T->lchild->bf若为1,则插入到了T的左子树的左子树
//本质为LL型,因此做单右旋处理
case LH:
R_Rotate(T); //单右旋处理
(*T)->bf=L->bf=EH; //恢复相应的bf值
break;
//T->lchild->bf若为-1,则插入到了T的左子树的右侧
//本质为LR型,因此做双旋处理
case RH:
Lr=L->rchild; //L的右子树赋为Lr
//检查Lr的bf情况,作相应的bf恢复调整
switch(Lr->bf)
{
case LH:
(*T)->bf=RH; //调整T的bf值
L->bf=EH; //调整L的bf值
break;
case RH:
L->bf =LH;
(*T)->bf=EH;
break;
case EH:
L->bf=(*T)->bf=EH;
break;
}
Lr->bf =EH; //调整Lr的bf值
L_Rotate(&(*T)->lchild); //双旋第一旋:对T的左子树左旋处理
R_Rotate(T); //双旋第二旋:对T进行右旋处理
}