平衡二叉树详解

AVL树即是一种自平衡的二叉树,在每次插入和删除操作时通过旋转不平衡的节点来使二叉树重新保持平衡,并且查找、插入和删除操作在平均和最坏情况下时间复杂度都是O(log n)。

分为四种旋转情况:

第一种:LL型

要进行右旋操作。

平衡二叉树某一节点的左孩子的左子树上插入一个新的节点,使得该节点不再平衡。这时只需要把树向右旋转一次即可,如图所示,原A的左孩子B变为父结点,A变为其右孩子,而原B的右子树变为A的左子树。


相应的代码是:

void R_Rotate(BTree *p)//以p为根节点的二叉排序树进行右旋转
{
	BTree L;
	L=(*p)->lchild;
	(*p)->lchild=L->rchild;
	L->rchild=(*p);
	*p=L;//p指向新的根节点
}

第二种:RR型

要进行左旋操作。

平衡二叉树某一节点的右孩子的右子树上插入一个新的节点(该节点可以是BR的左节点,也可以是右节点),使得该节点不再平衡。这时只需要把树向左旋转一次即可,如图所示,原A右孩子B变为父结点,A变为其左孩子,而原B的左子树BL将变为A的右子树。


相应代码为:

void L_Rotate(BTree *p)//以p为根节点的二叉排序树进行左旋转
{
	BTree R;
	R=(*p)->rchild;
	(*p)->rchild=R->lchild;
	R->lchild=(*p);
	*p=R;
}


第三种:LR型

要先对左子树进行左旋再对根节点进行右旋。

平衡二叉树某一节点的左孩子的右子树上插入一个新的节点,使得该节点不再平衡。这时需要旋转两次,仅一次的旋转是不能够使二叉树再次平衡。如图所示,在B节点按照RR型向左旋转一次之后,二叉树在A节点仍然不能保持平衡,这时还需要再向右旋转一次。



实现时判断新节点的插入位置,当新节点在左孩子的左子树上时,只需进行右旋操作;当新节点在左孩子的右子树上时,要进行LR旋操作。

代码如下:

void LeftBalance(BTree *T)
{
	BTree L,Lr;
	L=(*T)->lchild;
	switch(L->BF)
	{
		//检查T的左子树平衡度,并作相应的平衡处理
		case LH://新节点插入在T的左孩子的左子树上,做单右旋处理
			(*T)->BF=L->BF=EH;
			R_Rotate(T);
			break;
		case RH://新插入节点在T的左孩子的右子树上,做双旋处理
			Lr=L->rchild;
			//先设置平衡度,再进行旋转
			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;
			L_Rotate(&(*T)->lchild);    //对左子树进行左旋
			R_Rotate(T);				//对根节点进行右旋
	}
} 

第四种情况:RL型

要先对右子树进行右旋再对根节点进行左旋。跟LR相反。

平衡二叉树某一节点的右孩子的左子树上插入一个新的节点,使得该节点不再平衡。同样,这时需要旋转两次,旋转方向刚好同LR型相反。


实现代码:

void RightBalance(BTree *T)
{
	BTree R,Rl;
	R=(*T)->rchild;
	switch(R->BF)
	{
		case RH://新节点插在T的右孩子的右子树上,要做单左旋处理
			(*T)->BF=R->BF=EH;
			L_Rotate(T);
			break;
		case LH://新节点插在T的右孩子的左子树上,要做双旋处理
			Rl=R->lchild;
			switch(Rl->BF)
			{
				case LH:
					(*T)->BF=EH;
					R->BF=RH;
					break;
				case EH:
					(*T)->BF=R->BF=EH;
					break;
				case RH:
					(*T)->BF=LH;
					R->BF=EH;
					break;
			}
			Rl->BF=EH;
			R_Rotate(&(*T)->rchild);
			L_Rotate(T);
	}
}

当新节点插在T的右孩子的左子树上时,旋转操作后平衡因子的取值可看下图



删除算法

在二叉排序树中删去一个结点,分三种情况讨论:

 1.若*p结点为叶子结点,即PL(左子树)和PR(右子树)均为空树。由于删去叶子结点不破坏整棵树的结构,则只需修改其双亲结点的指针即可。

 2.若*p结点只有左子树PL或右子树PR,此时只要令PL或PR直接成为其双亲结点*f的左子树(当*p是左子树)或右子树(当*p是右子树)即可,作此修改也不破坏二叉排序树的特性。

 3.若*p结点的左子树和右子树均不空。在删去*p之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整。比较好的做法是,找到*p的直接前驱(或直接后继)*s,用*s来替换结点*p,然后再删除结点*s。



