AVL树

上一篇所写的二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

也就是说,AVL树本质上是一颗二叉查找树。

对于一棵AVL树,要么是一棵空树,要么需要具有如下性质:

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

AVL节点的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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. 更新平衡因子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
bool Insert(const T& data)
{
// 1. 先按照二叉搜索树的规则将节点插入到AVL树中
// ...

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

/*
pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent
的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可

此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满

AVL树的性质,插入成功
2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,

时以pParent为根的树的高度增加,需要继续向上更新
3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
*/
while (pParent)
{
// 更新双亲的平衡因子
if (pCur == pParent->_pLeft)
pParent->_bf--;
else
pParent->_bf++;
// 更新后检测双亲的平衡因子
if (0 == pParent->_bf)
break;
else if (1 == pParent->_bf || -1 == pParent->_bf)
{
// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉

// 的高度增加了一层,因此需要继续向上调整
pCur = pParent;
pParent = pCur->_pParent;
}
else
{
// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以pParent
// 为根的树进行旋转处理
if(2 == pParent->_bf)
{
// ...
}
else
{
// ...
}
}
}
return true;
}

AVL树的旋转

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

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

    R单旋

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    void RotateR(PNode pParent)
    {
    PNode pSubL = pParent->_pLeft;
    PNode pSubLR = pSubL->_pRight;

    pParent->_pLeft = pSubLR;
    if(pSubLR)
    pSubLR->_pParent = pParent;

    pSubL->_pRight = pParent;
    PNode pPParent = pParent->_pParent;
    pParent->_pParent = pSubL;
    pSubL->_pParent = pPParent;

    if(nullptr == pPParent)
    _pRoot = pSubL;
    else
    {
    if(pParent == pPParent->_pLeft)
    pPParent->_pLeft = pSubL;
    else
    pPParent->_pRight = pSubL;
    }

    pParent->_bf = pSubL->_bf = 0;
    }
  1. 新节点插入较高右子树的右侧—右右:左单旋

    R旋

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    void RotateL(PNode pParent)
    {
    PNode pSubR = pParent->_pRight;
    PNode pSubRL = pSubR->_pLeft;

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

    pSubR->_pLeft = pParent;
    PNode pPParent = pParent->_pParent;
    pParent->_pParent = pSubR;
    pSubR->_pParent = pPParent;

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

    pParent->_bf = pSubR->_bf = 0;
    }
  1. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋

    LR双旋

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void RotateLR(PNode pParent)
    {
    PNode pSubL = pParent->_pLeft;
    PNode pSubLR = pSubL->_pRight;
    int bf = pSubLR->_bf;

    RotateL(pParent->_pLeft);
    RotateR(pParent);

    //更新平衡因子
    if(1 == bf)
    {
    pSubL->_bf = -1;
    }
    else if(-1 == bf)
    {
    pParent->_bf = 1;
    }
    }
  1. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

RL旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void RotateRL(PNode pParent)
{
PNode pSubR = pParent->_pRight;
PNode pSubRL = pSubR->_pLeft;
int bf = pSubRL->_bf;

RotateR(pParent->_pRight);
RotateL(pParent);

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

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

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
    当pSubR的平衡因子为1时,执行左单旋
    当pSubR的平衡因子为-1时,执行右左双旋

  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
    当pSubL的平衡因子为-1是,执行右单旋
    当pSubL的平衡因子为1时,执行左右双旋
    旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

    AVL树的验证

    AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  3. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

  4. 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    节点的平衡因子是否计算正确
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int _Height(PNode pRoot)
{
if(nullptr == pRoot)
return 0;
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);

return leftHeight>rightHeight ? leftHeight+1 : rightHeight+1;
}

bool _IsAVLTree(PNode pRoot)
{
if(nullptr == pRoot)
return true;
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);

if(abs(pRoot->_bf)>1 || rightHeight - leftHeight != pRoot->_bf)
return false;
return _IsAVLTree(pRoot->_pLeft) && _IsAVLTree(pRoot->_pRight);
}

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证
查询时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,
但一个结构经常修改,就不太适合

0%