AVL平衡二叉树原理解析全网最详细,最清晰解释欢迎相互学习 新人访问少,但绝对是干货

代码转自
c语言中文网
http://c.biancheng.net/view/3432.html

旋转操作 学习自
https://www.cnblogs.com/cherryljr/p/6669489.html

但注释与解析绝大部分为原创 ,非常详细深刻,
每一行代码都有解释
图片为 我分析AVL树的非常核心重要的比较,
不要嫌乱,认真看
图片和代码严格对应

看懂图片笔迹就能看懂的代码注释

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


```cpp
#include <stdio.h>
#include <stdlib.h>
//分别定义平衡因子数
#define LH +1
#define EH  0
#define RH -1
typedef int ElemType;
//typedef enum { false, true } bool;
//定义二叉排序树
typedef struct BSTNode {
	ElemType data;
	int bf;//balance flag
	struct BSTNode *lchild, *rchild;
}*BSTree, BSTNode;

//对以 p 为根结点的二叉树做右旋处理,令 p 指针指向新的树根结点
//这里需要的 结点p是旋转中心节点
//根据左旋右旋法则,
// 右右单旋转时,这里的 旋转中心结点p 为最小不平衡树的 孩子结点
// 右左双旋转时候,旋转中心结点p 为最小不平衡树的 孙子结点
void R_Rotate(BSTree* p)
{
	//借助文章中的图 5 所示加以理解,其中结点 A 为 p 指针指向的根结点
	BSTree lc = (*p)->lchild;

	//这里lc->rchild 如果不为NULL,则应该挪到 (*p)->lchild;
	//如果lc->rchild 如果为NULL,则挪到 (*p)->lchild;也不影响,因为 (*p)的lchild;转换过程中被转换掉变为NULL了,则再加一次NULL也不影响
	//所以无论lc->rchild 是否为 NULL,都应该挪到 (*p)->lchild;
	(*p)->lchild = lc->rchild;
	lc->rchild = *p;
	*p = lc;
}
//对以 p 为根结点的二叉树做左旋处理,令 p 指针指向新的树根结点
//这里需要的 结点p是旋转中心节点
//根据左旋右旋法则,
// 左左单旋转时,这里的 旋转中心结点p 为最小不平衡树的 孩子结点
// 左右双旋转时候,旋转中心结点p 为最小不平衡树的 孙子结点
void L_Rotate(BSTree* p)
{
	//借助文章中的图 6 所示加以理解,其中结点 A 为 p 指针指向的根结点
	BSTree rc = (*p)->rchild;
	//这里rc->lchild 如果不为NULL,则应该挪到 (*p)->rchild;
	//如果rc->lchild 如果为NULL,则挪到 (*p)->rchild;也不影响,因为 (*p)的rchild;转换过程中被转换掉变为NULL了,则再加一次NULL也不影响
	//所以无论rc->lchild 是否为 NULL,都应该挪到 (*p)->rchild;
	(*p)->rchild = rc->lchild;
	rc->lchild = *p;
	*p = rc;
}

//对以指针 T 所指向结点为根结点的二叉树作左子树的平衡处理,令指针 T 指向新的根结点
// 需要这这里平很处理的结点 必定是已经不平衡了得结点
//也就是必须是平衡因子为-2 或者 2 的结点,又因为左旋,所以这里需要的 参数T的平衡因子必须为2 ,才可以调用这个左旋函数

//这里是对T的左子树进行不平衡的旋转处理
//分为左左单旋转和左右双旋转
void LeftBalance(BSTree* T)
{
	BSTree lc, rd;
	lc = (*T)->lchild;
	//查看以 T 的左子树为根结点的子树,失去平衡的原因,
	//如果 bf 值为 1 ,则说明新节点添加在左子树为根结点的左子树中,需要对其进行左左单旋转旋处理;
	//反之,如果 bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋(左右双旋转)的处理
	switch (lc->bf)
	{

   //bf 值为 1 ,则新节点说明添加在左子树为根结点的左子树中,需要对其进行左左单旋转旋处理;
	case LH:

		//先调整好这棵树转换 完成后 应该的有的 平衡因子
		(*T)->bf = lc->bf = EH;
		//再直接旋转,不必考虑平衡因子,因为上面已经提前一步调整好了平衡因子
		R_Rotate(T);
		break;

    //bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋(左右双旋转)的处理
	case RH:
		rd = lc->rchild;

		//先调整好这棵树转换 完成后 应该的有的 平衡因子
		switch (rd->bf)
		{
		case LH:
			(*T)->bf = RH;
			lc->bf = EH;
			break;
		case EH:
			(*T)->bf = lc->bf = EH;
			break;
		case RH:
			(*T)->bf = EH;
			lc->bf = LH;
			break;
		}
		rd->bf = EH;

		//再直接旋转,不必考虑平衡因子,因为上面已经提前一步调整好了平衡因子
		//先左旋 T的左子树
		L_Rotate(&(*T)->lchild);
		//再右旋T
		R_Rotate(T);
		break;
	}
}
//右子树的平衡处理同左子树的平衡处理完全类似
// 需要这这里平很处理的结点 必定是已经不平衡了得结点

//对以指针 T 所指向结点为根结点的二叉树作右子树的平衡处理,令指针 T 指向新的根结点
// 需要这这里平很处理的结点 必定是已经不平衡了得结点
//也就是必须是平衡因子为-2 或者 2 的结点,又因为左旋,所以这里需要的 参数T的平衡因子必须为-2 ,才可以调用这个右旋函数

//这里是对T的右子树进行不平衡的旋转处理
//分为右右单旋转和右左双旋转
void RightBalance(BSTree* T)
{
	BSTree lc, rd;
	lc = (*T)->rchild;

	//每一个都会有  rd->bf = EH;  这一步,所以补充在最后
	
	//查看以 T 的右子树为根结点的子树,失去平衡的原因,
	//如果 bf 值为 -1 ,则说明新节点添加在右子树为根结点的右子树中,需要对其进行右右单旋转旋处理;
	//反之,如果 bf 值为 -1,说明添加在以右子树为根结点的左子树中,需要进行双向先右旋后左旋(右左双旋转)的处理
	switch (lc->bf)
	{
	//bf 值为 1 ,则新节点说明添加在右子树为根结点的右子树中,需要对其进行右右单旋转旋处理;
	case RH:

		//先调整好这棵树转换 完成后 应该的有的 平衡因子
		(*T)->bf = lc->bf = EH;
		//再直接旋转,不必考虑平衡因子,因为上面已经提前一步调整好了平衡因子
		L_Rotate(T);
		break;

	//bf 值为 1,说明添加在以右子树为根结点的左子树中,需要进行双向先右旋后左旋(右左双旋转)的处理
	case LH:
		rd = lc->lchild;

		//先调整好这棵树转换 完成后 应该的有的 平衡因子
		switch (rd->bf)
		{
			//(*T)->rchild;和 (*T)->rchild->lchild; 分别为 1 1
		case LH:
			(*T)->bf = EH;
			lc->bf = RH;
			break;
		case EH:
			(*T)->bf = lc->bf = EH;
			break;
			//1  -1
		case RH:
			(*T)->bf = EH;
			lc->bf = LH;
			//	(*T)->bf = LH;
			//  lc->bf = EH;
			break;
		}

		//每一个都会有这一步,所以补充在最后
		rd->bf = EH;  

		//再直接旋转,不必考虑平衡因子,因为上面已经提前一步调整好了平衡因子
		//先右旋 T的左子树
		R_Rotate(&(*T)->rchild);
		//再左旋T
		L_Rotate(T);
		break;
	}
}

int InsertAVL(BSTree* T, ElemType e, bool* taller)
{
	//如果本身为空树,则直接添加 e 为根结点
	if ((*T) == NULL)
	{
		(*T) = (BSTree)malloc(sizeof(BSTNode));
		(*T)->bf = EH;
		(*T)->data = e;
		(*T)->lchild = NULL;
		(*T)->rchild = NULL;
		*taller = true;
	}
	//如果二叉排序树中已经存在 e ,则不做任何处理
	else if (e == (*T)->data)
	{
		*taller = false;
		return 0;
	}
	//如果 e 小于结点 T 的数据域,则插入到 T 的左子树中
	else if (e < (*T)->data)
	{
		//如果插入过程,不会影响树本身的平衡,则直接结束,返回上一层递归
		//未能成功插入
		//如果成功插入,则通过 taller的值去 判断 是否需要 调整结点平衡因子 和 旋转操作
		if (!InsertAVL(&(*T)->lchild, e, taller))
			return 0;
		//判断插入过程是否会导致整棵树的深度 +1
		if (*taller)
		{
			//判断根结点 T 的平衡因子是多少,
			//由于是在其左子树添加新结点的过程中导致失去平衡,
			//所以当 T 结点的平衡因子本身为 1 时,需要进行左子树的平衡处理,否则更新树中各结点的平衡因子数

			//某个结点p1 的平衡因子为 LH,EH,RH 时候,在其左子树 插入 新的节点p2后,
			//p2的平衡因子必定为EH,而p1的平衡因子就会变化,变化趋势为(因为p1的左子树被插入,所以p1的平衡因子
			//会增加1)
			//LH-> 2此时不平衡,需要调整
			//EH-> LH 此时平衡,不需要调整
			//RH-> EH 此时平衡,不需要调整
			switch ((*T)->bf)
			{
			case LH:
				//每次退到有节点的平衡因子即将 变为-1,0,1之外的时候,进行变换。也就是遇到平衡因子为-2或者2的时候,
				//说明遇到了 最小不平衡树的跟结点,然后拿着根节点去变换
				//又因为这里 是左子树插入,并且原先节点的平衡因子为1(LH),就是此节点左边比右边深度 深1,
				//那么在此节点左子数插入后左边比右边深2,此时此节点的 平衡因子变为2,
				//说明这是一颗最小不平衡树的 根节点,又因为是 左子数插入了结点,因此需要进行左子树的 变换

				//这里变换时需要 输入的结点 必定是 最小不平衡树的根节点,
				//也就是T的平衡因子 必须为-2 或者 2.又因为左旋,则需要平衡因子为2的结点
				LeftBalance(T);  

				//最小不平衡树调整完成后,不会影响 再往上的节点的平衡因子,因此 taller变为false,
				//后序退出递归的时候 直接退出,不再调整平衡因子
				//而左旋或者右旋过程中,左旋右旋函数内 会调整好节点的平衡因子。
				*taller = false;
				break;
			case  EH:
				(*T)->bf = LH;
				//因为已知这是在左子树插入,那么原先的为0(EH)的父节点 会变为1(LH)
				//又因为 平衡因子为1(LH)的结点 不是 最小不平衡树的 根节点,所以只需要调整不平衡因子后 往上回退。
				//只有初次遇到 节点的平衡因子为-2或者2的时候,说明这是 最小不平衡树 根节点,
				//拿着这个根节点去调整(左旋,右旋,左右旋,右左旋)
				//因为这里需要进一步往上调整,所以	*taller = true;表明上一步需要继续调整

				*taller = true;
				break;
			case RH:
				(*T)->bf = EH;
				//因为已知这是在左子树插入,那么原先的为-1(RH)的父节点 会变为0(EH)
				//又因为 平衡因子为0(EH)的结点 不是 最小不平衡树的 根节点,所以只需要调整不平衡因子后 往上回退。
				//只有初次遇到 节点的平衡因子为-2或者2的时候,说明这是 最小不平衡树 根节点,
				//拿着这个根节点去调整(左旋,右旋,左右旋,右左旋)

				//又因为这里 是左子树插入,并且原先节点的平衡因子为-1(RH),就是此节点右边比左边深度 深1,
				//那么在此节点左子数插入后刚好左右平衡,则不需要再往上调整平衡因子了,
				//此时 	*taller = false;,也就是不再往上继续调整。到此处就平衡了。
				//同时,此节点平衡因子调为0
				*taller = false;
				break;
			}
		}
	}
	//同样,当 e>T->data 时,需要插入到以 T 为根结点的树的右子树中,同样需要做和以上同样的操作
	else
	{
		if (!InsertAVL(&(*T)->rchild, e, taller))
			return 0;
		//taller 是 判断 是否继续向上一层进行调整。
		//也就是 如果 下面的这些调整 就能够 使得 树 平衡 ,而且 各个平衡因子也都 已经调整 完毕,
		// 并且 再上之前的 下面节点的 平衡因子的 调整 不会影响 当前以及再往上 节点的数树的平衡以及 平衡因子
		//那么就将taller设为 false,表明 不再需要向上进行调整,直接返回上一层即可。
		if (*taller)
		{
			switch ((*T)->bf)
			{
			case LH:
				(*T)->bf = EH;
				//因为已知这是在右子树插入,那么原先的为1(LH)的父节点 会变为0(EH)
				//又因为 平衡因子为0(EH)的结点 不是 最小不平衡树的 根节点,所以只需要调整不平衡因子后 往上回退。
				//只有初次遇到 节点的平衡因子为-2或者2的时候,说明这是 最小不平衡树 根节点,
				//拿着这个根节点去调整(左旋,右旋,左右旋,右左旋)

				//又因为这里 是右子树插入,并且原先节点的平衡因子为1(LH),就是此节点左边比右边深度 深1,
				//那么在此节点右子数插入后刚好左右平衡,则不需要再往上调整平衡因子了,
				//此时 	*taller = false;,也就是不再往上继续调整。到此处就平衡了。
				//同时,此节点平衡因子调为0
				*taller = false;
				break;
			case EH:
				(*T)->bf = RH;
				//因为已知这是在右子树插入,那么原先的为0(EH)的父节点 会变为-1(RH)
				//又因为 平衡因子为-1(RH)的结点 不是 最小不平衡树的 根节点,所以只需要调整不平衡因子后 往上回退。
				//只有初次遇到 节点的平衡因子为-2或者2的时候,说明这是 最小不平衡树 根节点,
				//拿着这个根节点去调整(左旋,右旋,左右旋,右左旋)
				//因为这里需要进一步往上调整,所以	*taller = true;表明上一步需要继续调整
				*taller = true;
				break;
			case  RH:
				//每次退到有节点的平衡因子即将 变为-1,0,1之外的时候,进行变换。也就是遇到平衡因子为-2或者2的时候,
				//说明遇到了 最小不平衡树的根结点,然后拿着根节点去变换
				//又因为这里 是右子树插入,并且原先节点的平衡因子为-1(RH),就是此节点右边比左边深度 深1,
				//那么在此节点右子数插入后右边比左边深2,此时此节点的 平衡因子变为-2,
				//说明这是一颗最小不平衡树的 根节点,又因为是 右子数插入了结点,因此需要进行右子树的 变换

				//这里变换时需要 输入的结点 必定是 最小不平衡树的根节点,
				//也就是T的平衡因子 必须为-2 或者 2.又因为右旋,则需要平衡因子为-2的结点
				RightBalance(T);   

				//最小不平衡树调整完成后,不会影响 再往上的节点的平衡因子,因此 taller变为false,
				//后序退出递归的时候 直接退出,不再调整平衡因子
				//而左旋或者右旋过程中,左旋右旋函数内 会调整好节点的平衡因子。
			
				*taller = false;
				break;
			}
		}
	}
	return 1;
}
//判断现有平衡二叉树中是否已经具有数据域为 e 的结点
bool FindNode(BSTree root, ElemType e, BSTree* pos)
{
	BSTree pt = root;
	(*pos) = NULL;
	while (pt)
	{
		if (pt->data == e)
		{
			//找到节点,pos指向该节点并返回true
			(*pos) = pt;
			return true;
		}
		else if (pt->data>e)
		{
			pt = pt->lchild;
		}
		else
			pt = pt->rchild;
	}
	return false;
}
//中序遍历平衡二叉树
void InorderTra(BSTree root)
{
	if (root->lchild)
		InorderTra(root->lchild);

	printf("%d ", root->data);

	if (root->rchild)
		InorderTra(root->rchild);
}
int main()
{
	int i, nArr[] = { 1,23,45,34,98,9,4,35,23 };
	BSTree root = NULL, pos;
	bool taller;
	//用 nArr查找表构建平衡二叉树(不断插入数据的过程)
	for (i = 0; i<9; i++)
	{
		InsertAVL(&root, nArr[i], &taller);
	}
	//中序遍历输出
	InorderTra(root);
	//判断平衡二叉树中是否含有数据域为 103 的数据
	if (FindNode(root, 103, &pos))
		printf("\n%d\n", pos->data);
	else
		printf("\nNot find this Node\n");
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值