【C++自学笔记】详细理解AVL树(插入、左单旋、右单旋、左右单旋、右左单旋)

一、AVL树的概念

二叉搜索树虽然可以缩短查找的效率,但是如果数据有序或者接近有序二叉搜索树将退化为单只树,查找元素相当于在顺序表中搜索元素,效率低下。因此,引入AVL树:当向二叉搜索树中插入新节点后,如果能保证没给节点的左右子树盖度之差的绝对值不超过1(需要对树中的节点进行调整),即可降低树的高度,从而减少平均搜索的长度。

一棵AVL树或者是空树,或者具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树;
  • 它左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1);

                                                            

如果一颗二叉搜索树是高度平衡的,它就是AVL树。如果它有n个节点,其高度可保持在O(log2N),搜索时时间复杂度为O(log2N);

二、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树的插入

AVL树就是在二叉搜索树的基础上又引入了平衡因子,因此AVL树也可以看成时二叉搜索树,那么AVL树的插入步骤:

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

需要注意的是:新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性;

如果插入前pParent是-1,0,1,则分为以下两种情况:

  1. 如果pCur插入到pParent的左侧,只需要给pParent的平衡因子减一;
  2. 如果pCur插入到pParent的右侧,只需要给pParent的平衡因子加一;

-->此时pParent 的平衡因子可能有三种情况:0,+-1,+-2:

  1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为+-1,插入后被调整到0,此时满足;
  2. 如果pParent的平衡因子为+-1,说明插入之前pParent的平衡因子一定为0,插入后被更新为+-1,此时以pParent为根的树高度增加,需要继续向上更新;
  3. 如果pParent的平衡因子为+-2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理;
bool Insert(const T& data) {
		if (_pRoot == nullptr) {
			_pRoot = new Node(data);
			return true;
		}
		PNode pCur = _pRoot;
		PNode pParent = nullptr;
		//找到位置
		while (pCur) {
			pParent = pCur;
			if (pCur->_data > data)
				pCur = pCur->_pLeft;
			else if (pCur->_data < data)
				pCur = pCur->_pRight;
			else
				return false;
		}
		//插入
		pCur = new Node(data);
		if (data < pParent->_data) {
			pParent->_pLeft = pCur;
			pCur->_pParent = pParent;
		}
		else {
			pParent->_pRight = pCur;
			pCur->_pParent = pParent;
		}
		while (pParent) {
			//更新pParent的平衡因子
			if (pCur = pParent->_pLeft)
				pParent->_bf--;
			else
				pParent->_bf++;
			//验证平衡性
			if (pParent->_bf == 0)
				break;
			else if (pParent->_bf == 1 || pParent->_bf == -1) {
				//向上调整
				pCur = pParent;
				pParent = pCur->_pParent;
			}
			else {
				if (pParent->_bf == 2) {
					if (pCur->_bf == 1)
						RotateRL(pParent);
					else
						RotateRL(pParent);
				}
				else {
					if (pCur->_bf == -1)
						RotateRight(pParent);
					else
						RotateLR(pParent);
				}
				pCur = pParent->_pParent;
				pParent = pCur->_pParent;
			}
		}	
		return true;
	}

四、AVL树的旋转

如果在一颗原本是平衡的AVL树种插入一个新的节点,可能造成不平衡,此时必须调整数的结构,使之平衡化,根据节点的插入位置不同,AVL树的旋转分为4种:

1、新节点插入较高左子树的左侧--左左:右单旋

          

如图所示,原来的AVL树时平衡的,新节点插入到30的左子树后(不是左孩子),30的左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60的左子树的高度减少一层,右子树增加一层;即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的左子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可,在旋转过程,分为以下几种情况:

  1. 30节点的右孩子也可能存在,也可能不存在;
  2. 60可能是根节点,也可能是子树:如果是根节点,旋转完成后需要更新根节点;如果是子树,可能是某个节点的左子树,也可能是右子树。
