AVL树(超详细版)

前言:在前面我们学习过了二叉搜索树,我们知道如果想在树中查找结点,时间复杂度是o(N),但是在二叉树中查找结点时间复杂度是o(logN),但在二叉树中有特殊的情况。当树中的数据有序或接近有序,二叉树就会退化成单支树,查找元素就相当于在顺序表中查找,大大减慢了查找的效率。

在这里插入图片描述

为了解决这种情况,两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis提出一个解决该问题的方法,每当二叉搜索树插入新结点,如果能保持左右子树的高度差绝对值不超过1,从而降低树的高度,从而减少平均搜索长度。

AVL树的定义:前面我们讲了AVL树背景来源,那么什么是AVL树呢?AVL树需要满足左右子树高度差(简称平衡因子)的绝对值不超过1,它的左右子树也满足该性质。

平衡因子=右子树的高度-左子树的高度

在这里插入图片描述

AVL树结点的定义:

AVL树的性质我们已经知道了,那么现在我们要来定义AVL树的结点了
定义AVL树前,我们需要知道AVL树中需要有什么,我们需要有指向左右子树结点的指针,还有指向当前结点的父节点的指针(为了方便后面的插入,这里使用三叉链进行实现AVL树)还有为了平衡树的高度的平衡因子,还有存放的数据

template<class K,class V>
//设置为公用的可以在类外访问
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _parent;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;

	//平衡因子
	int _bf;

	//存放的数据
	pair<K, V> _kv;//不理解pair用法的老铁可以去查一下文档

	//构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:_parent(nullptr)
		: _left(nullptr)
		: _right(nullptr)
		: _bf(0)
		: _kv(kv)
	{};
};

AVL树的插入:

我们已经搞定了AVL树结点的定义,那么我们来到了AVL树结点最难的地方,AVL树结点的插入。

我们知道AVL树是在二叉搜索树的基础上增加了平衡因子,那么插入数据也是和二叉搜索树插入一样,由于笔者在上一篇博客已经讲过二叉搜索树插入的思路了,这里就不在讲述了,直接上代码了。

在这里插入图片描述

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	//AVL树插入
	bool Insert(const pair<K, V>& kv)
	{
		//先按二叉搜索规则插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		else
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_kv < kv)
				{
					parent = cur;
					cur = cur->_right;
				}

				else if (cur->_kv > kv)
				{
					parent = cur;
					cur = cur->_left;
				}

				else
				{
					return false;
				}
			}

			cur = new Node(kv);
			//把cur和parent连接起来
			//如果cur的key比parent的key大,那么就插入到右边
			if (cur->_kv.first > parent->_kv.first)
			{
				parent->_right = cur;
				//还需要把新的结点和父节点链接起来
				cur->_parent = parent;
			}
				
			else
			{
				parent->_left = cur;
				cur->_parent = parent;
			}
		}
	}
private:
	Node* _root = nullptr;
};

到这里AVL树的插入结点就完成了,但是别忘了AVL树还有平衡因子,由于我们在叶子结点插入了数据,那么必然会导致AVL树平衡因子的变化,那么我们需要调整这些变化的平衡因子。

更新平衡因子

在插入新的结点前,平衡因子只有-1/0/1这三种情况
如果我们在右子树插入新的结点,那么右子树的平衡因子会如何变化呢,同理左子树会如何变化呢?我们来画画图看看吧。
在这里插入图片描述


			//更新平衡因子
			//只要parent结点存在,我们就需要继续更新
			while (parent)
			{
				//如果插入的结点是parent的右节点
				//那么parent的平衡因子+1
				if (cur == parent->_right)
				{
					parent->_bf++;
				}
				//如果插入的结点是parent的左节点
				//那么parent的平衡因子-1
				else
				{
					parent->_bf--;
				}

			}

当平衡因子在插入后为0

在这里插入图片描述

				//更新后parent的平衡因子等于0了
				//表示平衡了就直接跳出循环了
				if (parent->_bf == 0)
					break;

当平衡因子在插入后为1/-1

在这里插入图片描述

		//更新完后parent的平衡因子等于-1和1时
		//我们还需要判断上面结点的平衡因子有没有超过1
		else if (parent->_bf == -1 || parent->_bf == 1)
		{
			cur = parent;
			parent = parent->_parent;
		}
				

当平衡因子在插入前为2/-2

