目录
一、什么是AVL树?
我们知道二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度
于是我们知道AVL树的定义如下:
如果树不为空,且满足:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
log_2 n,搜索时间复杂度O(log_2 n)。
二、AVL树节点的插入
思路:先按照二叉搜索树的方式进行插入,然后调节平衡因子。
其中,插入之前,节点的平衡因子可能值为-1,0,1,插入之后该节点的平衡因子可能值为0,正负1,正负2
- 若插入后平衡因子为0,说明以该结点为根的树较低的子树被填上了,导致该树的左右子树高度相同,此时该树的高度没有变化,不用向上更新平衡因子
- 若插入后平衡因子为正负1,说明原来的节点平衡因子为0,以该结点为根的树高度加1,需要向上更新平衡因子
- 若插入后平衡因子为正负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.新节点插入较高右子树的左侧---右左:先右单旋再左单旋
没写的地方思路类似,请自行完成,以上就是今天的所有内容