C++之 AVL树,手撕代码!

目录

一、AVL树的概念

二、AVL树或者是空树,或者是具有特殊性质的二叉搜索树

  给出一组数据,将其转化位AVL树

三、AvL树的插入

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

2.1左单旋转

 2.1右单旋转

 2.3先左后右

2.4先右后左

AVL树插入方法的完整代码

四、AVL树的删除

1.先找到要删除的节点

          2. 调整节点的平衡因子

删除的完整代码


一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行整),即可降低树的高度,从而减少平均搜索长度。

二叉搜索树的最优情况和最差情况

二、AVL树或者是空树,或者是具有特殊性质的二叉搜索树

  • 1.它的左右子树都是AVL树
  • 2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1或0或1),
  • 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log(n))搜索时复杂度O(log(n))。

  给出一组数据,将其转化位AVL树

16,3,7,11,9,26,18,14,15

在插入26的时候

 最终形成的形状

三、AvL树的插入

  • AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
  • 1. 按照二叉搜索树的方式插入新节点

  • 2. 调整节点的平衡因子

我们在调节平衡因子的时候是向上回溯着去调整的(从下到上),这时候可以借助一个栈结构,因为栈是先进后出

 因为插入了节点那么自插入节点之上的节点的平衡因子是一定要改变的那么我们就可以分为多种情况

 我们需要单独分析下上面代码的最后一种情况,这时候要去进行旋转操作,而旋转的情况又有四种

我们可以通过符号来进行判断需要进行哪种旋转方式

2.1左单旋转

 2.1右单旋转

 2.3先左后右

旋转的示意图:(最基本的情况下)

旋转后的subL,subR,和ptr的位置如下图所示

在先左后右调节平衡因子的时候比较复杂,因为subL,subR,ptr的平衡因子情况是都要进行改变的 

这时候就要分情况进行讨论

1.ptr->bf=0

2.ptr->bf=-1

 3.ptr->bf=1

调整bf的代码如下:

整体代码如下

2.4先右后左

调节bf,和上面一样,还是要分三种情况讨论

1.ptr->bf==0

这种情况最简单了,subR和subL的bf都会变成0

2.ptr->bf==-1

3.ptr->bf==1

AVL树插入方法的完整代码

template<class Type>
void AVL<Type>::RotateR(AVLNode<Type>*& ptr)
{
	AVLNode<Type>* subR = ptr;
	ptr = subR->leftChild;
	subR->leftChild = ptr->rightChild;
	ptr->rightChild = subR;

	//修改平衡因子
	ptr->bf = subR->bf = 0;
}
template<class Type>
void AVL<Type>::RotateL(AVLNode<Type>*& ptr)
{
	AVLNode<Type>* subL = ptr;
	ptr = subL->rightChild;
	subL->rightChild = ptr->leftChild;
	ptr->leftChild = subL;

	//修改平衡因子
	ptr->bf = subL->bf = 0;
}
template<class Type>
void AVL<Type>::RotateLR(AVLNode<Type>*& ptr)
{
	AVLNode<Type>* subR = ptr;
	AVLNode<Type>* subL = ptr->leftChild;
	ptr = subL->rightChild;

	subL->rightChild = ptr->leftChild;//这个旋转要看图去实现
	ptr->leftChild = subL;
	//调节subL的bf
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;

	subR->leftChild = ptr->rightChild;//旋转要看着图去实现
	ptr->rightChild = subR;
	//调节subR的bf
	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 = ptr;
	AVLNode<Type>* subR = subL->rightChild;
	ptr = subR->leftChild;

	//先进行右旋
	subR->leftChild = ptr->rightChild;
	ptr->rightChild = subR;

	//调整subR的bf
	if (ptr->bf >= 0)
		subR->bf = 0;
	else
		subR->bf = 1;

	//再进行左旋
	subL->rightChild = ptr->leftChild;
	ptr->leftChild = subL;

	//调整subL的bf
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;


}

/
template<class Type>
bool AVL<Type>::Insert(AVLNode<Type>*& t, const Type& v)
{
	//一、按照bst树的规则插入节点
	//1.1.查找插入的位置
	AVLNode<Type>* p = t, * pr = nullptr;
	stack<AVLNode<Type>*> st;//用来存放节点的指针

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

		pr = p;//pr这时候要向下追着p
		st.push(pr);

		if (p->data > v)
			p = p->leftChild;
		else
			p = p->rightChild;

	}
	//这时候p已经是到达了要插入的位置
	//1.2插入节点
	p = new AVLNode<Type>(v);

	if (pr == nullptr)//说明这时候,原本就是一个空树
	{
		t = p;
		return true;
	}
	else
	{
		//这时候要把新申请出来的节点通过pr重新连接到树之中
		if (pr->data > p->data)
			pr->leftChild = p;
		else
			pr->rightChild = p;
	}

	//调节平衡因子
	while (!st.empty())//当st还没有空的时候说明还可以向上追寻
	{
		pr = st.top();//pr获取到栈顶元素
		st.pop();
		if (p == pr->leftChild)//说明是在左树进行插入的
			pr->bf--;
		else
			pr->bf++;
		//调整完父节点的bf之后要分情况判断了

		if (pr->bf == 0)//说明这时候,相当于是在pr较矮的那端插入了节点,这时候不需要再向上管了
			break;//直接退出循环
		if (pr->bf == -1 || pr->bf == 1)//这时候就不确定上面的有没有出现不平衡的情况,要向上回溯
		{
			p = pr;//p向上指到pr的位置
		}
		else//这时候就相当于pr指到了不平衡的地方了,在这里就要进行调整了
		{
			//调整的时候又要进行分情况讨论
			if (pr->bf < 0)
			{
				if (p->bf < 0)//      /    要右单旋转
				{
					cout << "RotateR" << endl;
					RotateR(pr);
				}
				else		 //       <    先左后右
				{
					cout << "RotateLR" << endl;
					RotateLR(pr);
				}
			}
			else
			{
				if (p->bf > 0)//       \   左单旋转
				{
					cout << "RotateL" << endl;
					RotateL(pr);
				}
				else		 //		   >   先右后左
				{
					cout << "RotateRL" << endl;
					RotateRL(pr);
				}
			}
			break;//旋转调整完是肯定要进行退出的
		
		}

	}
	//旋转完还要进行一步重新链接的操作
	if (st.empty())//这时候说明根节点是要发生改变的
		t = pr;

	else//这是栈部位空的情况
	{
		AVLNode<Type>* ppr = st.top();
		if (pr->data < ppr->data)
			ppr->leftChild = pr;
		else
			ppr->rightChild = pr;

	}
	return true;
}