当平衡因子为正负2时,分为四种情况,我会分别画图讲解这四种情况
1.左单旋**(新插入的结点在右子树)**
在这里插入图片描述

	//左单旋
	//该树是三叉链实现
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		
		//先把parent的右节点指向subRL
		//如果subRL不为空
		//再让subRL的parent指向parent
		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		//再链接parent和subR
		subR->_left = parent;
		//先保存好parent的parent
		//等会可能会用到
		Node* ppNode = parent->_parent;
		//如果parent是根结点
		//那么就让subR做新的根结点
		//如果是子树的根结点,那么就让subR的parent
		//指向parent的parent
		parent->_parent = subR;
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			//当parent是ppNode的左结点
			if (parent == ppNode->_left)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			//再让subR的parent指向ppNode
			subR->_parent = ppNode;
		}
		//更新subR和parent的平衡因子为0
		subR->_bf = parent->_bf = 0;
	}

2.右单旋(新插入结点在左子树)
让parent->left指向subR->right,再让subR->right->指向parent
在这里插入图片描述

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subR = parent->_left;
		Node* subRL = subR->_right;

		parent->_left = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		subRL->_right = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subR;
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;

			subR->_parent = ppNode;
		}

		subR->_bf = parent->_bf = 0;
	}

3.右左双旋(在左子树的右侧插入结点)
在这里插入图片描述


	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subRL = subL->_right;

		//先保留subRL的平衡因子
		//通过判断subRL的平衡因子来确定是在
		//subRL的那边子结点插入的
		int bf = subRL->_bf;
		//让parent->rgiht先右旋转
		RotateR(parent->right);

		//再让parent左旋转
		RotateL(parent);

		//更新平衡因子
		//计算插入结点后的平衡因子
		if (bf == 1)
		{
			subRL->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}

		else if (bf == -1)
		{
			subRL->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}

		
		else if(bf==0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subRL->_bf = 0;
		}
	}

4.左右双旋(在右子树的左侧插入结点)
在这里插入图片描述

	//左右双旋(
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;
		RotateL(subL);

		RotateR(parent);

		if (bf == 1)
		{
			subL->_bf = -1;
			parent->_bf = 0;
			subLR->_bf = 0;
		}

		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		
        else if(bf==0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
	}

到这里,关于平衡因子等于-2/2的情况已经全部考虑到了。
接下来我们需要把左单旋和右单旋和左右单旋和右左单旋放进bf==-2/bf==2的代码中。

	
				else if (parent->_bf == -2 || parent->_bf == 2)
				{
					//parent==2表示右边高
					if (parent->_bf == 2)
					{
						//如果cur结点的bf==1
						//表示右边子树高,需要左单旋
						if (cur->_bf == 1)
						{
							RotateL(parent);
						}

						//表示cur先是右子树高
						//再是左子树高
						//需要右左双旋
						else if (cur->_bf == -1)
						{
							RotateRL(parent);
						}

					}

					//parent==-2表示左边高
					else if (parent->_bf == 2)
					{
						//如果cur的bf==-1
						//表示左子树高,需要右单旋
						if (cur->_bf == -1)
						{
							RotateR(parent);
						}

						//如果cur的bf==-1
						//表示左子树高
						//再是右子树高
						//需要左右单旋
						else if (cur->_bf == -1)
						{
							RotateLR(parent);
						}
					}
				    //旋转完成后,parent所在树恢复到了插入结点前的高度
			       //所以对parent的上层结点没有影响,就可以跳出循环了
					break;
				}

到这里为止,AVL树的插入已经完成了,由于AVL树删除和插入非常相似,笔者就不在这里再分享了。

验证AVL树

我们需要分为两步来验证AVL

1.先验证AVL树是否是二叉搜索树

如果中序遍历该树成功,那么就是二叉搜素树

2.再验证AVL树是否平衡

每个子树的高度差绝对值不超过1

验证的代码

void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	//中序递归遍历
	//先递归左树,再递归右树
	_InOrder(root->_left);
	cout << root->_kv.first<< ":" << root->_kv.second << endl;
	_InOrder(root->_right);
}

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

int Height(Node* root)
{
	if (root == nullptr)
		return 0;

	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

//判断AVL树是否平衡
bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	//只要右子树-左子树高度差不超过1就是平衡
	return abs(leftHeight - rightHeight) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}
bool IsBalance()
{
	return _IsBalance(_root);
}

那么我们就测试一下吧

void TestAVLTree()
{
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e,e));
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
}

在这里插入图片描述
我们可以看到AVL树正确的打印出来了,并且返回1表示AVL树平衡了。

总结:

AVL树相对于二叉搜索树更加复杂,希望看完这篇博客的老铁能对AVL树插入有了深刻的理解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

平平无奇。。。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值