AVL 树

目录

一、AVL树的特点

二、AVL树的插入

1.单旋 

1)右单旋

2)左单旋 

2.双旋 

1)左右双旋

2)右左双旋 

3.插入函数

三、AVL树完整代码 (旋转+插入)


一、AVL树的特点

在上一篇博客中说过,二叉搜索树是可能会退化的,这就导致查找的效率很低,而AVL树则是一种特殊的二叉搜索树,它的左右子树高度差绝对值不超过1,这样就可以避免二叉搜索树退化为单支树而造成效率低下。

AVL树的特点:左右子树都是AVL树;左右子树高度之差的绝对值不超过1。

AVL树的这种相对平衡是通过平衡因子和旋转控制的,平衡因子是结点的左子树高度减去右子树高度的值,这个值的绝对值是不超过1的,即可以为-1、0和1。

二、AVL树的插入

AVL树的插入过程比较复杂,会涉及到旋转,本文也是主要针对AVL树的插入过程,进行详细分析和讲解。

AVL树的插入与普通的二叉搜索树类似,需要从根结点开始遍历,并不断记录父结点的位置,找到要插入的位置,然后再判断是插入在左还是右,根据平衡因子的计算(右子树高度-左子树高度)很容易知道,若插入在左,则父结点的平衡因子-1,若插入在右,则平衡因子+1。插入之后,我们需要判断平衡因子是否符合规则(绝对值不超过1),分三种情况讨论。

1)插入后平衡因子为0,说明原来的平衡因子 + / - 1后为0,即原来的平衡因子为1或者-1,即一边高一边低,插入后左右子树高度相同。插入的结点是可能对祖先结点的平衡影子造成影响的,但在这种情况下是没有影响的,因为整个子树的高度是不变的,直接插入就可以解决问题。(例如下图中,在3的左侧插入一个结点,对于其父结点2来说平衡因子不变,仍然是1)

2)插入后平衡因子为1 / -1,说明原来的平衡因子为0,插入后整个子树的高度是增加1了的,例如在1的左侧插入一个结点,这种情况下是会影响祖先结点的,插入后2的平衡因子就变成了0,因此,在这种情况下,插入后需要向上遍历,改变祖先结点的平衡因子。

3)插入后平衡因子为2 / -2,说明原来的平衡因子为1或者-1,插入后加剧了“不平衡”,这时候已经不能满足AVL树的性质了,需要通过旋转来调整。

接下来主要针对第三种情况进行讲解。

我们先来看一下平衡因子变为2或者-2的情况:

说明:图中h是子树的高度(不包含新增结点),h>=0,图中的红色标记的数字则是插入结点后的平衡因子。

在AVL树的旋转平衡中有两种,单旋和双旋,在上图中情况一和情况三是单旋,情况二和情况四是双旋转,接下来逐一分析。

在分析旋转之前先看结点。

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& data = T())
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _bf(0)
	{}

	AVLTreeNode<T>* _pLeft;
	AVLTreeNode<T>* _pRight;
	AVLTreeNode<T>* _pParent;
	T _data;
	int _bf;   // 节点的平衡因子
};

1.单旋 

1)右单旋

情况一是需要右旋的,当新增结点在a所在子树时,可以看到,10的左子树高度加一,影响了10的平衡因子,进而影响20的平衡因子,使得20需要旋转。旋转的本质就是降低高的那侧,从图中很容易知道,c中的所有结点都是比10大的,b中所有结点比20小(依据二叉搜索树的性质),因此可以将20以及c作为10的右子树,然后将10的右子树给20,作为20的左子树,这样平衡因子就符合要求了,且变化过程没有违背二叉搜索树的性质。

图示:

代码实现:

将平衡因子为-2的结点(图中为20)起名为pParent,它的左孩子(图中为10)为subL,左孩子的右子树为subLR,它的父结点为pParentParent。

实现细节:subL的右子树指向pParent,pParent的父结点变为subL,pParent的左孩子变为subLR,subLR的父结点变为pParent,由于这个可能是子树,上面还有其他结点,所以要将pParentParent与subL连接起来。

特别注意:subLR可能为空,要判断,否则直接访问其父结点会报错;注意更改平衡因子。