对于第三种情况,还可以分下面两种情况来讨论:


对应代码为:

/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
int Delete(BTree *p)
{
    BTree q,s;
    if((*p)->rchild==NULL) /* 右子树空则只需重接它的左子树(待删结点是叶子也走此分支) */
    {
        q=*p; *p=(*p)->lchild; free(q);
    }
    else if((*p)->lchild==NULL) /* 只需重接它的右子树 */
    {
        q=*p; *p=(*p)->rchild; free(q);
    }
    else /* 左右子树均不空 */
    {
        q=*p; s=(*p)->lchild;
        while(s->rchild) /* 转左,然后向右到尽头(找待删结点的前驱) */
        {
            q=s;
            s=s->rchild;
        }
        (*p)->data=s->data; /*  s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) */
        if(q!=*p)
            q->rchild=s->lchild; /*  重接q的右子树 */ 
        else
            q->lchild=s->lchild; /*  重接q的左子树 */
        free(s);
    }
    return 1;
}

/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, */
/* 并返回TRUE;否则返回FALSE。 */
int DeleteBST(BTree *T,int key)
{ 
    if(!*T) /* 不存在关键字等于key的数据元素 */ 
        return 0;
    else
    {
        if (key==(*T)->data) /* 找到关键字等于key的数据元素 */ 
            return Delete(T);
        else if (key<(*T)->data)
            return DeleteBST(&(*T)->lchild,key);
        else
            return DeleteBST(&(*T)->rchild,key);
         
    }
}

平衡二叉树的完整代码:

/*
首先平衡二叉树是一个二叉排序树;
其基本思想是:
在构建二叉排序树的过程中,当每插入一个节点时,
先检查是否因为插入而破坏了树的平衡性,若是,
找出最小不平衡树,进行适应的旋转,使之成为新的平衡二叉树。
*/
#include<stdio.h>
#include<stdlib.h>
//#include<stdint.h>
#define LH 1   //左子树比右子树高
#define EH 0	//左右子树等高
#define RH -1	//右子树比左子树高  
 
typedef struct BTNode
{
	int data;
	int BF;//平衡因子(balance factor)
	struct BTNode *lchild,*rchild;
}BTNode,*BTree; 

void R_Rotate(BTree *p)//以p为根节点的二叉排序树进行右旋转
{
	BTree L;
	L=(*p)->lchild;
	(*p)->lchild=L->rchild;
	L->rchild=(*p);
	*p=L;//p指向新的根节点
}
 
void L_Rotate(BTree *p)//以p为根节点的二叉排序树进行左旋转
{
	BTree R;
	R=(*p)->rchild;
	(*p)->rchild=R->lchild;
	R->lchild=(*p);
	*p=R;
}
 
void LeftBalance(BTree *T)
{
	BTree L,Lr;
	L=(*T)->lchild;
	switch(L->BF)
	{
		//检查T的左子树平衡度,并作相应的平衡处理
		case LH://新节点插入在T的左孩子的左子树上,做单右旋处理
			(*T)->BF=L->BF=EH;
			R_Rotate(T);
			break;
		case RH://新插入节点在T的左孩子的右子树上,做双旋处理
			Lr=L->rchild;
			//先设置平衡度,再进行旋转
			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;
			L_Rotate(&(*T)->lchild);    //对左子树进行左旋
			R_Rotate(T);				//对根节点进行右旋
	}
} 
void RightBalance(BTree *T)
{
	BTree R,Rl;
	R=(*T)->rchild;
	switch(R->BF)
	{
		case RH://新节点插在T的右孩子的右子树上,要做单左旋处理
			(*T)->BF=R->BF=EH;
			L_Rotate(T);
			break;
		case LH://新节点插在T的右孩子的左子树上,要做双旋处理
			Rl=R->lchild;
			switch(Rl->BF)
			{
				case LH:
					(*T)->BF=EH;
					R->BF=RH;
					break;
				case EH:
					(*T)->BF=R->BF=EH;
					break;
				case RH:
					(*T)->BF=LH;
					R->BF=EH;
					break;
			}
			Rl->BF=EH;
			R_Rotate(&(*T)->rchild);
			L_Rotate(T);
	}
}
 
