【C++】AVL

AVL树是由Adelson-Velskii和Landis于1962年提出的一种自平衡二叉搜索树,其特点是每个节点的左右子树高度差不超过1,确保了搜索效率为O(logN)。插入操作后,需要更新节点的平衡因子并可能进行旋转调整,包括左旋、右旋以及左右双旋和右左双旋,以保持树的平衡。
摘要由CSDN通过智能技术生成

AVL树的概念

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

 简单来说,就是一颗二叉搜索树,这颗树每一个节点的左右子树高度差的绝对值都不能超过1且都是AVL树

这样结构的好处就是,我们搜索起来它的时间复杂度是O(logN)

节点

AVL树与之前碰见的二叉树不一样,它节点是这样的:

template<class T>
struct AVLTreeNode
{


	AVLTreeNode<T>* _parent; //父亲节点
	AVLTreeNode<T>* _left;	 //左孩子
	AVLTreeNode<T>* _right;	 //右孩子
	T _val;					 //值

	int bf;					 //平衡因子
};

它多了一个父节点,同时多了一个平衡因子,这个平衡因子就是来帮助我们之后调整二叉树的。

快速写一个构造函数:

	AVLTreeNode(const T& val = T())
		:_val(val)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _bf(0)
	{}

不需要什么参数,就传过来一个值即可,如果没传,就调用这里的缺省值。

插入

其实插入跟之前二叉搜索树是一样的

【C++】二叉搜索树_Knous的博客-CSDN博客

但是不同的地方在于,我们插入之后要更新我们的平衡因子,不是更新插入节点的,而是更新父亲节点的。

这里看图:

 这里就要提一下,平衡因子是怎么算的:

如果左子树插入一个节点,那父亲的平衡因子就-1

如果右子树插入一个节点,那父亲的平衡因子就+1

按照上面逻辑,现在算一下插入之前的平衡因子:

现在插入一个:

 

 

此时,这棵树就很平衡,当然,也可以在其他地方插入,那就会不平衡,这里我们先写一个插入,不平衡的情况之后分析。

先写一个模板,这个模板里面只有一个参数,就是根节点

template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;



private:
	Node* _root;
};

插入

	bool insert(const T& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		else
		{
			//插入
			Node* cur = _root;
			Node* parent = cur->_parent;
			while (cur)
			{
				if (cur->_val > val)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_val < val)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
					assert(false);
			}
			cur = new Node(val);
			if (cur == parent->_left)
				parent->_left = cur;
			else
				parent->_right = cur;

		}

	}

调整

我们插入完后,要更新父亲节点的平衡因子:

			//更新平衡因子
			while (parent)
			{
				if (cur == parent->_left)
					parent->_bf--;
				else
					parent->_bf++;
			}

更新完了,就该判断是否平衡了

这里简单看几个图分析情况:

第一种情况:

右单旋

假设此时已经左边高了,而我们又在左边插入了一个元素,此时,就变成了这样的情况,为了方便后续操作,将这个三个节点分别称之为:A/B/C 

 

将最高的那个节点拿下来,将其最为第二高的右节点

假设情况在复杂一点:

我们看一下插入前后的平衡因子:

 

当前节点为-1且父亲节点为-2,这就要右旋转了,为了之后方便写代码,这里将节点重新取名

这样,高度不仅没变,还保持了二叉搜索树的条件,为了方便阅读,这里给节点单独取名

	void _RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subR->_right;

		Node* pparent = parent->_parent;

		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;
		parent->_left = subLR;

		if (parent == _root)
		{
			_root = subL;
		}
		else
		{
			if (parent == pparent->_left)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		parent->_bf = subL->_bf = 0;
	}

这里可以对照着图理解一下,因为最后旋转完高度应该是一样的,所以平衡因子应该重置为0

---

左单旋

这种完全是跟右单旋反过来,它是这样的图:

具体思路跟右单旋相反:

 具体思路差不多,看代码:

	void _RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		if (subRL)
			subRL->_parent = parent;

		Node* pparent = parent->_parent;
		subR->_left = parent;
		parent->_right = subRL;
		parent->_parent = subR;

		if (parent == _root)
			_root = subR;
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
		}
		s

写完了先来测试一下效果:

这是一个左单旋,2的左边是1,右边是3,调整没问题。

 

 右单选,也没问题

---

左右双旋

有时,就会出现这种情况,A B C不都在一边,对于A来说,B在它的左边,对于B来说,C在它的右边,此时就不是单纯的左旋转或者右旋转能解决问题的了。

 此时,无论在E、F哪边插入,都会失去平衡,面对这种情况,我们就要考虑双旋,转两次来解决问题

这时,我们就要先左旋,再右旋:

左旋:

再右旋;

 

此时,就完成了对称调整

这里要注意的一点是,在调整之前C的平衡因子是多少,因为会有以下几种情况:

假设C调整之前是-1,说明在E插入,即上图调整

假设C调整之前是1,说明在F插入,即下图调整

根据调整之前不同,AB的值也不同

如果是-1:A= -1,B=C=0

如果是1:B=1,A=C=0

这里复用之前写好的旋转逻辑即可:

	void _RotateLR(Node* parent)
	{
		Node* subL = parent->_left;//B
		Node* subLR = subL->_right;//C
		int bf = subLR->_bf;
		_RotateL(parent->_left);
		_RotateR(parent);

		if (bf == 1)
			subL->_bf = -1;
		else if (bf == -1)
			parent->_bf = 1;
		else
			assert(false);
	}

右左双旋

大体可以参考左右双旋,思路也是一样的

 几乎是镜像左右旋,同样根据C的bf值有不同的结果,最后调整一下即可

	void _RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		_RotateR(parent->_right);
		_RotateL(parent);

		if (bf == -1)
			subR->_bf = 1;
		else if (bf == 1)
			parent->_bf = -1;

	}

删除之后也许会更新,了解插入的旋转可以帮助我们理解二叉搜索树的结构了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值