数据结构 ------平衡二叉树(AVL树)

一,平衡二叉树的概念

平衡二叉树是一种二叉排序树,其中每个节点的左子树和右子树的高度差最多等于1。
就是左边子树的深度减去右子树的深度的值成为平衡因子 ---- BF( balance factor),它只能是-1,0,1;

在这里插入图片描述
注意平衡二叉树的前提条件它必须是一颗二叉排序树

二,平衡二叉树的一些操作

在构造平衡二叉树的时候,一但发现失衡,也就是BF不满足条件的时候就得进行调整了,发现不平衡就立即改正。
所以先得知道如何调整,而调整也就四种情况,其实懂得原理之后,也就只有两种情况了。

(1)右旋

p节点为失衡节点,lchild 为失衡节点的左孩子
在这里插入图片描述

  • 图一发现 p 失衡
  • 图二把lchild 的左孩子接到失衡节点的右孩子上去
  • 图三将左孩子的右孩子变成失衡节点。
  • 最后 p 改为新的根节点即可。
//右旋 
void RRoate(AVLTree* p)
{
	AVLTree left = (*p) -> lchild;
	
	//将左孩子的右子树接到失衡节点的左子树去
	(*p) -> lchild = left -> rchild;
	left -> rchild = *p;
	*p = left; 
}

(2)左旋

其实左旋和右旋也是十分相似的
在这里插入图片描述

  • 图1发现p节点失衡
  • 图2将rchild的右孩子接到失衡因子的左孩子上
  • 图3将right 的左孩子等于了失衡因子
  • 最后p变成新的根节点
//左旋
void LRoate(AVLTree* p)
{
	AVLTree right = (*p) -> rchild;
	
	//将右孩子的左子树接到失衡节点的右子树去 
	(*p) -> rchild = right -> lchild;
	right -> lchild = (*p);
	*p = right; 
	 
}

(3)右左型

下面两张图,仔细观看,同样都是p失衡,但是rchild 所对应的孩子不同,对于图一来说,直接进行一个左旋就好了,但对于图2来说肯定不行,所以可以将图2变成图1。
直接对rchild进行一个右旋就好了。他就会变成图1。那么转成图1之后,不就是上面提到的左旋么。
在这里插入图片描述

//右左型的 
void RL(AVLTree* p)
{
	//需要先对它的右孩子进行右旋
	RRoate(&(*p)->rchild);
	//然后再对它进行左旋
	LRoate(p); 
	
}

在这里插入图片描述

(4)左右型

有了右左型,那肯定也得有左右型。

两者的p都是失衡的,而图1直接进行右旋即可,但是图2确不行,想一下上边的右左型的处理方式,这里也一样,先对lchild进行一个左旋使自己下去,孩子上来就旋转成图1的样子了。然后再对p进行右旋即可。

在这里插入图片描述

//左右型的 
void LR(AVLTree* p)
{
	//需要先对它的左孩子进行左旋
	LRoate(&(*p)->lchild);
	//然后再对它进行右旋
	RRoate(p); 
	
}

在这里插入图片描述

(5)总结

这就是关于AVL树的所有调整失衡的操作。
在这里插入图片描述
前两张图最简单,直接进行左旋或者右旋即可,而后两张图,得先对孩子进行旋转,在对自己进行旋转。

三,构造平衡二叉树

这代码只有左子树的插入,在此先说左子树的插入,因为左子树的插入和右子树的插入其实差不都的。
因为代码有点长,我一节一节分开了。

(1)插入