int InsertAVL(BTree *T,int e,int *taller)	//变量taller反应T长高与否
{
	if(!*T)
	{
		*T=(BTree)malloc(sizeof(BTNode));
		(*T)->data=e;
		(*T)->lchild=(*T)->rchild=NULL;
		(*T)->BF=EH;
		*taller=1;
	}
	else
	{
		if(e==(*T)->data)//不插入
		{
			*taller=0;
			return 0; 
		}
		if(e<(*T)->data)
		{
			if(!InsertAVL(&(*T)->lchild,e,taller))//未插入
				return 0;
			if(*taller)//以插入左子树,且左子树变高
			{
				switch((*T)->BF)
				{
					case LH://原本左子树比右子树高,需要做左平衡处理
						LeftBalance(T);
						*taller=0;
						break;
					case EH://原本左右子树等高,现因左子树增高而树增高
						(*T)->BF=LH;
						*taller=1;
						break;
					case RH://原本右子树比左子树高,现在左右子树等高
						(*T)->BF=EH;
						*taller=0;
						break;
				}
			}
		}
		else
		{
			//应在T的右子树中搜寻
			if(!InsertAVL(&(*T)->rchild,e,taller))
				return 0;
			if(*taller)//插入右子树,且右子树长高
			{
				switch((*T)->BF)
				{
					case LH://原本左子树比右子树高,现在左右子树等高
						(*T)->BF=EH;
						*taller=0;
						break;
					case EH://原本左右子树等高,现在右子树变高
						(*T)->BF=RH;
						*taller=1;
						break;
					case RH://原本右子树比左子树高,现在需做右平衡处理
						RightBalance(T);
						*taller=0;
						break;
				}
			}
		}
	}
	return 1;
}
 
int Find(BTree T,int key)
{
	if(!T)
		return 0;
	else if(T->data==key)
		return 1;
	else if(T->data<key)
		return Find(T->rchild,key);
	else
		return Find(T->lchild,key);
}
 
void Output(BTree T)
{
	if(T)
	{
		printf("%d",T->data);
		if(T->lchild||T->rchild)
		{
			printf("(");
			Output(T->lchild);
			printf(",");
			Output(T->rchild);
			printf(")");
		}
	}
}

/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
int Delete(BTree *p)
{
    BTree q,s;
    if((*p)->rchild==NULL) /* 右子树空则只需重接它的左子树(待删结点是叶子也走此分支) */
    {
        q=*p; *p=(*p)->lchild; free(q);
    }
    else if((*p)->lchild==NULL) /* 只需重接它的右子树 */
    {
        q=*p; *p=(*p)->rchild; free(q);
    }
    else /* 左右子树均不空 */
    {
        q=*p; s=(*p)->lchild;
        while(s->rchild) /* 转左,然后向右到尽头(找待删结点的前驱) */
        {
            q=s;
            s=s->rchild;
        }
        (*p)->data=s->data; /*  s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) */
        if(q!=*p)
            q->rchild=s->lchild; /*  重接q的右子树 */ 
        else
            q->lchild=s->lchild; /*  重接q的左子树 */
        free(s);
    }
    return 1;
}

/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, */
/* 并返回TRUE;否则返回FALSE。 */
int DeleteBST(BTree *T,int key)
{ 
    if(!*T) /* 不存在关键字等于key的数据元素 */ 
        return 0;
    else
    {
        if (key==(*T)->data) /* 找到关键字等于key的数据元素 */ 
            return Delete(T);
        else if (key<(*T)->data)
            return DeleteBST(&(*T)->lchild,key);
        else
            return DeleteBST(&(*T)->rchild,key);
         
    }
}


int main(int argc,char *argv[])
{
	int i;
	int A[]={3,2,1,4,5,6,7,10,9,8};
	BTree T=NULL;
	int taller;
	for(i=0;i<sizeof(A)/sizeof(int);i++)
		InsertAVL(&T,A[i],&taller);
	Output(T);
	DeleteBST(&T,4);
	Output(T);
	printf("\n");
	if(Find(T,4))
		printf("6 is find in the AVL tree!\n");
	else 
		printf("6 is not find in the AVL tree!\n"); 
	return 0;
}


参考文章:

http://www.cnblogs.com/zhuyf87/archive/2012/11/09/2763113.html

http://blog.csdn.net/gabriel1026/article/details/6311339


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值