平衡二叉树


 

包含的操作:

创建树,清空,搜索二叉树插入节点,返回树的高度,返回树的最大值,返回树的最小值,返回某个节点的父节点,删除某个节点,四种旋转操作LL、RR、LR、RL。

 

平衡二叉树的简介:

平衡二叉树是一种特殊的二叉搜索树(即每个节点的左子树的任何元素都比当前节点元素的值小,而右子树的任何元素都比当前节点元素的值大),但普通的二叉树在某些极端的情况下,会退化成近乎链状,如下图1中和图2中的两颗树的元素个数相等,但是图2中的树的查找效率明显将高于图1中的树。

                      图1                                                                                                               图2

为了降低这种查找的时间复杂度的预期,提出了平衡二叉树的概念,即树中的每个节点的左右子树的高度差的绝对值不超过1,这种高度差称为平衡因子(平衡因子=左子树高度减去右子树的高度)。由此图1中即是一棵平衡的二叉树,而图2不是,因为它的根节点的平衡因子大小为-2。

通过观察我们可以发现,只要将图2中的树向左“弯折”,即使图1的结果(如图3),而这种操作是可以在计算机中通过代码来实现的,以下是具体的流程讲解和代码实现。

在此说明,平衡二叉树的算法有多种,本文只讲有关的AVL树。

一些基本或辅助性质的二叉树操作

//二叉树的节点
struct BiTreeNode
{
	int data;
	BiTreeNode *Lchild;
	BiTreeNode *Rchild;

};
//将树清空
void MakeEmpty(BiTreeNode *root)
{
	if (root != NULL)
	{
		MakeEmpty(root->Lchild);
		MakeEmpty(root->Rchild);
		free(root);
	}
	
		return;
};

//二叉树的高度
int BiTreeHeight(BiTreeNode *root)
{
	int right, left;
	BiTreeNode *p = root;
	if (p != NULL)
	{
		left = BiTreeHeight(p->Lchild);
		right = BiTreeHeight(p->Rchild);
		if (left >= right)
			return left + 1;
		else
			return right + 1;
	}
	else
	return 0;
}

//二叉树的创建
BiTreeNode* CreateTree(BiTreeNode *root)
{
	int i;
	scanf("%d",&i);
	if (i != 0)
	{
		root = (BiTreeNode*)malloc(sizeof(BiTreeNode));
		root->data = i;
		root->Lchild  = CreateTree(root->Lchild);
		root->Rchild = CreateTree(root->Rchild);
		return root;
	}
	else
		return NULL;
}

//二叉树的先序遍历
void PreTravelOrder(BiTreeNode *root)
{
	printf("%d ",root->data);
	if (root->Lchild!=NULL)
	PreTravelOrder(root->Lchild);
	if (root->Rchild!=NULL)
	PreTravelOrder(root->Rchild);
}

//二叉树的最大值:此写法不能传入空树
int findMax(BiTreeNode *root)
{
	BiTreeNode *p = root;
	while (p->Rchild != NULL)
	{
		p = p->Rchild;
	}
	return p->data;
}

//二叉树的最小值:此写法不能传入空树
int findMin(BiTreeNode *root)
{
	BiTreeNode *p = root;
	while (p->Lchild != NULL)
	{
		p = p->Lchild;
	}
	return p->data;
}

//树中插入元素(此段代码的插入符合二叉搜索树,但并未处理插入后失衡的情况)
BiTreeNode* Insert(int a, BiTreeNode *root)
{
	BiTreeNode *p,*pi;
	pi = root;
	p = (BiTreeNode*)malloc(sizeof(BiTreeNode));
	p->data = a;
	p->Lchild = NULL;
	p->Rchild = NULL;
	if (root == NULL)
	{
		printf("This is a null tree!\n");
		return p;
	}
	while (1)
	{
		if (a == root->data)
		{
			printf("The element is already exist\n");
			free(p);
			return pi;
		}

		else if (a < root->data)
		{
			if (root->Lchild == NULL)
			{
				root->Lchild = p;
				return pi;
			}
			else
			{
				root = root->Lchild;
				continue;
			}
		}

		else if (a > root->data)
		{
			if (root->Rchild == NULL)
			{
				root->Rchild = p;
				return pi;
			}
			else
			{
				root = root->Rchild;
				continue;
			}
		}
	}
	return pi;
}

