使用的语言:
C++
(只是为了简化代码而使用了C++的引用参数
的特性,大部分代码都是c语言风格的,并没有使用到C++的高级特性,因此可以将实现代码当成C语言代码进行理解)
1.定义
关于AVL的实现,使用了平衡因子来判断树是否失衡,虽然增加了空间复杂度,但是比起通过判断树的深度要节省时间。结构体的定义如下:
enum Status
{
OK,ERROR,YES,NO
};
#define LH +1
#define EH 0
#define RH -1
typedef int RcdType;
typedef struct BBSTNode{
RcdType data;
int bf;//平衡因子
struct BBSTNode *lchild,*rchild;
} * BBSTree;
2.插入
插入实现
Status InsertAVL(BBSTree &T,RcdType e,Status &taller);
关于平衡二叉树的插入,使用递归实现,具体可分为以下三个步骤:
- 若根节点为空,则以插入的节点为根节点。
- 若插入的节点和根节点相同,则直接返回。
- 如果插入的节点比根节点小,则递归访问根节点的左子树,反之则访问右子树。回到步骤1。
以上为递归调用的基本架子,接下来就是关于树的平衡调节。这里在函数使用了taller这个引用行参,用来记录当前函数
下的树T的子树是否变高
。下面给出插入函数的代码。
//插入
Status InsertAVL(BBSTree &T,RcdType e,Status &taller)
{
//如果为空则以该节点为根创建一棵新的树
if(NULL==T)
{
T=(BBSTree)malloc(sizeof(BBSTNode));
if(NULL==T)return NO;
T->data=e;
T->bf=EH;
taller=YES;
T->lchild=T->rchild=NULL;
}
//如果该节点已经存在,那么就直接返回
else if (e==T->data)
{
taller = NO;
return NO;
}
else if(e<T->data)
{
if(NO==InsertAVL(T->lchild,e,taller))return NO;
if(YES==taller)//树变高了
{
switch(T->bf)
{
//本来左子树就高的话那么就失衡了
case LH:L_Balance(T);taller=NO;break;
case RH:T->bf=EH;taller=NO;break;
case EH:T->bf=LH;taller=YES;break;
}
}
}
else
{
if(NO==InsertAVL(T->rchild,e,taller))return NO;
if(YES==taller)
{
switch(T->bf)
{
case RH:R_Balance(T);taller=NO;break;
case LH:T->bf=EH;taller=NO;break;
case EH:T->bf=RH;taller=YES;break;
}
}
}
return YES;
}
平衡调整函数
通过判断树的平衡因子来判断树是否失衡,依靠taller向上传递变高的信息。插入的代码很简单,关键的难点是在
Status L_D_Balance(BBSTree &T);
Status R_D_Balance(BBSTree &T);
这两个函数,通过这两个函数可以实现树的失衡旋转和平衡因子的调整。由于左右的对称性,因此只需要写出其中一个函数,右边的函数也可以直接得出。
关于失衡调整函数,主要分为两个部分,一个是树的平衡调整,另外则是平衡因子的调整了。
树的平衡调整
下面介绍左边失衡
的情况。调整失衡函数的重点在于对最小失衡子树(即从插入的节点向上寻找到的第一个失衡的节点,这里通过上面的插入函数算法已经找到了)的操作。左子树的失衡分两种情况:(右子树的情况则只需要将L换成R,R换成L理解即可)
- LL型:在最小失衡子树的左孩子的左子树上插入了新节点
- LR型:在最小失衡子树的左孩子的右子树上插入了新节点
(注意这里的判断依据是在子树上插入节点)
关于上述的情况,分别可以使用两种旋转方法进行平衡调整(这里可以结合只有三个节点的情况进行理解),针对LL型,只需要将节点顺时针旋转(右旋)
即可,而对于LR型,则需先将左子树逆时针旋转(左旋)
(因为左子树比其右孩子的值要小),接下来再进行顺时针旋转(右旋)
即可。
这个步骤的实现代码十分的简单
//左旋转
void L_Rotation(BBSTree &T)
{
BBSTree t;
t = T->rchild;
T->rchild = t->lchild;
t->lchild = T;
T = t;
}
//右旋转
void R_Rotation(BBSTree &T)
{
BBSTree t;
t = T->lchild;
T->lchild = t->rchild;
t->rchild = T;
T = t;
}
树的平衡因子调整
光只是调整树的平衡是不行的,还需要调整树的平衡因子。这里先贴一下代码,平衡因子的调整和树的平衡调整结合在一起,通过平衡因子判断树的失衡情况,并通过左/右旋函数调整平衡。
//插入之后树T失衡时的平衡函数
//这里对三个节点进行定义
//ROOT=原来的树T
//RL=树T的左孩子
//RLR=树T的左孩子的右孩子
void L_Balance(BBSTree &T)
{
BBSTree lc,rc;
lc = T->lchild;
//通过判断RL失衡的情况为LL还是LR
switch(lc->bf)
{
//LL type
//这种情况下平衡因子改变的节点只有ROOT和RL
case LH:
T->bf = lc->bf = EH;
break;
//LR type
case RH://右子树高则说明插入的节点在RLR这棵树上
rc = lc->rchild;
//判断RLR的平衡因子
//这种情况下平衡因子发生改变的节点为上述定义的三个节点
switch(rc->bf)
{
//RLR的左子树高,旋转之后该左子树接到了RL的右子树上
//由于RL一定不存在右子树,且左子树高度一定为1,所以RL的平衡因子为EH
//ROOT右旋变成RLR的右子树,因此失去了左子树,并且由于RLR没有右子树,因此ROOT的变成右高
case LH:T->bf=RH;lc->bf=EH;break;
//ROOT这棵树只有三个节点的情况,包括ROOT,即ROOT的右子树为空,左子树高度为2
case EH:T->bf=lc->bf=EH;break;
//这种情况和上面左高的情况类似。
case RH:T->bf=EH;lc->bf=LH;break;
}
rc->bf = EH;//通过调整之后RLR成为新的树根,左右子树等高
//先左旋RL,将其转化成LL情况,再进行右旋
L_Rotation(T->lchild);
break;
}
R_Rotation(T);
}
关于平衡因子的调整,可以参照注释,但画图理解可能会快一点吧。。。
至此关于插入左子树的失衡情况处理这里就说明完毕了。
3.删除
有了插入的基础,删除就会简单一点了,代码基本上没什么变化,主要是删除多了一种插入没有的情况,下面举一个右失衡的情况。
平衡调整函数
如果是按照上面平衡调整的代码,则不会处理右子树平衡因子为EH的情况,这里博主则将删除的平衡调整函数根据插入的函数更改了一下
下面以右失衡为例,平衡因子的调整则只需要考虑EH的情况即可,其他的情况和插入时相同。
//删除之后右子树失衡时的平衡函数
//和左子树的操作是对称相反的
Status R_D_Balance(BBSTree &T)
{
BBSTree lc,rc;
rc = T->rchild;
Status result;
switch(rc->bf)
{
case RH:
T->bf = rc->bf = EH;
result = YES;
break;
case LH:
lc = rc->lchild;
switch(lc->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;
}
lc->bf = EH;
R_Rotation(T->rchild);
result = YES;
break;
case EH:
T->bf=RH;rc->bf=LH;
result = NO;
break;
}
L_Rotation(T);
return result;
}
}
删除实现
基本上是在插入的架子上改的,注意这里找到了要删除的元素之后,会去找删除元素的中序后继(即这颗树上比自己大的第一个节点),然后交换两者的值,继续删除,具体可以看代码。
//删除
Status DeleteAVL(BBSTree &T,RcdType e,Status &shorter)
{
//找不到则返回
if(NULL==T)
{
return NO;
}
else if (e<T->data)
{
if (NO==DeleteAVL(T->lchild,e,shorter))return NO;
if(YES==shorter)
{
//判断祖先节点的高度
switch(T->bf)
{
//如果本来左子树高的话,删除节点之后就应该变矮了
case LH:T->bf=EH;shorter=YES;break;
case EH:T->bf=RH;shorter=NO;break;
case RH:shorter=R_D_Balance(T);break;
}
}
}
else if(e>T->data)
{
if (NO==DeleteAVL(T->rchild,e,shorter))return NO;
if(YES==shorter)
{
switch(T->bf)
{
case RH:T->bf=EH;shorter=YES;break;
case EH:T->bf=LH;shorter=NO;break;
case LH:shorter=L_D_Balance(T);break;
}
}
}
else //找到要删除的节点
{
BBSTree p;
p = T;
//若左右子树都存在,那么寻找左子树的中序后继
if(NULL!=T->lchild && NULL != T->rchild)
{
p = T->lchild;
while (p->rchild != NULL) {
p = p->rchild;
}
//并将后继的值赋给该节点
T->data = p->data;
//接下来通过遍历该节点的左子树,找到后继删除
DeleteAVL(T->lchild,p->data,shorter);
if (shorter==YES)
{
switch(T->bf)
{
case LH:T->bf=EH;shorter=YES;break;
case EH:T->bf=RH;shorter=NO;break;
case RH:shorter=R_D_Balance(T);break;
}
}
}
else //选择左子树或者右子树接上
{
T = (NULL==T->lchild)?T->rchild:T->lchild;
free(p);
shorter = YES;
}
}
return YES;
}
以上则是平衡二叉树的插入删除实现了,具体代码可以看这里