平衡二叉树实现算法

本文详细介绍了AVL树中的平衡因子概念,以及右旋、左旋和针对左/右子树高度不平衡的左平衡旋转处理。重点展示了如何在插入新节点后通过旋转操作保持二叉排序树的平衡,确保查找、插入和删除操作的时间复杂度为O(logn)。
摘要由CSDN通过智能技术生成

代码的讲解如下:  首先是需要改进二叉排序树的结点结构,增加一个bf,用来存储平衡因子。

/*  二叉树的二叉链表结点结构定义  */
/*  结点结构  */
typedef  struct  BiTNode
{
/*  结点数据  */
int  data;              
/*  结点的平衡因子  */
int  bf;                
/*  左右孩子指针  */
struct  BiTNode  *lchild,  *rchild;        
}  
BiTNode,  *BiTree;

然后,对于右旋操作,我们的代码如下。

/*  对以p为根的二叉排序树作右旋处理,  */
/*  处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点  */
void  R_Rotate(BiTree  *P)
{
BiTree  L;
/*  L指向P的左子树根结点  */
L  =  (*P)->lchild;     
/*  L的右子树挂接为P的左子树  */
(*P)->lchild  =  L->rchild;        
L->rchild  =  (*P);
/*  P指向新的根结点  */
*P  =  L;               
}

此函数代码的意思是说,当传入一个二叉排序树P,将它的左孩子结点定义为L,将L的右子树变成P的左子树,再将P改成L的右子树,最后将L替换P成为根结点。这样就完成了一次右旋操作。

左旋操作代码如下。

/*  对以P为根的二叉排序树作左旋处理,  */
/*  处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0  */
void  L_Rotate(BiTree  *P)
{
BiTree  R;
/*  R指向P的右子树根结点  */
R  =  (*P)->rchild;     
/*  R的左子树挂接为P的右子树  */
(*P)->rchild  =  R->lchild;        
R->lchild  =  (*P);
/*  P指向新的根结点  */
*P  =  R;               
}

这段代码与右旋代码是对称的,在此不做解释了。
现在我们来看左平衡旋转处理的函数代码。

#define  LH  +1    
/*  左高  */
#define  EH  0      
/*  等高  */
#define  RH  -1    
/*  右高  */
/*  对以指针T所指结点为根的二叉树作左平衡旋转处理  */
/*  本算法结束时,指针T指向新的根结点  */
void  LeftBalance(BiTree  *T)
{
BiTree  L,Lr;
/*  L指向T的左子树根结点  */
L  =  (*T)->lchild;     
switch  (L->bf){                       
/*  检查T的左子树的平衡度,并作相应平衡处理  */
/*  新结点插入在T的左孩子的左子树上,要作单右旋处理  */
case  LH:               
(*T)->bf  =  L->bf  =  EH;
R_Rotate(T);
break;
/*  新结点插入在T的左孩子的右子树上,要作双旋处理  */
case  RH:               
/*  Lr指向T的左孩子的右子树根  */
Lr  =  L->rchild;       
/*  修改T及其左孩子的平衡因子  */
switch  (Lr->bf)        
{
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;
/*  对T的左子树作左旋平衡处理  */
L_Rotate(&(*T)->lchild);
/*  对T作右旋平衡处理  */
R_Rotate(T);            
}}

首先,我们定义了三个常数变量,分别代表1、0、-1。

1. 第1行表示函数被调用时,传入一个需要调整平衡性的子树T。根据调用时的条件,可以确定当前子树是不平衡状态,且左子树的高度大于右子树的高度。
2. 第4行将T的左孩子赋值给L,这是为了方便后续操作。
3. 第5至第27行是一个分支判断的过程,根据L的平衡因子的情况进行不同的处理。
4. 当L的平衡因子为1(表示左子树高度大于右子树),说明它与根结点的平衡因子符号相同。在第8行,将它们的BF值都改为0,表示调整后的平衡状态。然后,在第9行进行右旋操作。
   - 右旋操作是将L作为根结点,将T作为L的右孩子,将L的右孩子作为T的左孩子,并且更新对应的平衡因子。这样旋转后,原本不平衡的子树T就变得平衡了。
5. 当L的平衡因子为-1(表示左子树高度小于右子树),说明它与根结点的平衡因子符号相反,此时需要进行双旋处理。
   - 第13至第22行对L的右孩子Lr的平衡因子作判断,根据不同情况修改根结点T和L的BF值。
   - 第24行将当前Lr的平衡因子BF改为0,表示调整后的平衡状态。
   - 第25行对根结点T的左子树进行左旋操作,具体操作如图示所示。
   - 第26行对根结点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;
/*  已插入到T的左子树中且左子树“长高”  */
if  (*taller)           
{
/*  检查T的平衡度  */
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
{                       
/*  应继续在T的右子树中进行搜索  */
/*  未插入  */
if  (!InsertAVL(&(*T)->rchild,  e,  taller))    
return  FALSE;
/*  已插入到T的右子树且右子树“长高”  */
if  (*taller)           
{
/*  检查T的平衡度  */
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;
}
  1. 程序开始执行时,如果当前树为空,则申请内存新增一个节点。
  2. 如果存在相同节点,则不需要插入。
  3. 当新节点e小于当前树T的根节点值时,向左子树查找插入位置。
  4. 如果左子树不为空,则递归调用本函数,直到找到插入位置,返回false。否则,说明插入节点成功,执行下面的语句。
  5. 如果插入了节点,需要判断T的平衡因子。如果平衡因子是1,说明左子树高于右子树,需要进行左平衡旋转处理。如果平衡因子是0或-1,则新插入节点未使整棵二叉排序树失去平衡性,只需要修改相关的BF值即可。
  6. 如果新节点e大于当前树T的根节点值,则向右子树查找插入位置。代码与之前类似,不再详述。

平衡二叉树是一种特殊的二叉排序树,其左右子树的高度差不超过1,可以通过旋转操作来保持平衡性。

int  i;
int  a[10]  =  {  3,  2,  1,  4,  5,  6,  7,  10,  9,  8  };
BiTree  T  =  NULL;
Status  taller;
for  (i  =  0;  i  <  10;  i++)
{
InsertAVL(&T,  a[i],  &taller);
}

本算法代码很长,是有些复杂,编程中容易在很多细节上出错,要想真正掌握它,需要自己多练习。不过其思想还是不难理解的,总之就是把不平衡消灭在最早时刻。


如果我们需要查找的集合本身没有顺序,在频繁查找的同时也需要经常的插入和删除操作,显然我们需要构建一棵二叉排序树,但是不平衡的二叉排序树,查找效率是非常低的,因此我们需要在构建时,就让这棵二叉排序树是平衡二叉树,此时我们的查找时间复杂度就为O(logn),而插入和删除也为O(logn)。这显然是比较理想的一种动态查找表算法。
 

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值