AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)

AVL树的原理

数据结构|二叉排序树的构造、查找、删除
AVL树在结构上十分严格
1.二叉树满足左结点小于根结点,根结点小于右结点
也要得知 左子树上每一个值都小于根结点,根结点大于柚子树每一个结点!
**2.每一个结点的左子树与柚子树的高度差 最多为1 **

高度

A图(平衡树)
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
在上面这颗树中 4结点的高度为1;20结点的高度也为1;9结点高度为2。
B图(仔细看 不是平衡树)
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
高度从最远子树开始算起,存在就是高度为1.
**平衡因子 Balance Factor **
平衡因子bf是用来判断一个结点,左右子树 高度差的
如A图中9结点:左右子树高度均为1,所以二者高度差为0,那么9结点的平衡因子也为0
B图8结点:左子树高度为2,柚子树高度为0 (不存在)所以bf为2
B图4结点:左子树高度为1,柚子树高度为3,bf为-2

之前定义了bf(高度差=绝对值)至多为1,那么说明这个不平衡需要将其调整,看完这篇你就懂如何调整了

平衡二叉树如何实现

我们看图一步一步来!
1.先创建一颗树的第一个结点
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
2.插入一个数 4
4比9小,是左子树
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
4的高度为1,9的高度为2,且平衡因子为0和1,平衡树。
3.插入一个结点3
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
9的左子树高度为2,柚子树高度为0,所以平衡因子(左高-右高)为2。
**打破了平衡,**将其归纳为左左LL型,需要进行平衡调整!
思路:降低高度,保持平衡
对于LL型,将最高的降下来,将中间大小的结点作为根结点
可理解为 将最高结点进行右旋转到最低处。
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
(如果4有柚子树,那么分配给9作为它的左子树)
4.插入一个结点13
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
4的平衡因子为-1;9的平衡因子为-1;所以没有打破平衡
5.插入一个结点13
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
4的平衡因子为-2;9的平衡因子为-2;所以打破平衡。

调整思路: 寻找离地面最近的平衡因子为-2的结点。并以此寻找3个结点的子树
所以此处调整9 13 15
RR 型,左旋将最小值的结点降下来,中间大小结点作为根结点。取代那个降下来的结点。
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
5.插入一个结点8
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
4结点的平衡因子为-2,打破了平衡;
然后寻找一颗最近的子树(离地面),且与插入元素一条路 。可见是4-13-9

这是左右型LR
这如何 选择呢? 遵循一个规律从下面的开始,先左子树进行右旋也就是
9-13进行右旋,
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
此时新的失衡树变为4-9-13了 而无需再追着8了因为8已经被调整了,但是因为上面的4还未调整,所以此时第二次遵循调整上半部分,柚子树左旋
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)


这里是我看网课看到的 借鉴一下,总结了一下:
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
对于LR型和RL型来说,把ABC是哪个元素中,把中间那个大小的放在根结点位置,再把最小的放在左结点,最大的放在右结点上。

AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
对于LR 或者RL型,寻找根平衡因子为2或-2的根节点,然以此寻找最近的子树三个结点(也就是里地面最近,最高的平衡因子为2或-2,且与插入元素一条线的(旋转两次,第一次寻找插入结点的 那条线,第二次不用跟着那条线)),
然后将L部分右旋,R部分左旋 ,从下面部分开始进行。

中间大小的那个结点转变为三者的根结点,其左右子树,分别分配给其他两个结点。

AVL代码实现

这一部分比原理难多了 苦笑
一定要画图加深理解,观察结点之间的变换和平衡因子的变换

咱一步一步来
先定义一下AVL树的结构
其多了一个平衡因子,在结构体定义一个int 型 bf
有人可能会说没有出现2和-2
嗯嗯嗯 这点很重要
在2(bf未改,将树的结构改动就可以消除这个2)出现的时候,实际上结构出现了,但是我们及时将它调整回平衡树就行!

#define LH +1/*左边高一点*/
#define EH 0/*等高*/
#define RH -1/*右高*/

上面用来记录平衡因子

#include<iostream>
#include<malloc.h>
typedef int Status;
typedef int elementype;
#define TRUE 1
#define FALSE 0
using namespace std;
#define LH +1/*左边高一点*/
#define EH 0/*等高*/
#define RH -1/*右高*/

