一、AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。从原来的时间复杂度O(logn)转变为O(n)
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树 高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/1/0)
二、AVL树节点的定义
AVL的概念:左右子树高度差不超过1
因此要在二叉搜索树上增加一个平衡因子(来记录左右子树的高度差),当平衡因子超过1时,就要更新向上调整父节点的平衡因子(因为子节点的平衡因子发生变化,间接的导致父节点的平衡因子发生改变),这时就要知道父节点的位置,因此在二叉搜索树上要多维护一个父节点的指针。
struct AVLTreeNode
{
pair<K, V> _key;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; // balance fatcor
AVLTreeNode(const pair<K, V>& key)
: _key(key)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
三、AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子
-
按照二叉搜索树的方式插入新节点
-
调整节点的平衡因子
下面是二叉搜索树的插入,这个树的结构是左子树小于根,右子树大于根。
bool Insert(const pair<K, V>& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key.first < key.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key.first > key.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (parent->_key.first < key.first)
{
parent->_right = cur;
}
else {
parent->_left = cur;
}
}
调整节点的平衡因子
插入新节点,会影响部分祖先节点的平衡因子,因此要不断的更新平衡因子。
插入在左子树,parent平衡因子--
插入在右子树,parent平衡因子++
1、parent的平衡因子 == 0
parent的平衡因子更新前是 1 or -1,插入节点插入在矮的那边。parent所在子树的高度不变,不需要继续往上更新
2、parent的平衡因子 == 1 / -1
说明parent的平衡因子更新前是0,插入节点插入在任意一边。parent所在的子树高度都会变化了,需要继续往上更新
3、parent的平衡因子 == 2 / -2
说明parent的平衡因子更新前是1 / -1,插入节点插入在高的那边,进一步加剧了parent所在子树的不平衡,所以要发生旋转,旋转后要更新平衡因子
// 因为因为父节点要连接一下父节点的位置
cur->_parent = parent;
// 更新平衡因子
while (parent)
{
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
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)
{
//旋转处理
}
else {
assert(false);
}
}
3.1 AVL左单旋
上面图中的h可以认为是抽象图,就是二叉树搜索树的分支。
上面的图可以认为在subR的右子树(记住是右子树)中任意插入一个节点,使右子树的高度+1,导致subR的平衡因子发生改变,从而影响parent的平衡因子变成了2,从而发生旋转。发生左旋转的条件是什么?是parent的平衡因子为2,subR的平衡因子为1时发生左旋转。
代码实现一下
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parentParent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else {
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else {
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
parent->_bf = subR->_bf = 0;
}
3.2 AVL右单旋
subL为parent的左子树,subLR为subL的右子树,当a这个位置高度变为h+1时,parent平衡因子为-2,subL平衡因子为-1时,此时发生右旋转,parent的左子树变为subLR,subL变为父节点且右子树为parent
代码实现一下
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parentParent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
parent->_bf = subL->_bf = 0;
}
3. 2 左右双旋
左旋
如果subRL这个二叉树中高度增加1时也就是说当parent的平衡因子为2,subR的平衡因子为-1时左单旋,我们不难发现旋转的结果与预期不符
右旋如果subLR这个二叉树高度增加1时也就是说parent的平衡因子为-2,subL的平衡因子为1时进行右单旋,旋转的结果与预期不符。旋来旋去发现高度没有变化。
如何解决parent的平衡因子为2,subR的平衡因子为-1的问题?
上图就是解决方式,增加节点后的第二个图,90为parent进行右旋到第三张图,之后30再变成parent进行左旋。
这里最麻烦的是平衡因子的维护
当h == 0时
也就是说subRL这个节点为新增节点,subRL与parent与subR平衡因子都为0。
当h > 0 时
因为增加节点有可能在b或者在c,不能确定parent这个位置平衡因子是-1/0还是subR这个位置的平衡因子是0/1,如何解决呢?
我们记录一下插入节点后subRL的平衡因子,如果subRL的平衡因子为1,经过左右双旋后,parent平衡因子为-1,subR为0,subRL为0。如果subRL的平衡因子为-1,经过左右双旋后,parent的平衡因子为0,subR为1,subRL为0。
代码实现
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 0)
{
subRL->_bf = 0;
subR->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
如何解决parent的平衡因子为-2,subR的平衡因子为1的问题?
上图就是解决方式,增加节点后的第二个图,30为parent进行左旋到第三张图,之后90再变成parent进行右旋。
平衡因子的维护
这里也要分三种情况来进行判断
当h == 0时
subRL为新插入节点时,parent的平衡因子与subL和subLR都为0
当h > 0时
新增节点插入b位置时,subLR的平衡因子为-1时,双旋后parent的平衡因子为1,subL和subLR的平衡因子为0。
新增节点插入c位置时,subLR的平衡因子为1时,双旋后parent的平衡因子为0,subL的平衡因子为-1,subLR的平衡因子为0。
代码实现
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else {
assert(false);
}
}