// 右单旋
void RotateR(Node* pParent)
{
	Node* subL = pParent->_pLeft;
	Node* subLR = subL->_pRight;
	Node* pParentParent = pParent->_pParent;

	pParent->_pLeft = subLR;
	if (subLR)
		subLR->_pParent = pParent;

	subL->_pRight = pParent;
	pParent->_pParent = subL;

	//连接
	if (pParentParent == nullptr)
	{
		_pRoot = subL;
		subL->_pParent = nullptr;
	}
	else
	{
		if (pParentParent->_pLeft == pParent)
		{
			pParentParent->_pLeft = subL;
		}
		else
		{
			pParentParent->_pRight = subL;
		}

		subL->_pParent = pParentParent;
	}

	pParent->_bf = subL->_bf = 0;

}

2)左单旋 

左单旋与右单旋非常类似,这里直接给出旋转草图。

代码实现: 

    // 左单旋
	void RotateL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		Node* pParentParent = pParent->_pParent;

		pParent->_pRight = subRL;
		if(subRL)
		   subRL->_pParent = pParent;

		subR->_pLeft = pParent;
		pParent->_pParent = subR;
		
		//连接上面的根
		//pParent是根
		if (pParentParent == nullptr)
		{
			_pRoot = subR;
			subR->_pParent = nullptr;
		}
		else
		{
			//左
			if (pParentParent->_pLeft == pParent)
				pParentParent->_pLeft = subR;
			else
				pParentParent->_pRight = subR;  //右

			subR->_pParent = pParentParent;
		}
		pParent->_bf = subR->_bf = 0;
	}

2.双旋 

在情况二和情况四中,如果仅是单旋是解决不了问题的,需要双旋才可以。

例如,我们以情况二为例:

可以看到,如果只是按照前边的方法进行旋转,最后就是不断的循环,是不能解决问题的。

要解决这里的问题,需要进一步细分,将b所在子树拆开,分别考虑插入的结点的位置来旋转。

1)左右双旋

左右双旋是解决情况二的,但是在情况二中又要分三种情况讨论,因为不同情况下平衡因子的更新不同。

情况1:h>0,且插入的结点在subLR的左,即subLR的平衡因子为-1

情况2:h>0,且插入的结点在subLR的右,即subLR的平衡因子为1

情况3:h=0

 由草图也可以清晰看到,三种情况下都是先对10进行左旋,然后再对15进行右旋,双旋可以复用前边的单旋函数进行实现,最后改一下平衡因子即可。

同前边的单旋一样,20是pParent,10是subL,先对subL进行左旋,再对pParent进行右旋。

需要注意的是平衡因子的更新。

代码:

	// 左右双旋
	void RotateLR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		int bf = subLR->_bf;

		RotateL(pParent->_pLeft);
		RotateR(pParent);

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

2)右左双旋 

右左双旋与左右双旋非常类似,这里就不再画草图了,只给出代码。

	// 右左双旋
	void RotateRL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		int bf = subRL->_bf;

		RotateR(pParent->_pRight);
		RotateL(pParent);

		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			pParent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			pParent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			pParent->_bf = 0;
		}
		else
			assert(false);
		
	}

3.插入函数

	bool Insert(const T& data)
	{
		//插入影响的是祖先

		if (_pRoot == nullptr)
		{
			_pRoot = new Node(data);
			return true;
		}

		Node* cur = _pRoot;
		Node* parents = nullptr;
		while (cur)
		{
			if (cur->_data > data)
			{
				parents = cur;
				cur = cur->_pLeft;
			}
			else if (cur->_data < data)
			{
				parents = cur;
				cur = cur->_pRight;
			}
			else
			{
				//相等
				return false;
			}
		}

		cur = new Node(data);
		//判断插入在左还是右
		if (parents->_data > data)
		{
			//左
			parents->_pLeft = cur;
		}
		else
		{
			//右
			parents->_pRight = cur;
		}

		cur->_pParent = parents;

		//更新平衡因子
		while (parents)
		{
			//在左边插入减1,右边加1(平衡因子 = 右子树高度-左子树高度)
			if (cur == parents->_pLeft)
				parents->_bf--;
			else
				parents->_bf++;

			if (parents->_bf == 0)
			{
				//说明原来是1或者-1,一边高一边低,正好插入在低的那边
				//不会影响上面的祖先,不用更新上面的
				break;
			}
			else if (parents->_bf == 1 || parents->_bf == -1)
			{
				//说明原来是0,插入后会影响上面的祖先
				cur = parents;
				parents = parents->_pParent;
			}
			else if (parents->_bf == 2 || parents->_bf == -2)
			{
				//不平衡,需要旋转
				//同号单纯一边高,单旋即可,异号双旋
				if (parents->_bf == 2 && cur->_bf == 1)
					RotateL(parents);
				else if(parents->_bf == -2 && cur->_bf == -1)
					RotateR(parents);
				else if (parents->_bf == 2 && cur->_bf == -1)
					//先右旋变成单纯一边高,再左旋
					RotateRL(parents);
				else
					RotateLR(parents);

				break;

			}
			else
			{
				//说明有问题了
				assert(false);
			}
		}

		return true;
	}