typedef struct AVLBTree
{
	elementype data;
	int bf;//平衡因子
	struct AVLBTree *lchild, *rchild;
}*BTree, BNode;

代码的主体部分,构思一下 肯定会出现三连号的 左旋和右旋
那么先实现他!

//右旋
void R_Rotate(BTree &tree)//从失衡结点开始走
{
	BTree L = new AVLBTree;//创建一个中间结点

	L = tree->lchild;
	tree->lchild = L->rchild;//这一步是使得中间元素的右子树  为最大元素的左子树   
	L ->rchild = tree;//这里是从二者之间的替换
	tree = L;
}

//右旋
void L_Rotate(BTree &tree)
{
	BTree R = new AVLBTree;
	R = tree->rchild;
	tree->rchild = R->lchild;
	R ->lchild = tree;
	tree = R;
}

上面这部分相信很好理解
下面从创建二叉树开始走

这里分为三大部分
1.创建一个结点(树未被创建时,最低端结点未被创立时)
2.插入的元素,从根结点开始比较大小,使用递归寻找其位置
3.当新的元素插入成功后,根据左插入和右插入,修改平衡因子

(三点一线,遵循最高的)最高的那个对于插入左子树的元素x时,
如果x的根结点原本左高一个,那么此时又插入一个就会生成bf为2的结构,就要进行左平衡处理,以免失衡;

如果原本右高,那么左插之后平衡,无需处理

如果原本平衡,那么x的根结点bf改为左高

插入柚子树的元素时类似

Status InSertAVL(BTree &tree, int x, Status *taller)
{
	if (tree == NULL)//当树没有建立时
	{
		tree = new AVLBTree;
		tree->data = x;
		tree->bf = EH;
		tree->lchild = tree->rchild = NULL;
		*taller = TRUE;
	}
	else//树存在时,从根结点开始比较
	{
		if (x == tree->data)//这步是排除一颗树中相等的元素          
		{
			*taller = FALSE;//没有插入 说明没有长高   taller用于后续判断是否需要旋转调平衡
			return FALSE;
		}
		if (x<tree->data)//往左插  在tree结点的左侧插入 且寻找合适的位置
		{
			//用递归查找最小的那个值
			//InSertAVL 这个函数 插入成功返回真  没插入返回假
			//如果在左侧没有遇见相同的,那么将插到一个NULL的结点上  且形成新的结点,然后返回为真。
			//所以这一步,插入成功的时候不会执行
			if (!InSertAVL(tree->lchild, x, taller))//这里是一个递归  很重要!!!!思路关键
			{
				return FALSE;
			}

			//插入成功后,taller为1 (真)
			//插入成功之后修改其平衡因子,
			//平衡因子修改完之后,进行判断是否需要旋转调平衡
			if (*taller)//
			{
				switch (tree->bf)//查看这个结点的平衡因子
				{
				case LH:/*原本左高,此时又插入一个左结点  那就不可能平衡,所以要进行平衡处理*/
					LeftBalance(tree);//
					*taller = FALSE;//左平衡程序后  跳出循环
					break;
				case EH:/*原本等高*/  //现在插入一个左结点,其平衡打破
					tree->bf = LH;//使平衡因子为左高
					*taller = TRUE;//需要再次进行递归 调整 以便上面的平衡因子
					break;
				case RH:/*原本右高,现在插入左结点之后 平衡了  无需处理*/
					tree->bf = EH;
					*taller = FALSE;
					break;
				}
			}
		}
		else
		{
			if (!InSertAVL(tree->rchild, x, taller))
			{
				return FALSE;
			}
			if (*taller)
			{
				switch (tree->bf)
				{
				case LH://原本左偏,加入柚子树后平衡了
					tree->bf = EH;
					*taller = FALSE;//标记F  后续无需处理
					break;
				case EH://原本平衡 往右加入之后  所以改为-1
					tree->bf = RH;
					*taller = TRUE;//标记T  后续还要进行处理
					break;
				case RH://原本右偏,加入柚子树后 更加右偏,需要右平衡处理!!!
					RightBalance(tree);
					*taller = FALSE;//经过R处理之后  标记后续不再进行。
					break;
				}
			}
		}


	}

	return TRUE;
}

balance 这部分如何变化上面可以get到,难处在于,变化之前要将1其平衡因子给修改过来。

