平衡二叉树,AVL树之代码篇

时间:2023-11-10 11:46:26

  看完了第一篇博客,相信大家对于平衡二叉树的插入调整以及删除调整已经有了一定的了解,下面,我们开始介绍代码部分。

  首先,再次提一下使用的结构定义

 typedef char KeyType;            //关键字
typedef struct MyRcdType //记录
{
KeyType key;
}RcdType,*RcdArr;
typedef enum MyBFStatus //为了方便平衡因子的赋值,这里进行枚举
{ //RH,EH,LH分别表示右子树较高,左右子树等高,左子树较高
RH,EH,LH
}BFStatus;
typedef struct MyBBSTNode //树结点类型定义
{
RcdType data; //数据成员
BFStatus bf; //平衡因子
struct MyBBSTNode *lchild,*rchild; //左右分支
}BBSTNode,*BBSTree;

结构定义

  1.       旋转

  旋转是平衡二叉树的基础。所以我们首先介绍。先看具体代码。RRotate的作用就是以*T为根结点的二叉树向右旋转,LRotate就是向左旋转。

  参数说明:*T为待旋转子树的根结点。

 BBSTree RRotate(BBSTree *T)
{
BBSTree lchild;
lchild = (*T)->lchild;
(*T)->lchild = lchild->rchild;
lchild->rchild = (*T);
(*T) = lchild;
}
void LRotate(BBSTree *T)
{
BBSTree rchild;
rchild = (*T)->rchild;
(*T)->rchild = rchild->lchild;
rchild->lchild = *T;
*T = rchild;
}

左右旋转

  2.    失衡调整

  在前一篇博客我们已经知道,失衡的情况主要包括了两种,左子树过高与右子树过高。我们按其对代码进行划分,首先介绍左子树过高的情况,我们称为左平衡处理

  在这里,不得不再提一下,我们将左失衡,即做左子树过高的情况分为了三种,插入新的结点时,可能出现的情况为LL与LR,删除的时候可能出现的情况为LL,LR,LE。假如不明白为何LE只出现在删除的时候出现,请查看上一篇博客的插入调整与删除调整部分的内容。

  函数先行条件:*T为根的树为不平衡子树。

  参数说明:*T为不平衡子树的根结点

  函数大致说明:首先根据LL,LR,LE对不平衡树进行划分,设定旋转后的最终平衡因子,再对*T进行相应旋转。例如,LL型的不平衡树,最后为*T的左孩子作为根结点,首先设定为最终的值,*T的左孩子的平衡因子为EH,初始根的平衡因子也为EH。

void LeftBalance(BBSTree *T)
{
BBSTree lchild,rchild;
lchild = (*T)->lchild;
switch (lchild->bf)
{
case EH:
(*T)->bf = lchild->bf = LH;
lchild->bf = RH;
RRotate(T);
break;
case LH:
(*T)->bf = lchild->bf = EH;
RRotate(T);
break;
case RH:
rchild = lchild->rchild;
switch (rchild->bf)
{
case LH:
(*T)->bf = RH; lchild->bf = EH; break;
case RH:
(*T)->bf = EH; lchild->bf = LH; break;
case EH:
(*T)->bf = EH; lchild->bf = EH;
}
rchild->bf = EH;
LRotate(&((*T)->lchild));
RRotate(T);
break;
}
}

左平衡调整

 void RightBalance(BBSTree *T)
{
BBSTree rchild,lchild;
rchild = (*T)->rchild;
switch (rchild->bf)
{
case RH:
(*T)->bf = rchild->bf = EH;
LRotate(T);
break;
case EH:
(*T)->bf = RH;
rchild->bf = LH;
LRotate(T);
break;
case LH:
lchild = rchild->lchild;
switch (lchild->bf)
{
case LH:
rchild->bf = RH; (*T)->bf = EH;
break;
case RH:
rchild->bf = EH; (*T)->bf = LH;
break;
case EH:
rchild->bf = EH; (*T)->bf = EH;
break;
}
lchild->bf = EH;
RRotate(&((*T)->rchild));
LRotate(T);
break;
}
}

右平衡调整

  3.   插入新的结点

  说明:在插入新的结点的时候,我们使用一个taller的变量来记录树的高度是否变化。默认认为树的高度是有增加的。我们在插入新的结点后,首先判断树的高度是否增加了,假如树的高度没有变化,不必进行如何操作。当树的高度增加时,我们就考虑是否需要对树的进行平衡调整。假如原本根的平衡因子为LH,而插入点又在左子树上,并且子树的高度变高了的时候,我们就要进行左平衡处理。相对的,假如原本根的平衡因子为RH,而插入点又在右子树上,并且子树的高度变高了的时候,我们就要进行右平衡处理。而我们又知道,只需要对最小失衡树进行平衡调整,所以调整后要将taller置为FALSE

  参数说明:*T,待插入的平衡二叉树

         e,带插入的新的结点的值

         taller,*T的子树的高度是否变高的标志。

