目录
一、概念
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
二、原理及实现
1.定义
template<class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; // 平衡因子
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
pair类相当于将K和V整合在了一个类中。K和V详情参考:二叉搜索树
2.插入
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
插入节点的方法和我们前文讲到的二叉搜索树插入方法一致,我们在此就不重复叙述了。我们这里主要聊的是如何调整平衡因子来保持平衡。
注:cur:表示当前插入位置
parent:表示插入位置的父亲节点
g:代表parent的父节点
1)更新平衡因子
思路:
如果插入的 cur 为左节点,parent的平衡因子--,如果为右节点,则++。此时可能还需要向上更新平衡因子。
是否继续更新需要看子树高度是否发生变化:
- 当parent->_bf == 0 说明之前子树一边高一边低,现在平衡了,不需要继续更新。
- 当parent->_bf == 1/-1 ,说明之前两边一样高,现在子树高度增加了,需要往上继续更新。
- 当parent->_bf == 2/-2 ,说明现在高度不平衡,已经违反了规则,处理方法:就地旋转 。
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//多出来的两个都在右边
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
2)旋转
规则:
- 让这颗子树左右高度差不超过1
- 旋转过程中要保持是搜索树
- 更新调整孩子节点的平衡因子
- 让这颗子树高度和插入前保持一致
旋转还需要分成两种情况:直线旋转和折线旋转。
直线旋转:
只需要旋转一次即可,如上图,要向右旋转,我们只需要把 parent 的右节点给g作为左节点,然后g作为parent的右节点,最后就可以完成旋转。平衡后我们需要将平衡因子更新为0。
向左旋转是也是同理,我们只需要把parent的左节点给g当做右节点,然后g作为parent的右节点。
代码:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//if (_root == parent)
if (ppNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
折线旋转:
需要直线旋转两次得到,所以这里可以服用直线旋转。
我们先将parent左旋一次,然后将g右旋一次,最终就达到平衡。
1、cur就是新增,cur旋转前的平衡因子是0,旋转完三个节点的平衡因子都赋值为0即可。
2、当在cur左侧新增一个节点,cur旋转前的平衡因子是-1,旋转后,g的平衡因子为1,其他都为0。
3、当在cur的右侧新增一个节点,cur旋转前的平衡因子是1,旋转后parent的平衡因子是-1,其他都为0.
另一个方向的旋转也是同理。
代码:
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
else if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}