emmm,该来的总会来的,终于复习到了平衡二叉树部分。在写这份总结之前平衡二叉树操作是一个噩梦,之后,这是我知识库里的一个知识点,嗯,该把他供到“祠堂”里边去了。(当时没写完)
目录
《数据结构与算法》——树与二叉树之平衡二叉树(AVL)总结
定义
方法实现
平衡二叉树的插入
方法实现
参考文献
复习一下昨天学的东西——二叉排序树,我们在建立二叉排序树的时候,我们就是一味地向树中添加结点,已经插入的结点位置便不会再改变。如果我们输入一个序列:“1 2 3 4 5 6 7 8 9 ”,由此建立起的二叉排序树形状为右撇一条线,此时它的查找优势就完全失去了,那么这个二叉排序树就是极其失败的。就像前面复习的以完全有序的初始情况进行快速排序的情况一个意思。
如何解决这个问题呢?把序列平均分到左右两个子树上即可,(此处的平均指的是大体平均,不是完全平均),在画出的树形图上表现出来的就是每个结点的左右子树的高度差的绝对值不超过1。在此,我们定义,将左子树和右子树的高度差定义为该结点的平衡因子,那么它的数值只能是,-1、0、1三种情况。所以我们可以把一棵平衡二叉树描述为:
它或者是一棵空树,或者是具有以下性质的二叉树:
1.左右子树的高度差不超过1;
2.左右子树各为平衡二叉树。
注意此处是普通的二叉树,不一定非得是二叉排序树。
下面将对平衡二叉树的插入和删除两个方法进行总结和实现,其中它的建立就是不断向其中插入结点,销毁就是不停地进行删除结点。以二叉排序树为例。
二叉排序树保证平衡的思想:每当在树中插入(删除)一个结点时,首先要检查其插入路径上的结点是否因为此次操作导致了不平衡。若导致了不平衡,则找到路径上距离插入点最近的平衡因子大于1的结点A,再以结点A为根结点的子树,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之达到平衡。
书上将调整方法分为了4种,在看这四种情况之前,我们先看一下,有哪几种插入位置。如下表(图):平衡因子——结点——高度
1——A |
|||
0——B |
AR——H |
||
BL——H |
BR——H |
|
|
对于这种树来讲,可以在BL中插值,可以在BR上进行插值,可以在AR上进行插值。
在AR上插值,导致的结果是AR的高度+1或者不变,最终导致A点平衡因子-1或不变;
在BL上插值,导致的结果是BL的高度+1或者不变,最终导致A点平衡因子+1或不变;
在BR上插值,导致的结果是BR的高度+1或者不变,最终导致A点平衡因子+1或不变;
除了第一种情况,其余两种情况均有可能导致打破树的平衡性。
我们只考虑以下两种情况:
在BL上插值,导致的结果是BL的高度+1,最终导致A点平衡因子+1;
在BR上插值,导致的结果是BR的高度+1,最终导致A点平衡因子+1;
把树形进行左右对称变换,又将出现两种情况,即总共有四种情况。
则它们从左到右插入位置依次代表着LL,LR,RL,RR四种不同的旋转方式。
LL平衡旋转(右单旋转)
以上图来讲,因为在A点的左孩子的左节点上添加了一个结点导致了以A为根结点的树失去了平衡,平衡因子变成了2,需要一次向右的旋转。何为向右的旋转?就是将A作为B的右孩子结点,B的原右孩子结点作为A的新左结点存在。如下图所示。
0——B |
|||
BL——H+1 |
0——A |
||
|
|
BR——H |
AR——H |
注意哈,是以A为根结点的树,并不代表A结点为整棵树的根结点。
RR平衡旋转(左单旋转)
以上图来讲,因为在A点的右孩子的右节点上添加了一个结点导致了以A为根结点的树失去了平衡,平衡因子变成了-2,需要一次向左的旋转。何为向左的旋转?就是将A作为B的左孩子结点,B的原左孩子结点作为A的新右结点存在。如下图所示。
0——B |
|||
0——A |
BR——H+1 |
||
AL——H |
BL——H |
|
|
☆LR平衡旋转(先左后右双旋转)
原始表如下
1——A |
|||||||
0——B |
AR——H |
||||||
BL——H |
BR——H |
|
|
||||
|
|
|
|
|
|
|
|
插入后,调整前
2——A |
|||||||
-1——B |
AR——H |
||||||
BL——H |
1——C |
|
|
||||
|
|
CL——H |
CR——H-1 |
|
|
|
|
这个调整在两类四种里边是最复杂也是最难理解的一种方式,以上图来讲,因为在A点的左孩子的右节点上添加了一个结点导致了以A为根结点的树失去了平衡,平衡因子变成了2,需要两次旋转。为什么是两次?现在导致不平衡的最根本的原因是在CL中添加了一个结点导致总高度发生了变化,即现在有四棵子树,从小到大排列来讲就是BL、CL、CR、AR——H、H、H-1、H,除此之外还有三个结点B、C、A,对于子树来讲,只要将其设置在同一等级上,其高度差就不会超过一,而三个结点恰好可以组成一棵高度为2的满二叉树,所以将四棵子树作为第三层的根结点,左右高度差就不会超过1,就可以满足平衡二叉树的要求了。
我们可以根据最后的情况来倒推操作过程,注意,我们只能使用左旋和右旋两种操作。
根据前面情况描述,我们知道了左旋和右旋的定义。而此处左旋的对象是以B为根结点的子树,此时操作即是,将B作为C的左孩子结点,C的原左孩子结点作为B的新右结点存在;随后进行右旋操作(此时A的左结点为C),将A作为C的右孩子结点,C的原右孩子结点作为A的新左结点存在。调整后如下图所示。
0——C |
|||||||
0——B |
-1——A |
||||||
BL——H |
CL——H |
CR——H-1 |
AR——H |
||||
|
|
|
|
|
|
|
|
当然上面展示的是在c点的左子树上进行插入的情况,那在c点右子树上进行插入呢?操作可以完全一样,不同的是CL的高度变为H-1,CR的高度变为H。我们的目的只有一个调整它的平衡就可以,那么四个子树高度H、H、H和H-1不论怎么排列,总高度都是一样的。(当然得按照左子树的值都小于右子树的值。)
☆RL平衡旋转(先右后左双旋转)
原始表如下
1——A |
|||||||
AR——H |
0——B |
||||||
|
|
BL——H |
BR——H |
||||
|
|
|
|
|
|
|
|
插入后,调整前
-2——A |
|||||||
AL——H |
-1——B |
||||||
|
|
1——C |
BL——H |
||||
|
|
|
|
CL——H |
CR——H-1 |
|
|
调整后
1——C |
|||||||
0——A |
-1——B |
||||||
AL——H |
CL——H |
CR——H-1 |
BL——H |
||||
|
|
|
|
|
|
|
|
原理和上面的原理完全相同,就是方向不同,应以上面LR平衡旋转为模板理解本部分。
旋转是个啥意思?
一句话太多,看图~
我们通过上面的观察发现,在操作过程中无需比较结点数值的大小,单从该节点的高度和平衡因子两个数值进行操作,又因为该结点的平衡因子是由该结点左右子树的高度差决定的,所以,我们需要对以每个结点为根结点的树的高度或者该结点的平衡因子进行记录,所以更改二叉树结点的定义。如果每个结点均记录其双亲结点,则操作会很方便,因为这本来就是一个老爹变儿子,儿子变老爹的故事。但是在标准叙述中未出现查找其双亲结点这一操作,所以,我们不在其中添加双亲结点。定义如下。
typedef int ElemType;
typedef bool Status;
typedef struct BSTNode {
ElemType data;
int bf;
BSTNode *lchild,*rchild;
} BSTNode,*BSTree;
方法清单:
void R_Rotate(BSTNode &p);//右旋处理
void L_Rotate(BSTNode &p);//左旋处理
void LeftBalance(BSTNode &T);//左旋平衡处理
void RightBalance(BSTNode &T);//右旋平衡处理
Status InsertAVL(BSTNode &T,ElemType e, bool taller);//平衡二叉排序树插入元素
方法实现:(书上的代码+个人注释)
#define LH +1 //左高
#define EH 0 //等高
#define RH -1 //右高
void R_Rotate(BSTNode &p) {//见图一
//右旋处理
BSTNode *lc;
lc = p->lchild;
p->lchild = lc->rchild;
lc->rchild = p;
p = lc;
}//R_Rotate
void L_Rotate(BSTNode &p) {
//左旋处理
BSTNode *rc;
rc = p->rchild;
p->rchild = rc->lchild;
rc->lchild = p;
p = rc;
}//L_Rotate
Status InsertAVL(BSTNode &T,ElemType e, bool taller) { //平衡二叉排序树插入元素
//插入成功返回1,失败为0
//旋转处理,taller反映树是否长高
if(!T) { //空树插入新节点
T = (BSTNode) malloc(sizeof(BSTNode));
T->lchild = NULL;
T->Rchild = NULL;
T->bf = EH;
taller = 1;
} else {//当前树结点不为空,需要查找元素位置
if(T->data==e) { //当前元素相同
taller = 0;
return 0;
}
if(T->data>e) { //向左插入
if(!InsertAVL(T->lchild,e,taller))
return 0;//未插入
if(taller)//左子树长高
switch(T->bf) {//对当前结点的平衡因子变化进行判断
case LH://原左子树高,因左子树又变高即平衡因子变为2,需要对左子树进行左平衡旋转
LeftBalance(T);
taller = 0;
break;
case EH://原两子树一般高,因左子树变高即平衡因子变为1,不需要做平衡旋转
T->bf = LH;
taller = 1;
break;
case RH://原右子树高,因左子树变高即平衡因子变为0,不需要做平衡旋转
T->bf = EH;
taller = 0;
break;
}//switch()
}//if
else {//向右插入
if(!InsertAVL(T->rchild,e,taller))
return 0;//未插入
if(taller)//右子树树高发生变化
switch(T->bf) {//对当前结点平衡因子变化进行判断修改
case LH://原左子树高,因右子树变高致使平衡因子变为0,不需要进行平衡旋转
T->bf = EH;
taller = 0;
break;
case EH://原两子树等高,因右子树变高致使平衡因子变为-1,不需要进行平衡旋转
T->bf = RH;
taller = 1;
break;
case RH://原右子树高,因右子树又变高致使平衡因子变为-2,需要进行右平衡旋转
RightBalance(T);
taller = 0;
break;
}//switch()
}//else
}//else
return 1;
}//InsertAVL
void LeftBalance(BSTNode &T){//此时T的平衡因子为2
//左平衡旋转处理,判断为LL或LR型变化进行平衡
BSTNode *lc;
lc = T->lchild;
switch(lc->bf){//根节点左子树的平衡因子判断
case LH://左孩子节点平衡因子为1,则为L型,进行右单旋
T->bf = EH;
lc->bf = EH;
R_Rotate(T);
break;
case RH://左孩子节点平衡因子为-1,则为LR型
BSTNode *rd;
rd = lc->rchild;
switch(rd->bf){//根节点左孩子的右孩子结点平衡因子进行判断并修改
case LH:T->bf = RH; lc->bf = EH; break;//图四·情况1 注
case EH:T->bf = EH; lc->bf = EH; break;//图四·情况3
case RH:T->bf = EH; lc->bf = LH; break;//图四·情况2
}
rd->bf = EH;
L_Rotate(T->lchild);
R_Rotate(T);
}//switch(lc->bf)
}//LeftBalance
void RightBalance(BSTNode &T){//此时T的平衡因子为-2
//右平衡旋转处理,判断为RR或RL型变化进行平衡
BSTNode *rc;
rc = T->rchild;
switch(rc->bf){//根节点右子树的平衡因子判断
case RH://右孩子节点平衡因子为-1,则为RR型,进行左单旋
T->bf = EH;
lc->bf = EH;
L_Rotate(T);
break;
case LH://右孩子节点平衡因子为1,则为RL型
BSTNode *ld;
ld = rc->rchild;
switch(ld->bf){//根节点右孩子的左孩子结点平衡因子进行判断并修改
case RH:T->bf = LH; lc->bf = EH; break;
case EH:T->bf = EH; lc->bf = EH; break;
case LH:T->bf = EH; lc->bf = RH; break;
}
ld->bf = EH;
R_Rotate(T->rchild);
L_Rotate(T);
}//switch(rc->bf)
}//RightBalance
LeftBalance中对于第二个switch解释如下:
1.①对于情况一,1的左右子树高为H,2的左右子树高为H+1和H+2,4的左子树高为H+1,右子树高为H,5的左右子树高为H+3和H,6的左右子树高均为H-1,在二叉排序树中12456的大小相对位置是不变的,作出五个节点的基本树形图,将4的子树按照大小进行补位,此时有1的左右子树不变,2的右子树为原4左子树高为H+1,,对于2来讲平衡因子为0,即左右平衡,5的左子树为原4右子树高为H,对于5的树高变为H+1,,2和5变为4的做右孩子结点,平衡因子为H+2-(H+1)=1.
2.直接对其进行模拟亦可以得到如上结果。
如果只是为了浅层次了解这种结构算法,建议从参考书上找几个例题,手动模拟再和答案进行比对即可。
严蔚敏,吴伟民. 数据结构(C语言版)[M]. 北京: 清华大学出版社,2013.
如有错误,还请朋友不吝指正。