//找到某个节点的父节点
BiTreeNode* FindFather(BiTreeNode *son,BiTreeNode *root)
{
	BiTreeNode *p = son, *pi;
	pi = (BiTreeNode*)malloc(sizeof(BiTreeNode));
	pi = root;
	if (root == NULL)//传入的是空树
	{
		printf("Error\n");
		return NULL;
	}
	if (son == root)//查找的子节点是树的根节点
	{
		printf("\nNo Father Node!\n");
		return NULL;
	}
	if ((root->Lchild != NULL) && (root->Rchild = NULL))//传入的树只有一个节点
	{
		printf("The tree only one Node!\n");
		return NULL;
	}
	while (1)
	{
		if ((pi->Lchild->data == son->data) || (pi->Rchild->data == son->data))
		{
			return pi;
		}
		else if (son->data<pi->data)//必然位于其左子树
		{
			if (pi->Lchild == NULL)
			{
				printf("No Father Node!\n");
				return NULL;
			}
			else
				pi = pi->Lchild;
		}
		else if (son->data>pi->data)//必然位于其右子树
		{
			if (pi->Rchild == NULL)
			{
				printf("No Father Node!\n");
				return NULL;
			}
			pi = pi->Rchild;
		}
	}
}

四种旋转操作:RR、LL、RL、LR

LL、RR旋转

如上图中所展示的便是一种RR旋转,其基本概念是:平衡被破坏的节点(5)是右子树高度大于左子树,且导致平衡破坏的节点(7)是位于(5)的右子树的右子树上,类似的,我们如图4可以推导出LL旋转的规律。

(图片中节点上数字为节点的平衡因子大小)

代码中实际的基本操作为:

1.  传入不平衡节点A

2.  记录下A、B、E三个节点。

3.  将B的右儿子替换为A,再将A的右儿子替换为E。

4.  返回新的根节点B。

RR旋转的操作与此十分类似,在此不多做赘述。

以下是两种旋转的代码

//LL旋转
BiTreeNode* LL(BiTreeNode *blance)
{
	BiTreeNode *temp, *blances_Lchild;
	//先记录下需要平衡节点的左子树 和 左子树的右子树
	blances_Lchild = blance->Lchild;
	temp = blances_Lchild->Rchild;
	//将需要平衡的节点挂在左子树的右子树上,原本的右子树被覆盖,保存在temp中
	blances_Lchild->Rchild = blance;
	//再将temp挂在需要平衡节点的左子树上
	blance->Lchild = temp;
	
	return blances_Lchild;
}

//RR旋转
BiTreeNode* RR(BiTreeNode *blance)
{
	BiTreeNode *temp, *blances_Rchild;
	//先记录下平衡节点的右子树 和 右子树的左子树
	blances_Rchild = blance->Rchild;
	temp = blances_Rchild->Lchild;
	//将需要平衡的节点挂在右子树的左子树上,原本的左子树被覆盖,保存在temp中
	blances_Rchild->Lchild = blance;
	//再将temp挂在需要平衡节点的右子树上
	blance->Rchild = temp;
	
	return blances_Rchild;
}

RL、LR旋转

 

双旋转本质上是两次单旋转的结合。

例如LR旋转的过程:先将需要平衡的节点的左节点的右子树进行一次RR旋转,右旋过后的树变成了需要LL旋转的树,再LL旋转一次即可。

如图5


(图片中节点上数字为节点的平衡因子大小)

代码中实际的基本操作为:

1.  对A的左子树B进行RR旋转。

2.  对A本身进行LL进行LL旋转。

