AVL树详解

假设有如下一棵二叉树树,为一棵单枝树,而且在以后的插入操作中由于特殊情况使插入节点比树中任意节点都大,它的这种特性继续保持,那么会发生什么情况?
这棵树将蜕化为链表,它的高度将等于树中节点个数N,所有的查找操作、删除操作时间复杂度变为O(N),而且大部分的储存空间被浪费。对于二叉树,我们期望它的高度是O(logN),这样对于它的绝大多数操作的时间复杂度将保持在O (logN)以内。
这里写图片描述

为了避免上述情况的发生,我们在二叉树的每个节点上加入平衡因子,规定右子树的高度减去左子树的高度为该树平衡因子。


概述

AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis。

AVL树规定如下:

AVL(Adelson-Velskii 和 Landis)树是带有平衡因子的二叉搜索树树中所有节点的平衡因子(bf)必须满足-2 < bf < 2,即左右子树高度差不超过1。

这样对于一棵树在我们每次插入一个节点后,新树的平衡性可能被破坏,而且只有那些从插入点到跟节点路径上的节点的平衡性会被破坏。当沿着插入点向上更新平衡因子时,我们只需调整第一个不满足平衡性的节点,这一调整将使得整个树仍然满足AVL树的特性

左单旋和右单旋

我们称第一个不满足平衡性的节点为parent,容易得出parent的不平衡可能出现以下四种情况:

这里写图片描述

  1. 当左子树较高时,在左子树的左侧插入节点。
  2. 当左子树较高时,在左子树的右侧插入节点。
  3. 当右子树较高时,在右子树的右侧插入节点。
  4. 当右子树较高时,在右子树的左侧插入节点。

明显看出,插入一个节点后使得该节点的父节点平衡因子变成“1或-1”,才有可能破坏该树的平衡性。这一点对于AVL树调整的理解相当重要。

我们用旋转处理上述情形。
1. 对于左-左,通过一次右旋可以使其重新平衡(右单旋)。
2. 对于右-右,通过一次左旋可以使其重新平衡(左单旋)。
3. 对于左-右,先右旋再左旋可以使其重新平衡(右左双旋)。
4. 对于右-左,先左旋再右旋可以使其重新平衡(左右双旋)。

容易看出情形1和2、3和4分别是对称的。

下面是旋转的大体图解。
这里写图片描述

可以想象,对于上图左-左,我们抓着6那个节点上一提,由于重力原因,右边1012那两个节点将落下来,这样该树左边子树高度减少1,右边子树高度增加1,最重要的是,调整后的树的高度与插入节点前的高度相同,所以,当完成第一次调整后将使得整棵树满足AVL树的特性。我们称这样一次调整为右单旋

可以观察到,此次旋转中,610是最关键的两个节点,我们所有的旋转操作都是通过改变这两个节点的指向关系来完成。所以在旋转完成后,它俩的平衡因子也应该更新,而且通过旋转后的情况来看,都应该置0
对于右-右的情形采与 左左 如出一辙。

双旋

对于左-右右-左的情形,我们需要通过两次旋转即左右双旋右左双旋完成。

左-右情形的图中,我们先以节点4为根,进行一次左单旋,旋转完成后树的情况将与左-左相同,接着我们在进行一次右单旋,至此,对于左-右这种情形,我么处理已经完成。
在双旋转完成之后,被旋转的几个节点的平衡因子根据插入位置的不同会有不同。如下图所示:

这里写图片描述

特殊处理平衡因子
我们注意到新节点被插入到36的左边时,在旋转完成之后,它仍为该树左子树中的一个节点,这将导致40那个节点的平衡因子为1,但是我们在每次单旋完成之后时,将该节点平衡因子置0了。同理,当新节点被插入到36的右边时,在旋转完成之后,该节点将跑到根节点的右子树中,所以30那个节点的平衡因子为-1, 但我们在每次单旋之后却将其置0

所以,针对上面两种情况,我们在双旋斩完成之后,对相关节点的平衡因子作一些处理。

比较好的一种做法解释就是,在旋转之前,保存36那个节点的平衡因子,若为1,那么说明新节点被插到该节点的右侧,则旋转之后30那个节点的平衡因子应被更新为-1,否则说明新节点被插到左侧,则在旋转之后40那个节点平衡因子应被更新为1
对于右-左,和左右对称,与左右的原理相反。

在搞清楚旋转的原理后,我们只需通过节点平衡因子的情况选择合适的旋转方法即可。
在旋转操作中最主要的内容就是更新各个节点之间的指向关系和它们的平衡因子。下面是实现旋转具体的具体代码:


代码实现

下面是有关旋转的代码:

//节点
struct AVLTreeNode
{
    AVLTreeNode(const K& key, const V& value)
    : _key(key)
    , _value(value)
    , _bf(0)
    , _pParent(NULL)
    , _pLeft(NULL)
    , _pRight(NULL)
    {}

    AVLTreeNode<K, V>* _pParent;
    AVLTreeNode<K, V>* _pLeft;
    AVLTreeNode<K, V>* _pRight;
    K _key;
    V _value;
    int _bf;
};

//左单旋
void _RotateLeft(Node* pParent)
    {
        Node* pSubR = pParent->_pRight;//右子树的根
        Node* pSubRL = pSubR->_pLeft;//右子树根的左孩子

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

        Node* pPParent = pParent->_pParent;//根的父节点

        //调整节点指向
        if (NULL == pPParent)
            _pRoot = pSubR;
        else if (pParent == pPParent->_pLeft)
            pPParent->_pLeft = pSubR;
        else
            pPParent->_pRight = pSubR;

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

        //注意更新旋转后的平衡因子
        pParent->_bf = 0;
        pSubR->_bf = 0;
    }

    //右单旋
    void _RotateRight(Node* pParent)
    {
        Node* pSubL = pParent->_pLeft;//左子树的根
        Node* pSubLR = pSubL->_pRight;//左子树根的左孩子

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

        Node* pPParent = pParent->_pParent;//根的父节点

        if (NULL == pPParent)
            _pRoot = pSubL;
        else if (pParent == pPParent->_pLeft)
            pPParent->_pLeft = pSubL;
        else
            pPParent->_pRight = pSubL;

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

        pParent->_bf = 0;
        pSubL->_bf = 0;
    }

    //左右双旋
    void _RotateLR(Node* pParent)
    {
        //双旋转对平衡因子要作特殊处理
        Node* SubL = pParent->_pLeft;
        Node* SubLR = SubL->_pRight;
        int bf = SubLR->_bf;

        _RotateLeft(pParent->_pLeft);
        _RotateRight(pParent);

        //特殊处理平衡因子
        if (1 == bf)
            SubL->_bf = -1;
        else
            pParent->_bf = 1;
    }

    //右左双旋
    void _RotateRL(Node* pParent)
    {
        //双旋转对平衡因子要作特殊处理
        Node* SubR = pParent->_pRight;
        Node* SubRL = SubR->_pLeft;
        int bf = SubRL->_bf;

        _RotateRight(pParent->_pRight);
        _RotateLeft(pParent);

        //特殊处理平衡因子
        if (1 == bf)
            pParent->_bf = -1;
        else
            SubR->_bf = 1;
    }

【作者:果冻 http://blog.csdn.net/jelly_9

——谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值