【数据结构】平衡二叉树(原理 + 旋转 + 代码详解)

289 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情 >>

一、平衡二叉树是什么?

1、定义

平衡二叉树:左右子树高度差的绝对值不超过1的二叉查找树 特殊地,空树也是平衡二叉树

2、失衡

  1. 平衡因子=该节点的左子树-右子树的高度差
  2. 当平衡二叉树中存在某个结点的平衡因子的绝对值大于1,则该平衡树失衡。
  3. 平衡因子大于2,或者小于-2的结点称为失衡结点
  4. 最小失衡子树:从新插入结点开始向上寻找第一个失衡结点,以该结点为根的子树就称为最小失衡子树。

二、相关操作

1.平衡二叉树的结构

typedef struct BBSTNode{	
//平衡二叉树的结点,包括数据域、平衡因子、左右孩子 
	RcdType data;
	int bf;		//平衡因子 
	struct BBSTNode *lchild,rchild;
}*BBSTree;		//平衡二叉树
// BBSTree lc;定义了一个BBSTree类型的指针 

2、失衡的四种情况及基本操作

在这里,我们首先需要明确一个点:就整棵树而言调整平衡的方法可能不唯一,哪怕是同一种类型都可能存在多种调整平衡的方法,但是: 一棵平衡二叉树是因为新插入结点而导致不平衡的,所以我们只需要对最小失衡子树进行平衡调整,整棵树也会跟着平衡(插入结点导致高度改变,平衡调整是使得高度复原),所以并不需要整棵树进行调整。因为如果一个树很大,调整方法很多,就很繁琐,也很容易乱。

  1. "LL型":在最小失衡子树的左孩子的左子树上插入了新结点。

在这里插入图片描述 右旋调整:

//对最小失衡子树p进行右旋调整 
void R_Rotate(BBSTree &p) {
	BBSTree lc = p->lchild;	
	p->lchild = lc->rchild;
	lc->rchild = p;
	p = lc;
} 

"LL型":

		最小失衡子树的根节点A的平衡因子		20
								其左孩子B的平衡因子		10

2. "RR型":在最小失衡子树的右孩子的右子树上插入了新结点。 在这里插入图片描述

左旋调整:

//对最小失衡子树p进行左旋调整 
void L_Rotate(BBSTree &p) {
	BBSTree rc = p->rchild;	// 
	p->rchild = rc->rchild;
	rc->lchild = p;
	p = rc;
} 

"RR型":

	最小失衡子树的根节点A的平衡因子		-20
				其左孩子B的平衡因子		-10

3. "LR型":在最小失衡子树的左孩子的右子树上插入新结点。

先左旋,再右旋: 在这里插入图片描述 "LR型":

		最小失衡子树的根节点A的平衡因子		22	→	-1
					其左孩子B的平衡因子		-100
			其左孩子的右子树C的平衡因子		 120	
		

4. "RL型":在最小失衡子树的右孩子的左子树上插入结点。

先右旋,在左旋 在这里插入图片描述 "RL型":

		最小失衡子树的根节点A的平衡因子		-2	→	-20
					其左孩子B的平衡因子		-1	→	-1	→	-1
			其左孩子的右子树C的平衡因子		 1	→	-10	

对于平衡因子的变化,其实也不需要硬要记,可以适当举出些简单例子,即可知道,如下图: 在这里插入图片描述 对于“LR型”和“RL型”是比较有意思的,从上图可以对比出来,“LR型”的两种情况,分别进行平衡调整之后,插入结点的位置都是一样的。相信很多小伙伴们,可能会问在第二种情况中,第一次调整的时候已经平衡了,为什么还需要第二次调整呢?嘿嘿,我想这应该是一种方法归纳的原因吧,你们想想,我们在写代码的过程中可能会遇到各种各样的类型,如果每一种类型都用不同的调整方法,那还怎么递归,怎么调用呢?所以啊,这是将“LR型“的两种情况的调整方法统一成一个了。

不知道有没有小伙伴会疑惑为什么只有四种情况呢(LL、RR、LR、RL)? 对于这个问题,小姐姐建议各位去看看这篇文章,分析、总结都挺到位的。

原链接:详解平衡二叉树的失衡类型划分及调整策略设计

3、左、右平衡处理的操作

在进行左、右平衡处理操作介绍之前,我觉得挺有必要先总结以下,以上四种类型的最小失衡子树在平衡调整前后平衡因子的变化: 不管是“LL型”还是“RR型”,最终平衡因子发生变化的都是孩子和子树,并且最终平衡因子都是变成0. 另外,“LR型”和“RL型”就特殊一点,这两种类型不仅涉及到孩子和子树的平衡因子的变化,还有子树的孩子的变化。 “LR型”:可以看作是一次“左旋”

//左平衡处理

#define LH +1 	//左高
#define RH -1	//右高
#define EH 0 	//等高
void LeftBalance (BBSTree &T) {
	BBSTree lc,rd;
	lc = T->lchild;
	switch(lc->bf) {
		case LH:	T->bf = lc->bf = EH;	R_Rotate(T),break;
		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;
			L_Rotate(T->lchild);
			R_Rotate(T);
			break;
	}
} 

右平衡:不同情况下,平衡因子变化有些不一样,但是只要属于这种类型则调整平衡方法可统一,即为:先左旋,再右旋。


//对因在右子树上新插入结点导致失衡的最小失衡子树T进行右平衡处理 
void RightBalance(BSTree &T)     //对以指针T所指结点为根的二叉树作右平衡旋转处理
{
	BSTree rc,ld;
	rc=T->rchild;
	//树是由平衡到失衡的,即T->bf=-2,右边插入,故不可能为2 
	//所以可以直接有T的左右孩子的平衡因子来判断是何种类型 
	switch(rc->bf)	 
	{
	/* 
	当右孩子的平衡因子为-1时,-2,-1属于“RR型”
		右孩子的平衡因子为1时,-2,1属于“RL型”
	右孩子的平衡因子不可能为0,如果右孩子平衡因子为0,T树并没有失衡	
	*/
	case RH:
		T->bf=rc->bf=EH;
		L_Rotate(T);
		break;
	case LH:	
	//到这已经可以1知道是“RL型”了,但是“RL型”的插入有三种情况
	//这三种情况只有平衡因子的变化有些不同,而调整平衡的方法统一为先右旋后左旋 
		ld=rc->lchild;
		switch(ld->bf)
		{
		case RH:T->bf=LH;rc->bf=EH;break;
		case EH:T->bf=rc->bf=EH;break;
		case LH:T->bf=EH;rc->bf=RH;break;
		}
		ld->bf=EH;
		R_Rotate(T->rchild);
		L_Rotate(T);
	}
}

4、平衡二叉树的删除操作详解

提醒:

在这里,给大家提个醒吧,一般“LL型”、“RR型”是比较固定的就是不管树是什么样的形状,只需要挑最左、最右插入即可,可是“RL型”和“LR 型”就不一样了,比如“RL型”它是在右孩子的左子树上插入结点是没错的,可是到底是在左子树的哪里插入(左、右)?左子树的原来状态是如何(-1,01)?也正因为这样,所以在右平衡处理的时候要对三种情况进行分情况讨论。