C++实现平衡二叉树

写在前面

平衡二叉树相关实现有点复杂,请先看视频学习基础知识
基本知识视频讲解请看王道考研
链接如下
【王道计算机考研 数据结构】 https://www.bilibili.com/video/BV1b7411N798/?p=76&share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
它主要是,手动去实现,因此有些不全面

然后代码实现还请看我本人的东西

还有一个 b站 蓝不过海呀 它动画做的不错
链接如下
【平衡二叉树(AVL树)】 https://www.bilibili.com/video/BV1tZ421q72h/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8

主要是学习数据结构,真的做什么请直接使用map、set等容器

平衡二叉树的定义

平衡二又树(Balanced Binary Tree),简称平衡树(AVL树)
树上任一结点的左子树和右子树的高度之差不超过1。
结点的平衡因子=左子树高-右子树高
显然,平衡二叉树是特殊的二叉排序树,即平衡二叉排序树。

我这里直接在二叉树的结点中加入平衡因子

template<class ElemType>
struct BTNode
{
	//平衡二叉树使用
	int balance;//平衡因子

	//关键值,查找使用

	ElemType data;//保存的数据

	BTNode<ElemType>* father;//父亲
	BTNode<ElemType>* lchild;//左子树 left child		
	BTNode<ElemType>* rchild;//右子树 right child

	//无参构造函数
	BTNode();
	//有参构造
	BTNode(const ElemType& theElement, BTNode* the_father = NULL, BTNode* the_left = NULL, BTNode* the_right = NULL);
};

然后,继承二叉排序树

//平衡二叉树
template<class ElemType>
class AVL :public BST<ElemType>
{
	//省略一些代码
}

前面的准备工作

实现插入、删除需要先做这些准备

左旋,右旋

左旋和右旋的操作不破坏二叉查找树(中序遍历不变),因此可以用来调整。

后面要使用这种方法!

注意目标是B!

左旋右旋
有点晕,所以直接定义四个指针,b指向主目标,gf,a,br指向相关的,然后根据图中的结果去修改

template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
	/*功能:左旋处理
	* 输入:
	* b:要处理的结点指针
	* 返回:
	* 其它:
	* 此函数只管左旋处理,别的不管
	* 请看清要处理的结点
	*/
	void L_Rotate(BTNode<ElemType>* b);
	/*功能:右旋处理
	* 输入:
	* b:要处理的结点指针
	* 返回:
	* 其它:
	* 此函数只管右旋处理,别的不管
	* 请看清要处理的结点
	*/
	void R_Rotate(BTNode<ElemType>* b);
	//省略一些代码……
}
template<class ElemType>
void AVL<ElemType>::L_Rotate(BTNode<ElemType>* b)//过程很难写注释,请看的图片
{
	//排除一些错误情况
	if (b == NULL) return;
	if (b == this->root) return;

	BTNode<ElemType>* a = b->father;
	BTNode<ElemType>* gf = a->father;
	BTNode<ElemType>* bl = b->lchild;

	if (gf != NULL)//如果A不是根结点
	{
		//“从上往下”处理
		if (gf->lchild == a)
		{
			gf->lchild = b;
		}
		else
		{
			gf->rchild = b;
		}

		b->father = gf, b->lchild = a;

		a->father = b, a->rchild = bl;

		if (bl != NULL)
		{
			bl->father = a;
		}
	}
	else//请注意根节点没有“爹”这种特殊情况
	{
		this->root = b;

		b->father = NULL, b->lchild = a;

		a->father = b, a->rchild = bl;

		if (bl != NULL)
		{
			bl->father = a;
		}
	}	
}

template<class ElemType>
void AVL<ElemType>::R_Rotate(BTNode<ElemType>* b)//过程很难写注释,请看图片
{
	//排除一些错误情况
	if (b == NULL) return;
	if (b == this->root) return;

	BTNode<ElemType>* a = b->father;
	BTNode<ElemType>* gf = a->father;	
	BTNode<ElemType>* br = b->rchild;

	if (gf != NULL)//如果A不是根结点
	{
		//“从上往下”处理
		if (gf->lchild == a)
		{
			gf->lchild = b;
		}
		else
		{
			gf->rchild = b;
		}

		b->father = gf, b->rchild = a;

		a->father = b, a->lchild = br;

		if (br != NULL)
		{
			br->father = a;
		}
	}
	else//请注意根节点没有“爹”这种特殊情况
	{
		this->root = b;

		b->father = NULL, b->rchild = a;

		a->father = b, a->lchild = br;

		if (br != NULL)
		{
			br->father = a;
		}
	}
}

