平衡二叉树balanced binary tree---(AVL)

AVL树的由来以及概念

1.AVL树的由来:平衡二叉树的本质还是二叉搜索树,但是由于如果对二叉搜索树进行有序的树进行插入时,那么就有可能出现最坏的条件,当我们去寻找一个树的时候,时间复杂度就有可能变成(n-1)/2,这时候我们制造的二叉搜索树便没了多大用处了,所以我们诞生了平衡二叉树。
2.AVL树的特点:当二叉搜索树进行插入新节点的时候,如果能够保证左右子树的高度差的绝对值不超过1(那么就要通过调整完成),即可降低树的高度,从而保证了搜索的效率。
3.AVL树的性质
①:其左右子树都是AVL树
②:左右子树的高度差绝对值不超过1(-1,1,0)
4.搜索性能
平衡二叉树的一种绝对平衡的二叉树,那么当其有n个节点的时候,其高度就可以保持在log2(n),并且它的搜索效率为O(log2(n))。

AVL树的节点插入和删除

1.节点选择:
首先我们要明白的是,在搜索二叉树中,我们是通过递归进行实现的,但是在AVL树中,我们是要通过迭代方式实现,并且其节点中增加了一个新的元素,为
平衡因子(平衡因子的大小本篇默认是右子树高度减左子树高度)
,所以其节点的实现方式如下:

template<class Type>
class AVLNode
{
	friend class AVL<Type>;
public:
	AVLNode(Type d = Type(), AVLNode<Type>* left = nullptr, AVLNode<Type>* right = nullptr)
		:data(d), leftchild(left), rightchild(right), bf(0)
	{}
	~AVLNode()
	{}
private:
	Type data;
	AVLNode<Type>* leftchild;
	AVLNode<Type>* rightchild;
	int bf;//调节因子,用来判断该节点是否平衡
};

平衡因子的初始化为0,对插入节点进行操作时,再对其进行修改。
2.插入操作
1.插入操作的步骤:
①:找到合适的位置,对需要插入的节点进行插入操作。
②:从插入的节点开始向上排查,是否因为插入了新的节点让该数不平衡了,如果不平衡了,那么就必须进行修改。
2.
步骤①:由于我们是通过迭代器实现的,所以我们必须使用栈,通过栈的特点进行操作,代码如下:

while (p != nullptr)//用非递归的方式去插入数据,目的也是为了能对搜索树进行修改
	{
		if (p->data == v)
			return;

		pr = p;
		s.push(pr);//入栈,让寻找的每一步都入栈,修改的时候只修改入栈的步骤。

		if (p->data < v)
			p = p->rightchild;
		else
			p = p->leftchild;
	}

其中p为指向根节点的指针,pr为一直指向p的父节点,在栈中保存每次插入的路线,这样可以在进行修改平衡因子的时候,能方便的找到需要修改的节点。
步骤②:
其中我们的修改步骤分为四种旋转方式:
1.单向左旋转,出现的情况大概是下图情况:
在这里插入图片描述
如图,出现如上图的节点插入操作的时候,我们此时应该需要让bf为2的节点转下去,让bf为1的节点成为其父节点,并且进行旋转后,该三个节点的bf都为0了。
2.单向右旋转:
情况如下图:
在这里插入图片描述
如图,与左旋转的方式一样,将bf为-2的节点旋转下来,让bf为-1的节点成为它俩的父节点,同时旋转完后,这三个节点的bf都为0。
3,先向左旋转,再向右旋转:
如下图:
在这里插入图片描述
如上图,在这个情况下,需要先将bf为0的节点转到bf为-1的位置,并且让bf为-1的节点变为bf为0节点的左子树,然后情况就和右单旋转一样,但是这个情况的各个节点的bf变化却不同了,其中是看图中bf为0的节点是否有左右子树来定的,情况如下图:
在这里插入图片描述