//插入数据 
void ALVInsert(AVLTree* T, ElemType key,bool* taller)
{
	if(*T == NULL)
	{
		*T = BuyNode(key); 
		*taller = true; 
	}
	else
	{
		if( (*T) -> data == key)
		{
			printf("重复插入节点\n");
			exit(-1);
		}
		
		
		if(key < (*T) -> data)
		{	
			ALVInsert(&(*T)->lchild,key,taller);
			//去左子树插入
			//.............
			//.............
			}
		else
		{	
			ALVInsert(&(*T)->rchild,key,taller);
			//去右子树插入
			//.............
			//.............
		}

这段代码也就是一个平衡二叉树的插入代码而已,唯一不一样的就是加入了一个taller
而这个变量是为了在以后的调整平衡时候用到的。

(2)判断是否失衡

当你找到合适的位置,并且插入成功后就该判断新插入的节点是否导致二叉树失衡了。

下面是左子树的判断失衡

#define LH +1   //左高 
#define RH -1	//右高
#define EH 0	//等高 
		//去左子树插入 
		if(key < (*T) -> data)
		{
			
			ALVInsert(&(*T)->lchild,key,taller);
			
			if(*taller == true)
			{
				//判断平衡 
				switch ((*T) -> bf)
				{
					//无非就这三种情况么 
					case LH:				//新节点加入进来,发现原来是左边已经是 +1 了,再加入就成 +2 了所以的调整一下
						LeftBalance(T);
						*taller = false; 
						break; 
					case EH:				//原来是等高因为新节点的加入到了左边,所以得左边的+1
						(*T) -> bf = LH;
						*taller = true; 
						break;
					case RH:
						(*T) -> bf = EH; 	//原来是右高,但是新节点加入到了左边,所以等高了
						*taller = false; 
						break;							
				} 	
			}			
		}

(3)调整

进行调整就是刚开始提到那四种情况,因为这边是左调整,所以只有左左型和左右型的调整。

void LeftBalance(AVLTree *T)
{
	AVLTree Lchild = NULL;		//失衡因子的左孩子 
	AVLTree LRchild = NULL;		//失衡因字的左孩子的右孩子 ····· 左右情况时候用到
	Lchild = (*T) -> lchild; 
	
	switch (Lchild -> bf)
	{
		case LH:				//左高(+1)进来的 ,发现符号还是+1 属于  左左型的 
			//调整平衡因子
			//。。。。。。。。。。。
			RRoate(T);			//进行右旋 
			break;
		case RH:				//左高(+1)进来的 	,发现符号是 右高(-1)  左右型的 
			LRchild = Lchild -> rchild;
			//调整平衡因子
			//。。。。。。。。。。。

			//进行左右型旋转	
			LR(T);
			break;
	}
	 
}
可并不是简简单单的进行旋转就完事了,必须得对他们所涉及到的平衡因子也进行调整

下面才是整个左调整的全部代码

void LeftBalance(AVLTree *T)
{
	AVLTree Lchild = NULL;		//失衡因子的左孩子 
	AVLTree LRchild = NULL;		//失衡因字的左孩子的右孩子 ····· 左右情况时候用到
	Lchild = (*T) -> lchild; 
	
	switch (Lchild -> bf)
	{
		case LH:				//左高(+1)进来的 ,发现符号还是+1 属于  左左型的 
			//调整平衡因子
			(*T) -> bf = Lchild -> bf = EH;  	//平衡了 
			RRoate(T);			//进行右旋 
			break;
		case RH:				//左高(+1)进来的 	,发现符号是 右高(-1)  左右型的 
			LRchild = Lchild -> rchild; 
			
			//调整平衡因子
		 	switch (LRchild->bf)
			{
				case LH:
					(*T)->bf = RH;
					Lchild->bf = EH;
					break;
				case EH:
					(*T)->bf = Lchild->bf = EH;
					break;
				case RH:
					(*T)->bf = EH;
					Lchild->bf = LH;
					break;
			}
			LRchild -> bf = EH;
			//至此调整完毕 
			
			//进行左右型旋转	
			LR(T);
			break;
	}
	 
}

(4)总结

右子树的插入和调整与左子树非常相似。结尾处有整个AVL树的源码。
在这里插入图片描述

四,结尾

平衡二叉树就是将所有坏事直接扼杀在开始的时候,整篇的代码很长,很复杂,需要多多练习。
参考: 大话数据结构中的AVL树,以及这篇博客
源码链接

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值