//右单旋
	void RotateRight(PNode pParent) {
		//pSubL:pParent的左孩子
		//pSubLR:pParent左孩子的右孩子
		PNode pSubL = pParent->_pLeft;
		PNode pSubLR = pSubL->_pRight;
		
		pParent->_pLeft = pSubLR;
		//pSubLR != NULL 更新双亲
		if (pSubLR)
			pSubLR->_pParent = pParent;
		
		PNode ppParent = pParent->_pParent;
		pSubL->_pRight = pParent;
		//更新pParent的双亲
		pParent->_pParent = pSubL;
		if (ppParent == nullptr) {
			_pRoot = pSubL;
			pSubL->_pParent = nullptr;
		}
		else {
			if (ppParent->_pLeft == pParent)
				ppParent->_pLeft = pSubL;
			else
				ppParent->_pRight = pSubL;
		}
		//更新_bf
		pSubL->_bf = 0;
		if (pParent->_pLeft == nullptr && pParent->_pRight != nullptr)
			pParent->_bf = 1;
		else
			pParent->_bf = 0;
	}

2、新节点插入较高左子树的右侧--右右:左单旋

                  

类似于右单旋;

void RotateLeft(PNode pParent) {
		PNode pSubR = pParent->_pRight;
		PNode pSubRL = pSubR->_pLeft;
		PNode ppParent = pParent->_pParent;

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

		pSubR->_pRight = pParent;
		pParent->_pParent = pSubR;
		if (ppParent == nullptr) {
			_pRoot = pSubR;
			pSubR->_pParent == nullptr;
		}
		else {
			if (ppParent->_pLeft == pParent)
				ppParent->_pLeft = pSubR;
			else
				ppParent->_pRight = pSubR;
		}

		pSubR->_bf = 0;
		if (pParent->_pRight == nullptr && pParent->_pLeft != nullptr)
			pParent->_bf = -1;
		else
			pParent->_bf = 0;

	}

3、新节点插入较高左子树的右侧--左右:先左单旋再右单旋

        

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再进行右单旋,旋转完成后再考虑平衡因子的更新;

void RotateLR(PNode pParent) {
		PNode pSubL = pParent->_pLeft;
		PNode pSubLR = pSubL->_pRight;

		int bf = pSubLR->_bf;

		RotateLeft(pParent->_pLeft);
		RotateRight(pParent);

		if (bf == 1)
			pSubL->_bf = -1;
		else if (bf == -1)
			pParent->_bf = 1;
	}

4、新节点插入较高右子树的左侧--左右:先右单旋再左单旋

     

参考先左单旋再右单旋

五、AVL树的验证

1、如果中序遍历是有序的序列,说明此树是二叉搜索树;

2、验证是否为平衡树

  • 每个节点子树高度差的绝对值不超过1(注意节点种如果没有平衡因子);
  • 节点的平衡因子是否计算正确;
	int Height(PNode pRoot);
	bool IsBalanceTree(PNode pRoot) {
		if (pRoot == nullptr)
			return true;
		//计算左右之差
		int leftHeight = Height(pRoot->_pLeft);
		int rightHeight = Height(pRoot->_pRight);
		int diff = rightHeight - leftHeight;
		//如果左右之差不等于pRoot的_bf,或者绝对值超过1,都返回false
		if (diff != pRoot->_bf || (diff > 1 || diff < -1))
			return false;
		//左右递归验证
		return IsBalanceTree(pRoot->_pLeft) && IsBalanceTree(pRoot->_pRight);
	}

总结:

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

1、pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR:

  • 当pSubR的平衡因子为1时,执行左单旋;
  • 当pSubR的平衡因子为-1时,执行右左单旋;

2、pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubR:

  • 当pSubR的平衡因子是-1时,执行右单旋;
  • 当pSubR的平衡因子是1时,执行左右单旋;

六、AVL树的删除

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

七、AVL树的性能

AVL树是一颗绝对平衡的树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,及log2N,但是如果要求对AVL树做一些结构修改的操作,性能非常低下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_ClivenZ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值