如图,旋转之后,其跟节点的bf肯定是0,因为是新插入的节点,但是如果修改的节点有左子树,那么经过双旋转后,进行左旋转的树的bf和进行右旋转的树的bf都会根据p节点的子树情况进行改变:
①:p的bf= 0,subL和subR的bf都为0。
②:p的bf=-1,subL的bf为0,subR的bf为1。
③:p的bf=1,subL的bf为-1,subR的bf为0。
4.先进行右旋转,再进行左旋转:
情况如下图:
在这里插入图片描述
如上图旋转,发现旋转后,左右子树的bf和先左旋后右旋的情况相同。
3.进行插入的时候,上述情况也只是对其需要进行修改的节点进行旋转修改,判断需不需要修改指针有以下三种方法:
①:如果插入节点之后插入节点的父节点的bf为0,那么证明这个树的高度差没有发生任何变化,仍然是一个平衡二叉树,所以可以直接退出,无需进行修改。
②:如果插入节点之后,插入的节点的父节点为1或-1,那么应该往上继续回溯,插入这个节点导致该节点所在子树的高度增高了,那么越往根节点的部分就越容易出现问题。
③:如果插入节点之后,插入的节点的父节点的绝对值大于1,那么就应该对其进行修改,使其平衡。
3.删除操作
删除操作与插入操作也比较类似,也是通过删除节点,然后观察之后会不引起平衡二叉树的不平衡,如果不平衡,应该对其进行修改,使其平衡。
1.删除节点的步骤
①:找到该节点
②:对该点进行删除,然后对平衡二叉树进行修改
2.寻找节点
与插入操作类似,都是通过栈进行的操作,对每一步查找都进行入栈,直到找到需要删除的节点。
其代码如下:

while (p != nullptr)
	{
		if (p->data == v)
			break;

		pr = p;//pr为要删除节点的父节点
		st.push(pr);

		if (p->data < v)
			p = p->rightchild;
		else if (p->data > v)
			p = p->leftchild;
	}

3.删除节点
与二叉搜索树的删除节点类似:
①:如果删除的节点有两个子树,那么就要在删除节点的左子树找出最大的树或者在删除节点右子树找出最小的那个树与要删除的节点进行替换,然后在左子树或者右子树中去删除交换的那个树即可(就是化删除两个节点的子树为删除没有节点的子树)
②:如果删除的节点子树少于或者等于一个,那么就直接删除
但是与二叉搜索树不同的是,平衡二叉树删除节点后要去判断树的平衡性。
4.删除节点同样也是需要插入操作的那四个旋转方法,但是判断方法和节点平衡因子的修改方法不同。
①:如果删除节点后,其父节点的bf变为-1或者1,那么证明树的高度没有发生变化,所以该树还是平衡二叉树,所以不需要继续修改,可以直接退出。
②:如果删除节点后,其父节点的bf变为0,那证明该节点所在子树的高度降低,应该往上继续回溯,寻找是否有节点不平衡,然后进行修改
③:如果删除节点后,其父节点的bf的绝对值大于1,那么就应该对其进行修改了,且修改的方式分为以下三种:
设该节点的父节点为pr,然后设置一个节点p指向pr左右较高的一个子树
a:如果p的bf = 0,那么进行一次单循环即可完成处理。
如下为左单循环的图例:
在这里插入图片描述

可以看出,如果是左循环,那么pr的bf就为1,且q的bf为-1;
那么如果是右循环,pr的bf为-1,q的bf为1。
b:如果p的bf不为0,如果pr的bf与p的bf正负号相同,那么就经过一次单循环即可完成,并且循环完成后pr的bf和p的bf都为0。
如图为左单旋转的例子:
在这里插入图片描述
c:如果p的bf不为0,并且pr的bf与p的bf正负号相反,那么就经过一次单循环即可完成,且bf的修改情况与插入操作的bf的修改情况相同;
如图为先右后左的旋转:
在这里插入图片描述

AVL树的模拟实现