关于更新平衡因子

插入删除啥的,要更新平衡因子
显然,对于正常的平衡二叉树,平衡因子只能是-1、0、1,当出现-2、2时就要进行调节

先说一个非常笨的办法,就直接遍历整个二叉树,每一个结点都去看它左右子树高度,然后得到平衡值
当然这太笨了,“看它左右子树高度”还得用递归

这时,二叉树结点中有指向父亲的指针的好处尤其凸显,并不需要“从上到下”,能直接找父亲,就可以“从下往上”,用循环就行

1、子树变高时

子树H+1,

子树变高

他爹要变化,他爷爷、太爷爷……看情况,存在不需要继续往上调整的情况,

我们需要找出什么情况下,不用继续往上调整

请看下面

左子树变高1

平衡因子+1

左子树变高

结论如果出现–2->-1 或1->0就不用在往上变化了

右子树变高1

平衡因子-1

右子树变高

结论如果出现 2->1 或1->0就不用在往上变化了

子树变高时的代码
template<class ElemType>
class AVL :public BST<ElemType>
{
protected:

	//这几个函数在,插入删除啥的时候要用,不能随便调用,所以设置为私有	

	/*功能:认为某个结点的子树高度增加1,向上调整平衡因子
	* 输入:
	* theAdd:认为高度增加1的子树
	* leftOrRight:开始变高的是左子树还是右子树,左请传入true,右请传入false
	* 返回:
	* 其它:包含这个结点
	*/
	void adjustBlanceAdd(BTNode<ElemType>* theAdd, bool leftOrRight);
	//省略一些代码……
}

template<class ElemType>
void AVL<ElemType>::adjustBlanceAdd(BTNode<ElemType>* theAdd, bool leftOrRight)
{
	if (leftOrRight == true)//如果“左面”变高
	{
		theAdd->balance += 1;
		if (theAdd->balance == -1 || theAdd->balance == 0)//-2->-1 -1->0,不会向上传导,跳出循环
			return;
	}
		
	else//如果“右面”变高
	{
		theAdd->balance -= 1;
		if (theAdd->balance == 1 || theAdd->balance == 0)//2->1 1->0,不会向上传导,跳出循环
			return;
	}		

	if (theAdd == this->root) return;//根节点,直接结束
	
	//往上调整平衡因子,直到不用再调整(请看笔记中的图片,实在不知道怎么说道理了)

	//注意第一个根据leftOrRight调整过了,接下来弄上面
	BTNode<ElemType>* n = theAdd;//遍历用的指针,作为“孩子”
	BTNode<ElemType>* nf = theAdd->father;//遍历用的指针,作为n的“父亲”
	while (nf != NULL)
	{
		
		//其它情况会向上传导,继续循环

		if (nf->lchild == n)//对于nf来说,“左面”变高
		{			
			nf->balance += 1;
			if (nf->balance == -1 || nf->balance == 0)//-2->-1 -1->0,不会向上传导,跳出循环
				break;
		}		
		else//对于nf来说,“右面”变高
		{
			nf->balance -= 1;
			if (nf->balance == 1 || nf->balance == 0)//2->1 1->0,不会向上传导,跳出循环
				break;
		}
		n = n->father;
		nf = nf->father;
	}
}

2、子树变矮

同样的
子树变矮

他爹要变化,他爷爷、太爷爷……看情况,存在不需要继续往上调整的情况,

我们需要找出什么情况下,不用继续往上调整

请看下面

左子树变矮1

平衡因子-1
左子树变矮

结论如果出现 0->-1 或-1->-2就不用在往上变化了

右子树变矮1

平衡因子+1

右子树变矮

结论如果出现 0->1 或1->2就不用在往上变化了

子树变矮时的代码

template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
	/*功能:认为某个结点的子树高度减少1,向上调整平衡因子
	* 输入:
	* theSub:认为高度减小1的子树
	* leftOrRight:开始变矮的是左子树还是右子树,左请传入true,右请传入false
	* 返回:
	* 其它:包含这个结点
	*/
	void adjustBlanceSub(BTNode<ElemType>* theSub, bool leftOrRight);