四、AVL树的删除

1.先找到要删除的节点

在找的时候肯定是从根节点开始去找的,并依靠一个栈结构去记录寻找时候的路径

和插入的时候是一样的,定义一个pr和p,让pr逐渐去追赶p

 找到要删的节点之后就要分情况讨论了,

我们参考BST树的删除,删除的时候是删除最多有一颗子树的节点

这两种情况只需要把子树填补上就行了

 还有一种情况就是删除的节点有两颗子树

 这时候就需要用左树中的最大值或者右数中的最小值替换

 2. 调整节点的平衡因子

我们先让q指向pr较高的子树,再根据子树的bf进行分情况讨论

1.q->bf==0

 

 2.q->bf!=0,但和ptr的符号一样

 

这时候需要先进行一次单旋转,旋转完之后,q的层数就减少了一层 ,相当于整个根节点的右树减少了一层,需要回溯

这时候再去由pr和q的bf的正负号去判断到底是进行哪种旋转

删除的完整代码

template<class Type>
bool AVL<Type>::Remove(AVLNode<Type>*& t, const Type& key)
{
	//1 按照bst的规则删除节点

	stack<AVLNode<Type>*> st;//用来存储路径

	AVLNode<Type>* p = t, * pr = nullptr;
	while (p != nullptr)//找到p的位置
	{
		if (key == p->data)
			break;

		pr = p;
		st.push(pr);

		if (key < p->data)
			p = p->leftChild;
		else
			p = p->rightChild;
	}
	if (p == nullptr)//这时候说明是没有找到要删的节点
		return false;

	AVLNode<Type>* q;//这个删除操作细品之后简直就是艺术
	if (p->leftChild != nullptr && p->rightChild != nullptr)
	{
		pr = p;//他的pr也是要追下来的,就相当于是继续执行了上面找p节点位置的代码,
		st.push(pr);//也就是说是把要删的p给转移了目标,就像是刚刚并没有找到要删的p一样

		q = p->leftChild;
		while (q->rightChild != nullptr)
		{
			pr = q;
			st.push(pr);

			q = q->rightChild;//找到左树中的最大值,以便进行替换值
		}
		p->data = q->data;//进行值的替换
		p = q;    //把删除p转换为要删除q
	}

	if (p->leftChild != nullptr)
		q = p->leftChild;
	else
		q = p->rightChild;
	
	//执行完上面的代码,p是一定指向了要删除的节点


	//摘除节点
	if (p->data < pr->data)
		pr->leftChild = q;
	else
		pr->rightChild = q;

	q = p;
	//调整平衡
	while (!st.empty())
	{
		pr = st.top();
		st.pop();

		if (q->data < pr->data)//这是判断是在左树删的还是右树删的
			pr->bf++;
		else
			pr->bf--;
		

		if (pr->bf == -1 || pr->bf == 1)//这时候树的高度是没有发生变化的
			break;
		if (pr->bf == 0)
		{
			q = pr;//回溯
		}
		else//这时候就需要进行旋转操作了
		{
			//让q指向较高子树
			if (pr->bf < 0)
				q = pr->leftChild;
			else
				q = pr->rightChild;

			if (q->bf == 0)//不需要回溯,直接退出
			{
				if (pr->bf > 0)
				{
					RotateL(pr);
					pr->leftChild->bf = 1;
				}
				else
				{
					RotateR(pr);
					pr->rightChild->bf = -1;
				}
				break;
			}
			else
			{
				if (pr->bf > 0)
				{
					if (q->bf > 0)
						RotateL(pr);
					else
						RotateRL(pr);
				}
				else
				{
					if (q->bf < 0)
						RotateR(pr);
					else
						RotateLR(pr);
				}
			//注意,需要向上进行回溯,因为子树的高度降低了,同时重新链接节点
				if (st.empty())
					t = pr;
				else
				{

					AVLNode<Type>* ppr = st.top();
					if (pr->data < ppr->data)
						ppr->leftChild = pr;
					else
						ppr->rightChild = pr;
				}

			}
		}

	}
	if (st.empty())
		t = pr;
	else
	{

		AVLNode<Type>* ppr = st.top();
		if (pr->data < ppr->data)
			ppr->leftChild = pr;
		else
			ppr->rightChild = pr;
	}

	delete p;


}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值