C++AVL树(四种旋转方式)

Posted 西科陈冠希

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++AVL树(四种旋转方式)相关的知识,希望对你有一定的参考价值。

AVL树概况

AVL树是在搜索树的基础上机型高度上的调整防止搜索树出现单支树的情况而导致效率低下,一颗AVL树或者空树,具有以下的特点:

  1. 左右子树都为AVL树
  2. 左右子树高度只差绝对值不超过1
    此时二叉搜索树的高度是平衡的,这棵树就是AVL树。

图例:
在这里插入图片描述

节点的定义

节点采用的是三叉链的方式进行创建,并且此时填入了一个平衡因子(用来控制左右子树的高度)

template<class T>
struct AVLTreeNode
 {
 
AVLTreeNode(const T& data)
 : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
 , _data(data), _bf(0)
 {}
 
AVLTreeNode<T>* _pLeft; // 该节点的左孩子
 
AVLTreeNode<T>* _pRight; // 该节点的右孩子
 
AVLTreeNode<T>* _pParent; // 该节点的双亲
 
T _data;
 
int _bf; // 该节点的平衡因子
 };

AVL树增删

插入

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

这里的插入方法和搜索树的基本上一致,先通过数据大小找出插入位置,然后通过判断parent的大小判断左还是右(左小右大)

Node* newnode = new Node(data);
		Node* cur = _pRoot;
		Node* parent = nullptr;
		if (cur == nullptr)
		{
			newnode = _pRoot;
			return true;
		}
		while (cur)
		{
			if (cur->_data > newnode->_data)
			{
				parent = cur;
				cur = cur->_pLeft;
			}
			if (cur->_data < newnode->_data)
			{
				parent = cur;
				cur = cur->_pRight;
			}
			else
			{
				return false;
			}
			cur = newnode;
			if (parent->_data > newnode->_data)
			{
				parent->_pLeft = newnode;
				newnode->_pParent = parent;
			}
			if (parent->_data < newnode->_data)
			{
				parent->_pRight = newnode;
				newnode->_pParent = parent;
			}

下来就该判断bf然后选择是否需要进行旋转

Node* Enode = cur;
			while (parent)
			{
				if (cur == parent->_pRight)
				{
					parent->_bf++;
				}
				else if (cur == parent->_pLeft)
				{
					parent->_bf--;
				}
				if (parent->_bf == 0)
				{
					break;
				}
				else if (abs(parent->_bf) < 2)
				{
					cur = parent;
					parent = parent->_pParent;
				}

删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

AVL树旋转

总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
    当pSubR的平衡因子为1时,执行左单旋
    当pSubR的平衡因子为-1时,执行右左双旋
  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
    当pSubL的平衡因子为-1是,执行右单旋
    当pSubL的平衡因子为1时,执行左右双旋
    旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

单旋转

右单旋转

在这里插入图片描述

if (cur->_bf == -1)//右旋
					{
						Node* subL = parent->_pLeft;
						Node* subLR = subL->_pRight;
						parent->_pLeft = subL;
						if (subLR)
						{
							subLR->_pParent = parent;
						}
						subL->_pRight = parent;
						Node* pparent = parent->_pParent;
						if (parent == _pRoot)
						{
							_pRoot = subL;
							subL->_pParent = nullptr;
						}
						else
						{
							if (parent == pparent->_pLeft)
							{
								pparent->_pLeft = subL;
								subL->_pParent = pparent;
							}
							else
							{
								pparent->_pRight = subL;
								subL->_pParent = pparent;
							}
							subL->_bf = parent->_bf = 0;
						}

这里贴出代码,代码的过程主要是通过图中一步步变换而来,

左单旋转

在这里插入图片描述

else if (cur->_bf == 1)//左旋
					{
						Node* subR = parent->_pRight;
						Node* subRL = subR->_pLeft;
						parent->_pRight = subR;
						if (subRL)
						{
							subRL->_pParent = parent;
						}
						subR->_pLeft = parent;
						Node* pparent = parent->_pParent;
						parent->_pParent = subR;
						if (parent == _pRoot)
						{
							_pRoot = subR;
							subR->_pParent = nullptr;
						}
						else
						{
							if (parent == pparent->_pLeft)
							{
								pparent->_pLeft = subR;
								subR->_pParent = pparent;
							}
							else if (parent == pparent->_pRight)
							{
								pparent->_pRight = subR;
								subR->_pParent = pparent;
							}
							subR->_bf = parent->_bf = 0;
						}

左旋和右旋类似,只不过是通过支树的长短进行判断也就是bf并且在旋转后要改变bf的值。

双旋转

LR旋转

在这里插入图片描述
此时先尽兴30和60以下子树的旋转通过60的子树+1;推断出30的bf++然后向上推导,此时就是一条折线,这就需要双旋转,先将60的左给30的右,60的右给90的左然后将60向上边提拉,最终要保持60的两边是30和90,并且通过给b还是c增加节点来判断30和90的bf。

else if (cur->_bf == 1)//LR双旋
					{
						Node* subL = parent->_left;
						Node* subLR = parent->_left->_parent;
						int bf = subLR->_bf;
						//先左旋再右旋
						RotateL(parent->_pLeft);
						RotateR(parent);
						if (subLR->_bf == -1)
						{
							subLR->_bf = 0;
							subL->_bf = 0;
							parent->_bf = 1;
						}
						else if (subLR->_bf == 1)
						{
							subLR->_bf = 0;
							parent->_bf = 0;
							subL->_bf = -1;
						}
						else
						{
							subL->_bf = subLR->_bf = parent->_bf = 0;
						}

RL旋转

同理先进行右旋然后进行左旋,最后更新bf。

AVL树验证及其性能

AVL树验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    节点的平衡因子是否计算正确
int Height(Node* pRoot)
	{
		if (pRoot == nullptr)
		{
			return 0;
		}
		return 1 + max(Height(pRoot->_pLeft), Height(pRoot->_pRight));
	}
	bool IsAVLTree(Node* pRoot)
	{
		if (abs(Height(_pRoot->_pLeft) - Height(_pRoot->_pRight)) < 2)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

AVL树性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即logn但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

以上是关于C++AVL树(四种旋转方式)的主要内容,如果未能解决你的问题,请参考以下文章

C++AVL树(四种旋转方式)

AVL树

PTA Root of AVL Tree (AVL树模板+四种旋转+指针)

C++AVL树的实现--详细解析旋转细节

C++AVL树的实现--详细解析旋转细节

C++_AVL树插入,查找与修改的实现(Key_Value+平衡因子+三叉链)