Status InsertAVL(BBSTree *T,RcdType e,Status *taller)
{
if(!(*T)) //新建一个节点
return CreatBBSTNode(T,e);
else if(e.key == (*T)->data.key)
{
*taller = FALSE;
return TRUE;
}
if(e.key < (*T)->data.key) //插入到左子树
{
Status sign = InsertAVL(&(*T)->lchild,e,taller);
if(FALSE == sign || OVERFLOW == sign)
return FALSE;
if(TRUE == *taller)
{
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 //插入到了右子树
{
Status sign = InsertAVL(&(*T)->rchild,e,taller);
if(FALSE == sign || OVERFLOW == sign)
return FALSE;
if(TRUE == *taller)
{
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;
}

插入新的结点

  4.   删除

  说明:平衡二叉树也是一棵二叉查找树,所以其删除操作与二叉查找树是一致的。只是我们需要进行平衡处理。我们使用bfChild记录待删除结点的的子树的根结点的原平衡因子。新的平衡因子由子树的根结点的bf成员进行记录。当待删除的结点处于当前节点的左分支上时,删除结点后,我们调用DelLeftCase设定树的平衡因子以及对树进行调整。当待删除的结点处于当前节点的右分支上时,删除结点后,我们调用DelRightCase设定树的平衡因子以及对树进行调整。而我们需要对结点的平衡因子进行重新设定,只有在子树的高度有所降低时进行。而子树的高度降低,对应着Del***Case函数中的子树是否变为NULL或者子树的平衡因子从LH或者RH变为EH。

  参数说明:*T为待进行调整的子树的根结点

       bfChild为*T的左孩子在删除结点前的平衡因子

 //参数说明:*T为待进行调整的子树的根结点
//bfChild为*T的右孩子在删除结点前的平衡因子
void DelLeftCase(BBSTree *T,int bfChild)
{
//当bf为-1或1变为0,或者孩子为空时说明子树高降低
if((!(*T)->lchild) || (EH != bfChild && EH == (*T)->lchild->bf))
{
switch ((*T)->bf)//左子树树高降低
{
case EH:
(*T)->bf = RH;
break;
case LH:
(*T)->bf = EH;
break;
case RH: //原本右子树比较高
RightBalance(T);
break;
}
}
} void DelRightCase(BBSTree *T,int bfChild)
{
//当bf为LH或RH变为EH,或者孩子为空时说明子树高降低
if((!(*T)->rchild) || (EH != bfChild && EH == (*T)->rchild->bf))
{
switch ((*T)->bf)
{
case EH:
(*T)->bf = LH;
break;
case RH:
(*T)->bf = EH;
break;
case LH: //原本左子树比较高
LeftBalance(T);
break;
}
}
}
BBSTree DeleteNode(BBSTree *T,KeyType key)
{
int bfChild;
if(*T)
{
if((*T)->data.key > key)
{
bfChild = (*T)->lchild->bf;
(*T)->lchild = DeleteNode(&(*T)->lchild,key);
DelLeftCase(T,bfChild);
}
else if((*T)->data.key < key)
{
bfChild = (*T)->rchild->bf;
(*T)->rchild = DeleteNode(&(*T)->rchild,key);
DelRightCase(T,bfChild);
}
else//当前节点就是要删除的节点
{
if((*T)->lchild) //*T不是叶子结点并且具有直接前驱
{
BBSTree farRight = GofarRight((*T)->lchild);
(*T)->data = farRight->data;
//可以确定,删除的节点为当前节点的左子树的某一个节点
(*T)->lchild = DeleteNode(&(*T)->lchild,farRight->data.key);
DelLeftCase(T,bfChild);
}
else if((*T)->rchild) //*T不是叶子结点并且具有直接后驱
{
BBSTree farLeft = GofarLeft((*T)->rchild);
(*T)->data = farLeft->data;
(*T)->rchild = DeleteNode(&(*T)->rchild,farLeft->data.key);
DelRightCase(T,bfChild);
}
else //*T是叶子结点
{
free(*T);
*T = NULL;
}
}
}
return (*T);//包含了返回NULL与正常的当前节点
}

删除

  假如您看了图解篇以及代码篇,或许对AVL有了一定的了解。不过还是建议将所有的代码自己实现一次。下面附上完整测试代码的下载链接:http://yunpan.cn/cwUVwIYbPwsC8  访问密码 4302。

  测试代码大概解释:使用#表示空结点。代码先自动生成一棵平衡二叉树,首先删除一个结点,用户输入Y就会再次删除平衡二叉树的一个结点。每次删除完了会将树进行输出。输入的格式为A【B,C】的格式,A代表根结点,B为A的左孩子,C为A的右孩子。输入其他字符可以查看最终结果。

  PS:假如代码无法编译,请检查随机函数在你的编译器是否可用。

  欢迎各位朋友批评改正,谢谢。