//省略一些代码……
}
template<class ElemType>
void AVL<ElemType>::adjustBlanceSub(BTNode<ElemType>* theSub, bool leftOrRight)
{
	if (leftOrRight == true)//如果“左面”变矮
	{
		theSub->balance -= 1;
		if (theSub->balance == -1 || theSub->balance == -2)//0->-1或-1->-2,不会向上传导,结束
			return;
	}		
	else//如果“右面”变矮
	{
		theSub->balance += 1;
		if (theSub->balance == 1 || theSub->balance == 2)//0->1或1->2,不会向上传导,跳出循环
			return;
	}

	if (theSub == this->root) return;

	//注意第一个根据leftOrRight调整过了,接下来弄上面

	//往上调整平衡因子,直到不用再调整(请看笔记中的图片,实在不知道怎么说道理了)
	BTNode<ElemType>* n = theSub;//遍历用的指针,作为“孩子”
	BTNode<ElemType>* nf = theSub->father;//遍历用的指针,作为n的“父亲”
	while (nf != NULL)
	{		
		if (nf->lchild == n)//对于nf来说,“左面”变矮
		{
			nf->balance -= 1;
			if (nf->balance == -1 || nf->balance == -2)//0->-1或-1->-2,不会向上传导,结束
				break;
			//其它情况会向上传导,继续循环			
		}
		
		else//对于nf来说,“右面”变矮
		{
			nf->balance += 1;
			if (nf->balance == 1 || nf->balance == 2)//0->1或1->2,不会向上传导,跳出循环
				break;
			//其它情况会向上传导,继续循环			
		}
		n = n->father;
		nf = nf->father;
	}
}

寻找最小不平衡子树

从某个结点开始向上,寻找最小不平衡子树
这个比较简单,就一直往上,找到2或-2就行

template<class ElemType>
class AVL :public BST<ElemType>
{
protected:
	/*功能:从某结点(包含这个)开始,向上查找最小不平衡子树
	* 输入:
	* start:开始查找的结点指针
	* s:保存最小不平衡子树的指针
	* 返回:
	* true:发现最小不平衡子树
	* false:没有出现不平衡
	* 其它:
	*/
	bool findUnblance(BTNode<ElemType>* start, BTNode<ElemType>*& s);
//省略一些代码……
}

template<class ElemType>
bool AVL<ElemType>::findUnblance(BTNode<ElemType>* start, BTNode<ElemType>*& s)
{
	bool the_find = false;

	BTNode<ElemType>* n = start;//遍历用的指针	
	while (n != NULL)
	{
		if (n->balance >= 2 || n->balance <= -2)
		{
			the_find = true;
			s = n;//记录最小不平衡子树位置
			break;
		}
		n = n->father;
	}	
	return the_find;
}

调整最小不平衡子树(LL、LR、RR、RL)

也就是平衡因子2 -2的

主要关注平衡因子!

L?类,A的平衡因子为+2

A的平衡因子为2,A的左子树比右子树高

1)LL平衡旋转(右单旋转)

LL:A的左孩子(记为B)的左子树比右子树高

需要一次向右的旋转操作。

情况1:B的平衡因子+1

图中高度H大于等于0
情况1:B的平衡因子+1
最小不平衡子树高度H+3->调整后H+2

情况2:B的平衡因子0

图中高度H大于等于1
LL情况2

2)LR平衡旋转(先左后右双旋转)

记A为最小不平衡子树根结点

LR:A的左孩子的右子树比左子树高

需要进行两次旋转操作,先左旋转后右旋转。

记A的左孩子为B,记B的右孩子为C

先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,

然后再把该C结点向右上旋转提升到A结点的位置

最小不平衡子树高度H+3->调整后H+2

情况1:C的平衡因子-1

C的右子树比左子树高

图中高度H大于等于1

LR情况1

情况2:C的平衡因子+1

C的左子树比右子树高

图中高度H大于等于1

LR情况2

情况3:C平衡因子为0

图中高度H大于等于0
LR情况3

H为0时代表,BL为空,C本身就是新插入的如图所示
LR情况3特殊

R?类,A的平衡因子为-2

3)RR平衡旋转(左单旋转)

RR:在A的右孩子的右子树中插入导致不平衡

将A的右孩子B向左上旋转代替子由-1减至-2,
A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树

情况1:B的平衡因子-1

图中高度H大于等于0
RR情况1
​ 最小不平衡子树高度H+3->调整后H+2

情况2:B的平衡因子0

RR情况2

4)RL平衡旋转(先右后左双旋转)

记A为最小不平衡子树根结点

RL:在A的右孩子的左子树中插入导致不平衡

由于在A的右孩子 ®的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,

先右旋转后左旋转。