三、AVL树完整代码 (旋转+插入)

// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:
	AVLTree()
		: _pRoot(nullptr)
	{}

	// 在AVL树中插入值为data的节点
	bool Insert(const T& data)
	{
		//插入影响的是祖先

		if (_pRoot == nullptr)
		{
			_pRoot = new Node(data);
			return true;
		}

		Node* cur = _pRoot;
		Node* parents = nullptr;
		while (cur)
		{
			if (cur->_data > data)
			{
				parents = cur;
				cur = cur->_pLeft;
			}
			else if (cur->_data < data)
			{
				parents = cur;
				cur = cur->_pRight;
			}
			else
			{
				//相等
				return false;
			}
		}

		cur = new Node(data);
		//判断插入在左还是右
		if (parents->_data > data)
		{
			//左
			parents->_pLeft = cur;
		}
		else
		{
			//右
			parents->_pRight = cur;
		}

		cur->_pParent = parents;

		//更新平衡因子
		while (parents)
		{
			//在左边插入减1,右边加1(平衡因子 = 右子树高度-左子树高度)
			if (cur == parents->_pLeft)
				parents->_bf--;
			else
				parents->_bf++;

			if (parents->_bf == 0)
			{
				//说明原来是1或者-1,一边高一边低,正好插入在低的那边
				//不会影响上面的祖先,不用更新上面的
				break;
			}
			else if (parents->_bf == 1 || parents->_bf == -1)
			{
				//说明原来是0,插入后会影响上面的祖先
				cur = parents;
				parents = parents->_pParent;
			}
			else if (parents->_bf == 2 || parents->_bf == -2)
			{
				//不平衡,需要旋转
				//同号单纯一边高,单旋即可,异号双旋
				if (parents->_bf == 2 && cur->_bf == 1)
					RotateL(parents);
				else if(parents->_bf == -2 && cur->_bf == -1)
					RotateR(parents);
				else if (parents->_bf == 2 && cur->_bf == -1)
					//先右旋变成单纯一边高,再左旋
					RotateRL(parents);
				else
					RotateLR(parents);

				break;

			}
			else
			{
				//说明有问题了
				assert(false);
			}
		}

		return true;
	}


	~AVLTree()
	{
		Destroy(_pRoot);
		_pRoot = nullptr;
	}

	

private:

	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		Node* pParentParent = pParent->_pParent;

		pParent->_pLeft = subLR;
		if (subLR)
			subLR->_pParent = pParent;

		subL->_pRight = pParent;
		pParent->_pParent = subL;

		//连接
		if (pParentParent == nullptr)
		{
			_pRoot = subL;
			subL->_pParent = nullptr;
		}
		else
		{
			if (pParentParent->_pLeft == pParent)
			{
				pParentParent->_pLeft = subL;
			}
			else
			{
				pParentParent->_pRight = subL;
			}

			subL->_pParent = pParentParent;
		}

		pParent->_bf = subL->_bf = 0;

	}

	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		Node* pParentParent = pParent->_pParent;

		pParent->_pRight = subRL;
		if(subRL)
		   subRL->_pParent = pParent;

		subR->_pLeft = pParent;
		pParent->_pParent = subR;
		
		//连接上面的根
		//pParent是根
		if (pParentParent == nullptr)
		{
			_pRoot = subR;
			subR->_pParent = nullptr;
		}
		else
		{
			//左
			if (pParentParent->_pLeft == pParent)
				pParentParent->_pLeft = subR;
			else
				pParentParent->_pRight = subR;  //右

			subR->_pParent = pParentParent;
		}
		pParent->_bf = subR->_bf = 0;
	}

	// 右左双旋
	void RotateRL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		int bf = subRL->_bf;

		RotateR(pParent->_pRight);
		RotateL(pParent);

		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			pParent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			pParent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			pParent->_bf = 0;
		}
		else
			assert(false);
		
	}

	// 左右双旋
	void RotateLR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		int bf = subLR->_bf;

		RotateL(pParent->_pLeft);
		RotateR(pParent);

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

	void Destroy(Node* pRoot)
	{
		if (pRoot == nullptr)
			return;

		Destroy(pRoot->_pLeft);
		Destroy(pRoot->_pRight);
		delete pRoot;
		
	}

private:
	Node* _pRoot;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值