【C++】AVL树

目录

一、什么是AVL树?

二、AVL树节点的插入

三、四种旋转


一、什么是AVL树?

我们知道二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下

因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

于是我们知道AVL树的定义如下:

如果树不为空,且满足:

  1. 它的左右子树都是AVL树
  2. 左右子树高度之差(简称平衡因子)的绝对值不超过1
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
log_2 n,搜索时间复杂度O(log_2 n)

二、AVL树节点的插入

思路:先按照二叉搜索树的方式进行插入,然后调节平衡因子

其中,插入之前,节点的平衡因子可能值为-1,0,1,插入之后该节点的平衡因子可能值为0,正负1,正负2

  1. 若插入后平衡因子为0,说明以该结点为根的树较低的子树被填上了,导致该树的左右子树高度相同,此时该树的高度没有变化,不用向上更新平衡因子
  2. 若插入后平衡因子为正负1,说明原来的节点平衡因子为0,以该结点为根的树高度加1,需要向上更新平衡因子
  3. 若插入后平衡因子为正负2,此时违反了AVL的平衡性,需要旋转

//树节点的定义,K,V均是模板参数,如不需要可以直接替换成int等类型
struct AVLTreeNode
{
	AVLTreeNode(const K& _data = K(),const V& value=V())
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(_data)
		,_value(value)
		, _bf(0)
	{}

	AVLTreeNode<K, V>* _pLeft;
	AVLTreeNode<K, V>* _pRight;
	AVLTreeNode<K, V>* _pParent;
	K _data;//每个节点存两个值
	V _value;
	int _bf;   // 节点的平衡因子
};
bool Insert(const K& data,const V& value)
{
	Node* parent = nullptr;
	Node* cur = _pRoot;
	if (_pRoot == nullptr)
	{
		_pRoot = new Node;
		_pRoot->_data = data;
		_pRoot->_value = value;
		return true;
	}
	while (cur)
	{
		if (data > cur->_data)
		{
			parent = cur;
			cur = cur->_pRight;
		}
		else if (data < cur->_data)
		{
			parent = cur;
			cur = cur->_pLeft;
		}
		else
		{
			return false;
		}
	}
	Node* tmp = new Node;
	if (data<parent->_data)
	{
		cur = tmp;
		cur->_data = data;
		cur->_value = value;
		cur->_pParent = parent;
		parent->_pLeft = cur;
		parent->_bf--;
	}
	else
	{
		cur = tmp;
		cur->_data = data;
		cur->_value = value;
		cur->_pParent = parent;
		parent->_pRight = cur;
		parent->_bf++;
	}
	//调整平衡因子
	while (parent->_pParent)
	{
		Node* pParent = parent->_pParent;
		//树的高度没有变化,不用向上调整
		if (parent->_bf == 0)
		{
			break;
		}
		//树的高度变化,向上调整
		else if (parent->_bf == -1 || parent->_bf == 1)
		{
			if (pParent->_pLeft == parent)
			{
				pParent->_bf--;
			}
			else
			{
				pParent->_bf++;
			}
		}
		//右树高,旋转
		else if (parent->_bf == 2)
		{
			if (parent->_pRight->_bf == 1)
			{
				RotateL(parent);
			}
			else
			{
				RotateRL(parent);
			}
		}
        //左树高,旋转
		else if (parent->_bf == -2)
		{
			if (parent->_pLeft->_bf == -1)
			{
				RotateR(parent);
			}
			else
			{
				RotateLR(parent);
			}
		}
		parent = parent->_pParent;
	}
	return true;
}

三、四种旋转

 根据插入节点位置的不同,旋转可分为四种:

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

思路:

  上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左
子树增加
 了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子
树增加一层,
 即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有
右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点
的平衡因子即可。
void RotateR(Node* pParent)
{
	Node* SubL = pParent->_pLeft;
	Node* SubLR = SubL->_pRight;
	pParent->_pLeft = SubLR;
	SubL->_pRight = pParent;
	if(SubLR)
		SubLR->_pParent = pParent;
	Node* ppNode = pParent->_pParent;
	pParent->_pParent = SubL;
	if (pParent == _pRoot)
	{
		_pRoot = SubL;
		SubL->_pParent = nullptr;
	}
	else
	{
		if (ppNode->_pLeft == pParent)
		{
			ppNode->_pLeft = SubL;
		}
		else
		{
			ppNode->_pRight = SubL;
		}
		SubL->_pParent = ppNode;
	}
	pParent->_bf = SubL->_bf = 0;
}

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

 

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

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
考虑平衡因子的更新
// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
void _RotateLR(PNode pParent)
{
	PNode pSubL = pParent->_pLeft;
	PNode pSubLR = pSubL->_pRight;

	// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
	int bf = pSubLR->_bf;

	// 先对30进行左单旋
	_RotateL(pParent->_pLeft);

	// 再对90进行右单旋
	_RotateR(pParent);
	if (1 == bf)
		pSubL->_bf = -1;
	else if (-1 == bf)
		pParent->_bf = 1;
}

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

没写的地方思路类似,请自行完成,以上就是今天的所有内容

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值