记A的右孩子为B,记B的左孩子为C

先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,

然后再把该C结点向左上旋转提升到A结点的位置

情况1:C的平衡因子+1

RL情况1
​ 最小不平衡子树高度H+3->调整后H+2

情况2:C的平衡因子-1

RL情况2

情况3:C平衡因子为0

图中高度H大于等于0
在这里插入图片描述

H为0时代表,BL为空,C本身就是新插入的如图所示
在这里插入图片描述

打代码提示:

找到最小不平衡子树后,可以直接根据初始时A、B、C的平衡因子判断是那种“不平衡的类型”

平衡值的调整结果,也可以直接使用图中的结论

并不需要去看子树到底多高

调整最小不平衡子树代码
template<class ElemType>
class AVL :public BST<ElemType>
{
	/*功能:调整最小不平衡子树
	* 输入:
	* A:最小不平衡子树的根结点指针
	* 返回:
	* 调整后的,子树的根节点指针
	* 其它:
	* 最小不平衡子树调整后,高度-1
	* 调整时,左旋或右旋的目标时A的孩子,而不是A
	* 最后A就不是子树的根指针了,指不定旋转到哪里去了!
	*/
	BTNode<ElemType>* adjustMinUnblance(BTNode<ElemType>* A);
	//省略一些代码
}

template<class ElemType>
BTNode<ElemType>* AVL<ElemType>::adjustMinUnblance(BTNode<ElemType>* A)
{
	//判断不平衡类型,并调整(原理请看笔记中的图片)

	//L?型
	if (A->balance == 2)
	{
		BTNode<ElemType>* B = A->lchild;
		//LL型
		if (B->balance != -1)
		{
			this->R_Rotate(B);
			if (B->balance == 1)//图中的LL情况1
			{
				B->balance = 0;
				A->balance = 0;
				//cout << "LL情况1" << endl;
			}
			else if (B->balance == 0)//图中的LL情况2
			{
				B->balance = -1;
				A->balance = 1;
				//cout << "LL情况2" << endl;
			}

			return B;
		}
		//LR型
		else if (B->balance == -1)
		{
			BTNode<ElemType>* C = B->rchild;
			this->L_Rotate(C);//注意这个函数没有调整过平衡因子
			this->R_Rotate(C);//注意这个函数没有调整过平衡因子
			if (C->balance == -1)//图中的LR情况1
			{
				C->balance = 0;
				B->balance = 1;
				A->balance = 0;
				//cout << "LR情况1" << endl;
			}
			else if (C->balance == 1)//图中的LR情况2
			{
				C->balance = 0;
				B->balance = 0;
				A->balance = -1;
				//cout << "LR情况2" << endl;
			}
			else if (C->balance == 0)//图中的LR情况3
			{
				C->balance = 0;
				B->balance = 0;
				A->balance = 0;
				//cout << "LR情况3" << endl;
			}
			return C;
		}
	}
	//R?型
	else if (A->balance == -2)
	{
		BTNode<ElemType>* B = A->rchild;
		//RR型
		if (B->balance != 1)
		{
			this->L_Rotate(B);
			if (B->balance == -1)//图中的RR情况1
			{
				B->balance = 0;
				A->balance = 0;
				//cout << "RR情况1" << endl;
			}
			else if (B->balance == 0)//图中的RR情况2
			{
				B->balance = 1;
				A->balance = -1;
				//cout << "RR情况2" << endl;
			}
			return B;
		}
		//RL型
		else if (B->balance == 1)
		{
			BTNode<ElemType>* C = B->lchild;
			this->R_Rotate(C);
			this->L_Rotate(C);
			if (C->balance == 1)//图中的RL情况1
			{
				C->balance = 0;
				B->balance = -1;
				A->balance = 0;
				//cout << "RL情况1" << endl;
			}
			else if (C->balance == -1)//图中的RL情况2
			{
				C->balance = 0;
				B->balance = 0;
				A->balance = 1;
				//cout << "RL情况2" << endl;
			}
			else if (C->balance == 0)//图中的RL情况3
			{
				C->balance = 0;
				B->balance = 0;
				A->balance = 0;
				//cout << "RL情况3" << endl;
			}
			return C;
		}
	}
	return NULL;//莫名奇妙的返回空
}

平衡二叉树的插入

有了前面的全部准备工作,插入就可以容易实现
步骤如下:
1.进行普通的二叉排序树插入
2.调整平衡因子
3.寻找最小不平衡子树
有:调节最小不平衡子树,调整平衡因子
没有:结束