void LeftBalance(BTree &tree)
{
	BTree L = new AVLBTree;
	BTree Lr = new AVLBTree;
	L = tree->lchild;

	//这个时候进行左平衡有两种情况
	//1.   仅仅左插入   所以要进行一次右循环
	//2.   打破平衡时是插入了左边的右结点  生成左-右型,这个时候要进行双循环。******上左下右的  结构时,要遵循先将下面的两个结点进行左循环,再将上面两个结点进行右循环
	
	switch (L->bf)
	{
	case LH://两个连续的左子树,进行右循环即可校正
		tree->bf = L->bf = EH;
		R_Rotate(tree);
		break;
	case RH://中间元素L为-1, 一左一右,插入的是右子树,所以遵循上左下右进行解决
	{
		Lr = L->rchild;//找到右元素
		switch (Lr ->bf)//这一步  是进行平衡因子的修改,因为平衡调整,不仅是位置上的调整,还需调整其平衡因子
		{
		case LH://具体如何可以根据不同情况画图   然后可以理解
			tree->bf = RH;
			L->bf = EH;
			break;
		case EH://这种是影响 在最下面的左右型
			tree->bf = L->bf = EH;
			break;
		case RH:
			tree->bf = EH;
			L ->bf = LH;
			break;
		}
	}
		Lr->bf = EH;//修正
		L_Rotate(tree->lchild);
		R_Rotate(tree);
	}
}

void RightBalance(BTree &tree)
{
	BTree R = new AVLBTree;
	BTree Rr = new AVLBTree;
	R = tree->rchild;
	switch (R->bf)
	{
	case RH:
		tree->bf = R->bf = EH;
		L_Rotate(tree);
		break;
	case LH:
	{
		Rr = R->lchild;
		switch (Rr->bf)//
		{
		case RH:
			tree->bf = LH;
			R->bf = EH;
			break;
		case EH:
			tree->bf = R->bf = EH;
			break;
		case LH:
			tree->bf = EH;
			R->bf = RH;
			break;
		}
	}
		Rr->bf = EH;
		R_Rotate(tree->rchild);
		L_Rotate(tree);
	}
}

关于Balance这 开始也不能理解其中含义,但是画了图之后,就可以理解了,平衡旋转你肯定可以理解l, 这段代码还对平衡因子在旋转之前做了修改。
AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
下面是完整代码,代码源自大话数据结构一书

#include<iostream>
#include<malloc.h>
typedef int Status;
typedef int elementype;
#define TRUE 1
#define FALSE 0
using namespace std;
#define LH +1/*左边高一点*/
#define EH 0/*等高*/
#define RH -1/*右高*/


typedef struct AVLBTree
{
	elementype data;
	int bf;//平衡因子
	struct AVLBTree *lchild, *rchild;
}*BTree, BNode;


//右旋
void R_Rotate(BTree &tree)
{
	BTree L = new AVLBTree;//创建一个中间结点

	L = tree->lchild;
	tree->lchild = L->rchild;//这一步是使得中间元素的右子树  为最大元素的左子树   
	L ->rchild = tree;//这里是从二者之间的替换
	tree = L;
}

//右旋
void L_Rotate(BTree &tree)
{
	BTree R = new AVLBTree;
	R = tree->rchild;
	tree->rchild = R->lchild;
	R ->lchild = tree;
	tree = R;
}
//判断左平衡


void LeftBalance(BTree &tree)
{
	BTree L = new AVLBTree;
	BTree Lr = new AVLBTree;
	L = tree->lchild;

	//这个时候进行左平衡有两种情况
	//1.   仅仅左插入   所以要进行一次右循环
	//2.   打破平衡时是插入了左边的右结点  生成左-右型,这个时候要进行双循环。******上左下右的  结构时,要遵循先将下面的两个结点进行左循环,再将上面两个结点进行右循环
	
	switch (L->bf)
	{
	case LH://两个连续的左子树,进行右循环即可校正
		tree->bf = L->bf = EH;
		R_Rotate(tree);
		break;
	case RH://中间元素L为-1, 一左一右,插入的是右子树,所以遵循上左下右进行解决
	{
		Lr = L->rchild;//找到右元素
		switch (Lr ->bf)//这一步  是进行平衡因子的修改,因为平衡调整,不仅是位置上的调整,还需调整其平衡因子
		{
		case LH://具体如何可以根据不同情况画图   然后可以理解
			tree->bf = RH;
			L->bf = EH;
			break;
		case EH://这种是影响 在最下面的左右型
			tree->bf = L->bf = EH;
			break;
		case RH:
			tree->bf = EH;
			L ->bf = LH;
			break;
		}
	}
		Lr->bf = EH;//修正
		L_Rotate(tree->lchild);
		R_Rotate(tree);
	}
}

