平衡二叉树
前言:本文对于平衡二叉树的原理讲解不是很透彻,建议先参考别的文章,大概了解整个过程再来仔细阅读此文。
另:为了方面实现,使用了C++引用参数。若要转化为C语言,使用指针即可。
基本概念
定义
平衡二叉查找树( Balanced Binary Sort Tree ) 简称平衡二叉树。我们平时说的平衡二叉树实际上指的是高度平衡的二叉树。亦可以称为 AVL 树。
性质
任何一个结点的左子树和右子树都是平衡二叉树,且左子树和右子树的高度之差的绝对值不超过1。
平衡因子:该结点的左子树高度减去其右子树的高度。所有结点的平衡因子只可能为-1、0、1。如果不是以上三个数,则这棵树失去了平衡。
平衡二叉树是一种特殊的二叉排序树。即该结点的值大于其左孩子的值,且小于其右孩子的值。
图示:
基本操作
结构定义
#define TRUE 1
#define FALSE 0
#define LH +1 //左高
#define EH 0 //等高
#define RH -1 //右高
typedef int Status;
typedef int KeyType;
//RcdType结构
typedef struct {
KeyType key;
}RcdType;
//BBST结构
typedef struct BBSTNode{
RcdType data;
int bf; //平衡因子
struct BBSTNode *lchild, *rchild;
}BBSTnode,*BBSTree;
平衡二叉树的失衡与调整
基础认识
插入或删除一个结点后,从该结点的位置起向上寻找第一个不平衡的结点( 平衡因子 bf 变成了 -2 或 2 ),以确定该树是否失衡。若找到,则以该结点为根的子树称为最小失衡子树。
调整平衡的过程实际上是由下往上进行平衡因子的更新与保持树平衡的过程。
失衡情况及调整方法
可将最小失衡子树的调整操作归纳以下情况:
- LL型:在最小失衡子树的左孩子的左子树上插入了新的结点
- RR型:在最小失衡子树的右孩子的右子树上插入了新的结点
- LR型:在最小失衡子树的左孩子的右子树上插入了新的结点
- RL型:在最小失衡子树的右孩子的左子树上插入了新的结点
各种情况处理方法
- LL型:右旋处理
- RR型:左旋处理
- LR型:先左旋再右旋
- RL型:先右旋再左旋
图示:
LL型
RR型
LR型
RL型
代码( 左旋与右旋 )
//右旋调整
void R_Rotate(BBSTree &p){
BBSTree lc = p->lchild;
p->lchild = lc->rchild;
lc->rchild = p;
p = lc;
}
//左旋调整
void L_Rotate(BBSTree &p){
BBSTree rc = p->rchild;
p->rchild = rc->lchild;
rc->lchild = p;
p = rc;
}
代码( 左平衡与右平衡处理 )
- 左平衡处理是对左子树引起的不平衡的处理。
- 右平衡处理是对右子树引起的不平衡的处理。
- 左平衡处理与右平衡处理是对结点的平衡因子更新与树的旋转的函数。(具体如何更新平衡因子不作具体解释)
- 左平衡处理与右平衡其实是一个相反的过程,弄懂一个,另外一个反着写即可。
//左平衡处理
void LeftBalance(BBSTree &T){
BBSTree lc,rd;
lc = T->lchild;
switch(lc->bf){ //检查T左子树的平衡因子
case LH: //LL型
T->bf = lc->bf = EH;
R_Rotate(T); //右旋
break;
case RH: //LR型
rd = lc->rchild;
switch(rd->bf){ //修改T和其左孩子的平衡因子
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); //对T左子树左旋
R_Rotate(T); //对T右旋
break;
case EH: //只有在删除时才有这个情况
T->bf = LH;
lc->bf = RH;
R_Rotate(T);
break;
}
}
//右平衡处理
void RightBalance(BBSTree &T){
BBSTree lc,rd;
rd = T->rchild;
switch(rd->bf){ //检查T右子树的平衡因子
case RH: //RR型
T->bf = rd->bf = EH;
L_Rotate(T);
break;
case LH: //RL型
lc = rd->lchild;
switch(lc->bf){ //修改T和其右孩子的平衡因子
case LH:
T->bf = EH;
rd->bf = RH;
break;
case EH:
T->bf = rd->bf = EH;
break;
case RH:
T->bf =LH;
rd->bf = EH;
break;
}
lc->bf = EH;
R_Rotate(T->rchild); //对右子树右旋
L_Rotate(T); //对T左旋
break;
case EH: //只有在删除时才有这个情况
T->bf = RH;
rd->bf = LH;
L_Rotate(T);
break;
}
}
插入
插入基本思想
递归查找到插入的位置,直到查找到的指针为NULL时,就可以进行插入操作。具体就是为NULL时,新建一个结点,替代原本NULL的位置。若树中已存在相同值的点,则无法插入。
代码
插入成功时返回TRUE,否则返回FALSE
// T为被插入的树,e为待插入的值,taller用于判断树高度是否改变。初始调用时应赋为FALSE
Status InsertAVL(BBSTree &T, RcdType e, Status &taller){
if(NULL==T){
T = (BBSTree)malloc(sizeof(BBSTnode));
T->data = e; //为新节点赋值
T->bf = EH;
T->lchild = NULL;
T->rchild =NULL;
taller = TRUE;
}
else if(e.key == T->data.key){ //重复节点 , 插入失败
taller = FALSE;
return FALSE;
}
else if(e.key < T->data.key){ //插入到左子树
if(FALSE == InsertAVL(T->lchild, e, taller)) //未插入
return FALSE;
if(taller == TRUE){ //树增高
switch(T->bf){
case LH: //原左高,作左平衡处理
LeftBalance(T);
taller = FALSE;
break;
case EH: //原等高,变左高
T->bf = LH;
taller = TRUE;
break;
case RH: //原右高,变等高
T->bf = EH;
taller = FALSE;
break;
}
}
}
else{ //插入到右子树
if(FALSE == InsertAVL(T->rchild, e, taller)) //未插入
return FALSE;
if(taller == TRUE){ //树增高
switch(T->bf){
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;
}
删除
删除基本思想
- 若删除的结点为叶子结点,直接free掉就可以了。即将其父节点对这个结点的指向修改为NULL。
- 若删除的结点为非叶子结点,则在这个结点的左子树中找到一个最大值 (这个最大值肯定处于叶子位置)。交换这两个结点的值,并将待删除的结点删去,此时就转化为了第一种情况。具体情况如下:
- 结点只有左子树或者只有右子树,则使用其左子树或右子树直接替代即可。
- 同时有左子树与右子树,则用其左子树中最大值替代这个结点。
- 删除时平衡因子的更新及树的旋转是插入的逆过程。但要注意的是,删除时的左平衡与右平衡处理中多了EH的情况。因为删除时有可能待平衡的子树平衡因子为EH,而这种情况在插入时不可能出现。
代码
删除成功返回TRUE,否则返回FALSE。
Status DeleteAVL(BBSTree &T, KeyType e, Status &taller){
if(!T)
return FALSE;
if(e < T->data.key){ //左树查找
if( FALSE == DeleteAVL(T->lchild,e,taller))
return FALSE;
if(TRUE == taller){
switch(T->bf){
case LH: //原左高,变等高
T->bf = EH; taller = TRUE;
break;
case EH: //原等高,变右高
T->bf = RH; taller = FALSE;
break;
case RH: //原右高,右平衡处理
if(T->rchild!=NULL&&T->rchild->bf==EH)
taller = FALSE;
else
taller = TRUE;
RightBalance(T);
break;
}
}
}
else if(e > T->data.key){ //右树查找
if( FALSE == DeleteAVL(T->rchild, e, taller))
return FALSE;
if(TRUE == taller){
switch(T->bf){
case LH: //原左高,左平衡处理
if(T->lchild!=NULL && T->lchild->bf==EH)
taller = FALSE;
else
taller = TRUE;
LeftBalance(T);
break;
case EH: //原等高,变左高
T->bf = LH; taller = FALSE;
break;
case RH: //原右高,变等高
T->bf = EH; taller = TRUE;
break;
}
}
}
else{ //查找到了
if(T->lchild && !T->rchild){ //只有左子树
BBSTree tmp = T;
T = T->lchild;
free(tmp);
tmp = NULL;
taller = TRUE;
}
else if(!T->lchild && T->rchild){ //只有右子树
BBSTree tmp = T;
T = T->rchild;
free(tmp);
tmp = NULL;
taller = TRUE;
}
else if(T->lchild && T->rchild){ //有左右子树
BBSTree LF = T->lchild;
while(LF->rchild) //寻找左子树最大节点
LF = LF->rchild;
KeyType Lmax = LF->data.key;
DeleteAVL(T->lchild, Lmax , taller);
T->data.key = Lmax;
if(TRUE == taller){
switch(T->bf){
case LH: //原左高,变等高
T->bf = EH; taller = TRUE;
break;
case EH: //原等高,变右高
T->bf = RH; taller = FALSE;
break;
case RH: //原右高,右平衡处理
if(T->rchild!=NULL&&T->rchild->bf==EH)
taller = FALSE;
else
taller = TRUE;
RightBalance(T);
break;
}
}
}
else { //叶子节点
free(T);
T=NULL;
taller = TRUE;
}
}
return TRUE;
}
平衡判断算法
递归判断每一个结点的平衡因子即可。
代码
// 判断是否平衡
int Balance_BF(BBSTree T){
if (!T)
return 0;
int depthLeft, depthRight;
depthLeft = Balance_BF(T->lchild);
depthRight = Balance_BF(T->rchild);
if(T->bf != depthLeft - depthRight || depthLeft - depthRight > 1 || depthLeft - depthRight < -1){
printf("\n%d 不平衡",T->data.key);
}
return 1 + (depthLeft > depthRight ? depthLeft : depthRight);
}
解释:使用了 printf 意在找出不平衡的点,修改 if 内容就可以自定义输出。
后记
本文只是博主的一篇学习笔记。可能不太适合对于过程不了解的人。对于各种过程可以参考以下博客。