template<class ElemType>
class AVL :public BST<ElemType>
{
public:	
	/*功能:在本平衡二叉树中插入元素e
	* 输入:
	* e:要插入的元素
	* 返回:
	* 成功:true
	* 失败:false(原本存在元素e)
	* 其它:
	*/
	bool Insert(ElemType e);
	//省略一些代码
}

template<class ElemType>
bool AVL<ElemType>::Insert(ElemType e)
{
	//插入,调用普通二叉排序树的插入
	BTNode<ElemType>* theNew = BST<ElemType>::Insert_p(e);
	
	if (theNew == NULL) return false;//原本存在元素e,直接返回false

	if (theNew == this->root) return true;//如果新插入的是根节点(原本树为空),直接返回true

	//向上调整平衡值(用长高的)		
	if (theNew->father != NULL)//当它有爹,即不是根节点,如果是根节点,就不用调整啥了
	{		
		this->adjustBlanceAdd(theNew->father, leftOrRight(theNew));//从它爹开始调整平衡值
	}	

	//寻找最小不平衡子树
	BTNode<ElemType>* A = NULL;//保存最小不平衡子树的指针	
	bool theFind = this->findUnblance(theNew, A);
	if (theFind == false) return true;//没有出现不平衡,直接返回true

	/*注意,调整最小不平衡子树后,子树高度-1*/		
	//调整最小不平衡子树
	BTNode<ElemType>* newA = NULL;//保存原最小不平衡子树根指针
	newA = adjustMinUnblance(A);//完事之后,最小不平衡子树的平衡因子也就调整好了

	//向上调整平衡因子(用变矮的)
	if (newA->father != NULL)//当它有爹,即不是根节点,如果是根节点,就不用调整啥了
	{		
		this->adjustBlanceSub(newA->father, leftOrRight(newA));//从它爹开始调整平衡值
	}	

	return true;

	//利用插入时的性质:插入时,只要调整最小不平衡子树,其它部分就不受影响,可以优化一点	
}

平衡二叉树的删除

平衡二叉树的删除操作:
删除结点后,要保持二叉排序树的特性不变(左<中<右)
若删除结点导致不平衡,则需要调整平衡
请注意:删除中调整最小不平衡子树后,可能出现新的不平衡,应当继续调整

有了前面的准备工作后,删除也容易实现

1、删除结点 (方法同“二叉排序树”),调整平衡值
2、一路向上寻找最小不平衡子树
找不到就结束
如果找到重复以下步骤,知道不在出现最小不平衡子树
3、调整最小不平衡子树
4、从此最小不平衡子树父结点往上调整平衡值,然后从最小不平衡子树父结点向上寻找最小不平衡子树
如果出现,重复3、4

template<class ElemType>
class AVL :public BST<ElemType>
{
public:	
	/*功能:在本平衡二叉排序树中删除元素e
	* 输入:
	* e:要删除的元素
	* 返回:
	* true:成功
	* false:失败(原本就不存在元素e)
	* 其它:会保证排序树结构不被破坏
	*/
	bool Delete(ElemType e);
	//省略一些代码
}

template<class ElemType>
bool AVL<ElemType>::Delete(ElemType e)
{
	bool isSuccess = false;
	BTNode<ElemType>* realDeleteFather = NULL;//保存真实删除的结点的父亲结点
	bool LoR = false;//保存真实删除的结点是左孩子还是右孩子

	isSuccess = BST<ElemType>::Delete_p(e, realDeleteFather, LoR);//调用平衡二叉树的相关删除函数

	if (isSuccess == false) return false;//删除失败,直接返回失败

	if (realDeleteFather == NULL) return true;//把根节点删了,直接返回成功

	//调整平衡因子
	this->adjustBlanceSub(realDeleteFather, LoR);

	BTNode<ElemType>* n = realDeleteFather;//用来遍历的结点指针
	BTNode<ElemType>* unBlance = NULL;//保存最小不平衡	
	while (findUnblance(n, unBlance) == true)//寻找最小不平衡子树,如果有最小不平衡子树,就循环
	{
		n = this->adjustMinUnblance(unBlance);//调整最小不平衡子树,并将调整后子树根节点赋给n

		if (n->father != NULL)//调整平衡因子
		{
			this->adjustBlanceSub(n->father, leftOrRight(n));//从它爹开始调整平衡值
		}		
	}
	return true;
}
  • 41
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cctv1324

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值