#include<iostream>
#include<vector>
#include<stack>
#include<stdlib.h>
using namespace std;
template<class Type>
class AVL;
template<class Type>
class AVLNode
{
	friend class AVL<Type>;
public:
	AVLNode(Type d = Type(), AVLNode<Type>* left = nullptr, AVLNode<Type>* right = nullptr)
		:data(d), leftchild(left), rightchild(right), bf(0)
	{}
	~AVLNode()
	{}
private:
	Type data;
	AVLNode<Type>* leftchild;
	AVLNode<Type>* rightchild;
	int bf;//调节因子,用来判断该节点是否平衡
};
template<class Type>
class AVL
{
public:
	AVL()
	{}
public:
	void Insert(const Type& v)
	{
		Insert(root, v);
	}
	void Remove(const Type& v)
	{
		Remove(root, v);
	}
protected:
	void Insert(AVLNode<Type>*& t, const Type& v);
	void Remove(AVLNode<Type>*& t,const Type& v);
protected:
	void RotateL(AVLNode<Type>*& ptr);
	void RotateR(AVLNode<Type>*& ptr);
	void RotateLR(AVLNode<Type>*& ptr);
	void RotateRL(AVLNode<Type>*& ptr);
private:
	AVLNode<Type>* root;
};
template<class Type>
void AVL<Type>::RotateL(AVLNode<Type>*& ptr)
{
	AVLNode<Type>* subL = ptr;
	ptr = ptr->rightchild;//为了使ptr一直指向该旋转的根节点

	subL->rightchild = ptr->leftchild;
	ptr->leftchild = subL;
	ptr->bf = subL->bf = 0;//调节平衡因子
}
template<class Type>
void AVL<Type>::RotateR(AVLNode<Type>*& ptr)
{
	AVLNode<Type>* subR = ptr;
	ptr = ptr->leftchild;//为了使ptr一直指向该旋转的根节点

	subR->leftchild = ptr->rightchild;
	ptr->rightchild = subR;
	ptr->bf = subR->bf = 0;//调节平衡因子
}
template<class Type>
void AVL<Type>::RotateLR(AVLNode<Type>*& ptr)
{
	AVLNode<Type> *subR, *subL;//将左旋转节点和右旋转节点选好,并将ptr继续指向该旋转的根节点
	subL = ptr->leftchild;
	subR = ptr;
	ptr = subL->rightchild;

	//先左旋转
	subL->rightchild = ptr->leftchild;
	ptr->leftchild = subL;
	//调整调节因子
	if (ptr->bf <= 0)//出错
		subL->bf = 0;
	else
		subL->bf = -1;
	//再右旋转
	subR->leftchild = ptr->rightchild;
	ptr->rightchild = subR;
	//调整调节因子
	if (ptr->bf >= 0)//出错
		subR->bf = 0;
	else
		subR->bf = 1;
	ptr->bf = 0;
}
template<class Type>
void AVL<Type>::RotateRL(AVLNode<Type>*& ptr)
{
	AVLNode<Type> *subL, *subR;
	subL = ptr;
	subR = ptr->rightchild;
	ptr = subR->leftchild;
	//先向右旋转
	subR->leftchild = ptr->rightchild;
	ptr->rightchild = subR;
	//调整调节因子
	if (ptr->bf >= 0)
		subR->bf = 0;
	else
		subR->bf = 1;

	//再向左旋转
	subL->rightchild = ptr->leftchild;
	ptr->leftchild = subL;
	//调整调节因子
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;
	ptr->bf = 0;
}
template<class Type>
void AVL<Type>::Insert(AVLNode<Type>*& t, const Type& v)
{
	//根据搜索二叉树去插入数据
	AVLNode<Type>* pr = nullptr;
	AVLNode<Type>* p = t;
	stack<AVLNode<Type>*> s;
	while (p != nullptr)//用非递归的方式去插入数据,目的也是为了能对搜索树进行修改
	{
		if (p->data == v)
			return;

		pr = p;
		s.push(pr);//入栈,让寻找的每一步都入栈,修改的时候只修改入栈的步骤。

		if (p->data < v)
			p = p->rightchild;
		else
			p = p->leftchild;
	}
	p = new AVLNode<Type>(v);
	if (pr == nullptr)
	{
		t = p;
		return;
	}
	else
	{
		if (p->data < pr->data)
			pr->leftchild = p;
		else
			pr->rightchild = p;
	}
	
	//插入完成后,要对搜索二叉树进行修改,看他是不是平衡二叉树,如果不是,就进行更改。
	while (!s.empty())
	{
		pr = s.top();
		s.pop();
		if (pr->leftchild == p)
			pr->bf--;
		else
			pr->bf++;

		if (pr->bf == 0)//如果说加入的节点的父节点的平衡因子还是0,
			break;      //那就证明这个节点的加入无法将平衡二叉树的平衡打破
		else if (pr->bf == 1 || pr->bf == -1)//如果加入节点的父节点的平衡因子为-1或1,那就继续向上回溯
			p = pr;
		else                 //加入节点后,平衡二叉树被破坏,需要进行修改,而修改的方式为旋转,
			//分别为左旋转,右旋转,先左后右旋转,先右后左旋转
		{
			if (pr->bf > 0)
			{
				if (p->bf > 0)
				{
					RotateL(pr);//向左旋转
				}
				else
				{
					RotateRL(pr);//先右后左旋转
				}
			}
			else
			{
				if (p->bf < 0)
				{
					RotateR(pr);//向右旋转
				}
				else
				{
					RotateLR(pr);//先左后右旋转
				}
			}
			break;
		}
	}
	//重新链接节点
	if (s.empty())
	{
		t = pr;
	}
	else
	{
		AVLNode<Type> *ppr = s.top();
		if (ppr->data < pr->data)
			ppr->rightchild = pr;
		else
			ppr->leftchild = pr;
	}
}
template<class Type>
void AVL<Type>::Remove(AVLNode<Type>*& t,const Type& v)
{
	//1.找到数
	AVLNode<Type>* p = t, *pr = nullptr, *q;
	stack<AVLNode<Type>*> st;
	while (p != nullptr)
	{
		if (p->data == v)
			break;

		pr = p;//pr为要删除节点的父节点
		st.push(pr);

		if (p->data < v)
			p = p->rightchild;
		else if (p->data > v)
			p = p->leftchild;
	}
	if (p == nullptr)//删除的节点不存在
		return;

	if (p->leftchild != nullptr && p->rightchild != nullptr)//要删除的节点有两个子树,就要化成只有一个子树的
	{
		pr = p;
		q = p->leftchild;
		while (q->rightchild != nullptr)
		{
			pr = q;
			st.push(pr);

			q = q->rightchild;
		}
		p->data = q->data;
		p = q;
	}
	//2.删除树
	//删除最多的只能有一个孩子
	if (p->leftchild != nullptr)
		q = p->leftchild;
	else
		q = p->rightchild;
	if (pr == nullptr)//删除的是根节点
	{
		t = q;                          //                            ?why?
		                                //如果没有这步,那么pr就是空,那么下面的操作就会出错,
		                                //让t指向q是删除该根节点,最后还是会进行重新链接
	}
	else if (pr->leftchild == p)
	{
		pr->leftchild = q;
	}
	else
	{
		pr->rightchild = q;
	}
	///
	bool is_break_flag = false;
	//3.修改树
	while (!st.empty())
	{
		pr = st.top();
		st.pop();

		if (pr->leftchild == nullptr && pr->rightchild == nullptr)
		{
			pr->bf = 0;
		}
		
		else
		{
			if (pr->leftchild == q)//证明左子树高度降低了
				pr->bf++;
			else
				pr->bf--;
		}
		if (pr->bf == -1 || pr->bf == 1)//原来的节点本来就是0,那么证明删除其的一个节点,完全不会影响树的平衡性
			break;
		else if (pr->bf == 0)//原来的节点是-1或者1,那么就必须向上回溯,看看其父节点的平衡性是否规范。
			q = pr;
		else                 //平衡性出现错误
		{
			if (pr->bf > 0)              //要让q指向子树高的那一个节点
				q = pr->rightchild;
			else
				q = pr->leftchild;
			//判断如何删除
			if (q->bf == 0)//为什么q->bf == 0就可以中止循环了呢?
				           //答:由于旋转之后,高度与删除之前没有发生改变,所以可以中止循环。
						   //例如:开始左边高,旋转之后就是右边高。
			{
				if (pr->leftchild = q)
				{
					RotateR(pr);
					pr->bf = 1;
					pr->rightchild->bf = -1;
				}
				else
				{
					RotateL(pr);
					pr->bf = -1;
					pr->leftchild->bf = 1;
				}
				is_break_flag = true;
			}
			else
			{
				if (pr->bf > 0 && q->bf > 0)
				{
					RotateL(pr);
				}
				else if (pr->bf > 0 && q->bf < 0)
				{
					RotateRL(pr);
				}
				else if (pr->bf < 0 && q->bf < 0)
				{
					RotateR(pr);
				}
				else if (pr->bf < 0 && q->bf > 0)
				{
					RotateLR(pr);
				}
				q = pr;//向上回溯
			}
			if (st.empty())
			{
				t = pr;
			}
			else
			{
				AVLNode<Type>* ppr = st.top();
				if (pr->data > ppr->data)
					ppr->rightchild = pr;
				else
					ppr->leftchild = pr;
			}
		}
		if (is_break_flag)
			break;
	}
	//4.删除节点
	delete p;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值