RL旋转同理

 

以下是两种旋转的代码

//LR旋转
BiTreeNode* RL(BiTreeNode *root)
{
	//先左单旋,后右单旋
	BiTreeNode *temp = root;
	root->Rchild = LL(root->Rchild);
	root = RR(root);

	return root;
}

//RL旋转
BiTreeNode* LR(BiTreeNode *root)
{
	//先右单旋,后左单旋
	BiTreeNode *temp = root;
	root->Lchild = RR(root->Lchild);
	root = LL(temp);

	return root;
}

最后,删除操作

由于每次删除都可能引起树的不平衡,所以必须对树的节点在递归中进行逐级旋转,以下是详细代码,具体操作见注释


//删除
BiTreeNode* Detele(BiTreeNode *root,int x)
{
	BiTreeNode *temp = root,*temp2;
	//查找的结果是空,返回原树
	if (root==NULL)
	{
		printf("Element do not find\n");
		return root;
	}

	//x位于其左子树里,在左子树里进行查找
	if (x < temp->data)
	{
		root->Lchild = Detele(root->Lchild, x);
		//删除过后进行旋转,先判断其左子树在删除后是否失去平衡
		if (BiTreeHeight(temp->Rchild) - BiTreeHeight(temp->Lchild) == 2)
		{
			//再判断需要何种旋转方式
			//由于是右子树大于左子树,所以只能是RL或RR旋
			if (((root->Rchild->Lchild) != NULL) && (BiTreeHeight(root->Rchild->Lchild)>BiTreeHeight(root->Rchild->Rchild)))
			{
				root = RL(root);
			}
			else
				root = RR(root);
		}
		return root;
	}

	//x位于其右子树里,在右子树里进行查找
	else if (x > temp->data)
	{
		root->Rchild = Detele(root->Rchild, x);
		if (BiTreeHeight(temp->Lchild) - BiTreeHeight(temp->Rchild) == 2)
		{
			//删除过后进行旋转,先判断其右子树在删除后是否失去平衡
			//由于是左子树大于右子树,所以只能是LR或RR旋
			if (((root->Lchild->Rchild) != NULL && (BiTreeHeight(root->Lchild->Rchild) > BiTreeHeight(root->Lchild->Lchild))))
			{
				root = LR(root);
			}
			else
				root = LL(root);
		}
		return root;
	}
	//x如果恰好等于该节点的值
	else
	{
		//有两个儿子时,,解决方式是从其右儿子中查找一个最小值进行替换
		if (root->Lchild != NULL && root->Rchild != NULL)
		{
			//找到右子树中的最小值
			while (temp->Lchild != NULL)
			temp = temp->Lchild;
			//将最小值赋给这个节点
			root->data = temp->data;
			//找到最小节点的父节点,并删除最小节点
			temp2 = FindFather(temp,root);
			temp2->Lchild = NULL;
			free(temp);
			//删除后该子树的根节点可能不平衡,需要进行旋转
			if (BiTreeHeight(temp->Lchild) - BiTreeHeight(temp->Rchild) == 2)
			{
				//删除过后进行旋转,先判断其右子树在删除后是否失去平衡
				//由于是左子树大于右子树,所以只能是LR或RR旋
				if (((root->Lchild->Rchild) != NULL && (BiTreeHeight(root->Lchild->Rchild) > BiTreeHeight(root->Lchild->Lchild))))
				{
					root = LR(root);
				}
				else
					root = LL(root);
			}
		}
		//只有一个或者没有儿子时
		else
		{
			//没有左儿子,此时可能只有右儿子,或者没有儿子,无论右儿子是否为空,只需要将右儿子拉上来即可
			if (root->Lchild == NULL)
				root = root->Rchild;
			//此时同理
			else if (root->Rchild == NULL)
				root = root->Lchild;

		}
		return root;
	}
}


如若有错,欢迎指正留言。

如有雷同,那当时看的博客多半就是您的。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值