void RightBalance(BTree &tree)
{
	BTree R = new AVLBTree;
	BTree Rr = new AVLBTree;
	R = tree->rchild;
	switch (R->bf)
	{
	case RH:
		tree->bf = R->bf = EH;
		L_Rotate(tree);
		break;
	case LH:
	{
		Rr = R->lchild;
		switch (Rr->bf)//
		{
		case RH:
			tree->bf = LH;
			R->bf = EH;
			break;
		case EH:
			tree->bf = R->bf = EH;
			break;
		case LH:
			tree->bf = EH;
			R->bf = RH;
			break;
		}
	}
		Rr->bf = EH;
		R_Rotate(tree->rchild);
		L_Rotate(tree);
	}
}



Status InSertAVL(BTree &tree, int x, Status *taller)
{
	if (tree == NULL)//当树没有建立时
	{
		tree = new AVLBTree;
		tree->data = x;
		tree->bf = EH;
		tree->lchild = tree->rchild = NULL;
		*taller = TRUE;
	}
	else//树存在时,从根结点开始比较
	{
		if (x == tree->data)//这步是排除一颗树中相等的元素          //所以AVL树后面要研究一下,一棵树中有相等的元素时是如何排序的
		{
			*taller = FALSE;//没有插入 说明没有长高   taller用于后续判断是否需要旋转调平衡
			return FALSE;
		}
		if (x<tree->data)//往左插  在tree结点的左侧插入 且寻找合适的位置
		{
			//用递归查找最小的那个值
			//InSertAVL 这个函数 插入成功返回真  没插入返回假
			//如果在左侧没有遇见相同的,那么将插到一个NULL的结点上  且形成新的结点,然后返回为真。
			//所以这一步,插入成功的时候不会执行
			if (!InSertAVL(tree->lchild, x, taller))//这里是一个递归  很重要!!!!思路关键
			{
				return FALSE;
			}

			//插入成功后,taller为1 (真)
			//插入成功之后修改其平衡因子,
			//平衡因子修改完之后,进行判断是否需要旋转调平衡
			if (*taller)//
			{
				switch (tree->bf)//查看这个结点的平衡因子
				{
				case LH:/*原本左高,此时又插入一个左结点  那就不可能平衡,所以要进行平衡处理*/
					LeftBalance(tree);//
					*taller = FALSE;//左平衡程序后  跳出循环
					break;
				case EH:/*原本等高*/  //现在插入一个左结点,其平衡打破
					tree->bf = LH;//使平衡因子为左高
					*taller = TRUE;//需要再次进行递归 调整 以便上面的平衡因子
					break;
				case RH:/*原本右高,现在插入左结点之后 平衡了  无需处理*/
					tree->bf = EH;
					*taller = FALSE;
					break;
				}
			}
		}
		else
		{
			if (!InSertAVL(tree->rchild, x, taller))
			{
				return FALSE;
			}
			if (*taller)
			{
				switch (tree->bf)
				{
				case LH://原本左偏,加入柚子树后平衡了
					tree->bf = EH;
					*taller = FALSE;//标记F  后续无需处理
					break;
				case EH://原本平衡 往右加入之后  所以改为-1
					tree->bf = RH;
					*taller = TRUE;//标记T  后续还要进行处理
					break;
				case RH://原本右偏,加入柚子树后 更加右偏,需要右平衡处理!!!
					RightBalance(tree);
					*taller = FALSE;//经过R处理之后  标记后续不再进行。
					break;
				}
			}
		}


	}

	return TRUE;
}


void main()
{
	BTree tree=NULL;

	int x;
	cin >> x;
	Status taller=0;
	InSertAVL(tree, x, &taller);
	
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	cin >> x;
	InSertAVL(tree, x, &taller);
	system("pause");
}
上一篇:【大话Mysql面试】-Mysql的索引为什么要使用B+树,而不是B树,红黑树等之类?


下一篇:[面面面]搞定计算机面试常见知识点——算法篇