AVL树(旋转问题详解)

二叉搜索树给我们了一个搜索一个点的方法,同时,也将二叉树中的节点排序了,但是,对于一些特殊的二叉树来说,使用二叉搜索树会很费空间,比如说:

左单支的这种情况,你说是二叉树,还不如说是单链表呢,还节省了一大波的空间呢(右指针域),同样的,对于右单支的情况也是如此,那么现在我们就要想能不能避免这个问题。

可以,一个平衡因子就可以搞定,加了平衡因子,那么这颗二叉搜索树就是AVLTree了

那么现在我们就来分析一下AVLTree,首先还是看一下平衡因子吧:平衡因子的实质就是右子树的高度减去左子树的高度的差值(当然你也可以用左减去右),既然是平衡因子,那么就有一个范围,这里的范围就是:-1,0,1。每一个结点的平衡因子都是这三个值中的一个,不是,那么就不平衡。那么为什么会是这三个呢?

下面我们来分析一下,首先0就不用说了,表示树的左右子树高度相等的,当然是平衡的,那么1和-1呢,这其实都是一个类型的,我们之说一个就行了,就按照-1来说吧,我们举一个最简单的例子。如果一个树只有两个节点,那么会出现什么结果呢?

AVL树(旋转问题详解)_第1张图片

这就是-1的结果,因此,所有,结点的右子树的高度减去左子树的高度的差值的绝对值如果大于1,那么他就不是平衡树。

了解了这些之后,我们就来构建一颗AVLTree吧

首先还是构建一个树的结点,其实二叉搜索树都已经写过了,这个都很简单了,这就比二叉搜索树多了一个平衡因子而已嘛。

template<class K, class V>
struct AVLTreeNode
{
    AVLTreeNode(const K& key, const V& value)
    : _pLeft(NULL)
    , _pRight(NULL)
    , _pParent(NULL)
    , _key(key)
    , _value(value)
    , _bf(0)
    {}


    AVLTreeNode *_pLeft;  //左子树
    AVLTreeNode *_pRight;  // 右子树
    AVLTreeNode *_pParent;  //双亲
    K _key; 
    V _value;
    int _bf;       // 平衡因子:right-left
};

然后我们按照一定的规则来插入元素,其实这些规则都是二叉搜索树的,无非就是当你的Key小于当前节点的Key的时候,从左边遍历,大于就从右边遍历,找到之后就插入,不过这里在插入之后要判断是否满足平衡树的条件,如果不满足的话,那么需要调平。

那么我们就重点来看一下怎样调平的吧:

首先插入进去之后平衡因子会变化,那么都有哪些结点的平衡因子变化了呢,我们不妨仔细看看,

AVL树(旋转问题详解)_第2张图片

这张图中红色的线链接的那个结点是新插入的结点,在插入之前结点:30、40、50的平衡因子都是0,但是插入之后,他们就变成了-1,他们还有一个共同点,那就是,他们都是新插入节点的祖先结点,也就是说,新插入一个结点,那么从根结点到此节点这条链的所有节点的平衡因子都变化。

至于说,如何变化的,显然如果是其左子树,那么-1,右子树+1

最后就是调平了,我们二叉树中的一条链上的结点的平衡因子变化了,那么就得看一下变化之后是不是还满足AVLTree的条件。

总体来说还是得分情况:

1、如果此节点的平衡因子变成0,那么直接就不用调了。

AVL树(旋转问题详解)_第3张图片

2、如果此节点是1或-1的话,还是平衡树,那么继续向上边遍历,看是否有超过1的

3、如果此节点的平衡因子的绝对值超过1,显然不满足平衡树的条件,这就需要我们做一下旋转处理了。

旋转大致分为四种:左单旋——右右,右单旋——左左,左右双旋——左右,右左双旋——右左。

那么我们来一个一个看,首先来看一下左单旋

右右的意思是:在此节点右子树的右侧插入一个节点。

AVL树(旋转问题详解)_第4张图片

如图所示,其中65或80这两个结点都满足这个旋转条件,为了方便起见,我们只取一个,来看一下它的旋转处理:

AVL树(旋转问题详解)_第5张图片

我们将55这个结点的连接到50的右,同时将50连接到60的左。不过,在做这些之前,我们需要将一些节点先保存起来,这样,变化之后,我们再将50和60这两个结点的平衡因子都置为0。

//左单旋——右右
    void _RotateL(Node* parent)
    {
        Node* subR = parent->_pRight;
        Node* subRL = subR->_pLeft;//有可能不存在

        parent->_pRight = subRL;
        if (subRL)
            subRL->_pParent = parent;

        subR->_pLeft = parent;
        Node* gparent = parent->_pParent;//保存parent的双亲
        parent->_pParent = subR;
        subR->_pParent = gparent;

        if (gparent == NULL)//parent是根结点
            _pRoot = subR;
        else if (gparent->_pLeft == parent)
            gparent->_pLeft = subR;
        else
            gparent->_pRight = subR;

        //旋转之后将其清零——结合图理解
        parent->_bf = 0;
        subR->_bf = 0;
    }

右单旋——左左

左左的意思是:在此节点的左子树的左侧,其实如果你理解了左单旋,你会发现,右单旋就和左单旋互换一个左右即可。

AVL树(旋转问题详解)_第6张图片

这里的20和35都是新插入的结点,同样的 ,我们也取出一个作为例子来看它的旋转

AVL树(旋转问题详解)_第7张图片

这个理念和做单选的一样,在这里做出一个建议,不要去尝试记忆两个,而要试着理解一个,那么另一个就像镜子里的自己,显而易见的。

双旋也就是两个单旋的组合,而左右双旋和右左双旋的区别就是左单旋和右单旋的顺序问题。

左右单旋:先进行左单旋,再进行右单旋

AVL树(旋转问题详解)_第8张图片

这就是一个可以进行左右单旋的例子,那么来看一下它的旋转吧

AVL树(旋转问题详解)_第9张图片

同样的右左双旋的理念也是这样的:先进行右旋,在进行左旋。

AVL树(旋转问题详解)_第10张图片

AVL树(旋转问题详解)_第11张图片

那么接下来我们就来看一下实现的过程吧,这个过程我debug的好多次,其中有各种的小毛病,注释上面都有,所以,看到的都要引以为戒。

AVLTree创建成功之后,我们得测验一下吧,就是判断这个二叉树是不是平衡树,按照二叉树的定义来,我们只需判断根结点的平衡因子是不是满足条件即可,然后再递归左子树和右子树。

不过,这里有一个需要注意的地方,就是平衡因子的计算是不是正确的,这个就是我刚才说的小毛病,所以,在判断平衡因子之前,我们再来计算一下平衡因子,如果和结点中的平衡因子不同,那么就不用在看其是否满足条件了。

// 检测二叉树是否为平衡树?
    bool _IsBalanceTree(Node* pRoot)
    {
        if (pRoot == NULL)
            return true;

        //求出左右子树的高度
        size_t left = _Height(pRoot->_pLeft);
        size_t right = _Height(pRoot->_pRight);

        //如果平衡因子计算错误  或  平衡因子大于1  那么不是平衡树
        if ((right - left) != pRoot->_bf || pRoot->_bf > 1)
            return false;

        return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
    }

下面是全部的代码,这里的测试用例很好,基本上涵盖了所有的旋转问题。

#include<iostream>
using namespace std;

template<class K, class V>
struct AVLTreeNode
{
    AVLTreeNode(const K& key, const V& value)
    : _pLeft(NULL)
    , _pRight(NULL)
    , _pParent(NULL)
    , _key(key)
    , _value(value)
    , _bf(0)
    {}


    AVLTreeNode<K, V> *_pLeft;  //左子树
    AVLTreeNode<K, V> *_pRight;  // 右子树
    AVLTreeNode<K, V> *_pParent;  //双亲
    K _key; 
    V _value;
    int _bf;       // 平衡因子:right-left
};


template<class K, class V>
class AVLTree
{
    typedef AVLTreeNode<K, V> Node;
public:
    AVLTree()
        : _pRoot(NULL)
    {}

    bool Insert(const K& key, const V& value)
    {
        if (_pRoot == NULL)//如果树为空,直接插入
        {
            _pRoot = new Node(key, value);
            return true;
        }

        //找插入位置
        Node* pCur = _pRoot;
        Node* parent = NULL;
        while (pCur)
        {
            if (key < pCur->_key)
            {
                parent = pCur;
                pCur = pCur->_pLeft;
            }
            else if (key>pCur->_key)
            {
                parent = pCur;
                pCur = pCur->_pRight;
            }
            else
                return false;
        }

        //插入
        pCur = new Node(key, value);//不要忘
        if (key < parent->_key)
            parent->_pLeft = pCur;
        else
            parent->_pRight = pCur;

        pCur->_pParent = parent;//插入之后将pCur的双亲的指针指向parent

        while (parent != NULL)
        {
            //平衡树可能不平衡了,重新调整
            if (parent->_pLeft == pCur)
                parent->_bf--;//不管pCur的兄弟结点是否存在,直接--,即可
            else
                parent->_bf++;

            //分情况讨论
            if (parent->_bf == 0)//层数没有增加
                return true;

            else if (parent->_bf == 1 || parent->_bf == -1)//层数加一,此时这个子树还是平衡的,继续向上调整
            {
                pCur = parent;
                parent = parent->_pParent;
            }

            else//  2||-2   已经不平衡了,需要进行旋转处理
            {
                if (parent->_bf == 2)
                {
                    if (pCur->_bf == 1)
                        _RotateL(parent);//右右——左单旋
                    else
                        _RotateRL(parent);//右左——右左双旋
                }
                else
                {
                    if (pCur->_bf == -1)
                        _RotateR(parent);//左左——右单旋
                    else
                        _RotateLR(parent);//左右——左右双旋
                }
                //插入只是影响树的局部,调整之后,树平衡
                break;
            }
        }
    }

    void InOrder()
    {
        cout << "InOrder: ";
        _InOrder(_pRoot);
        cout << endl;
    }

    size_t Height()
    {
        return _Height(_pRoot);
    }

    bool IsBalanceTree()
    {
        return _IsBalanceTree(_pRoot);
    }
private:
    // 检测二叉树是否为平衡树?
    bool _IsBalanceTree(Node* pRoot)
    {
        if (pRoot == NULL)
            return true;

        //求出左右子树的高度
        size_t left = _Height(pRoot->_pLeft);
        size_t right = _Height(pRoot->_pRight);

        //如果平衡因子计算错误  或  平衡因子大于1  那么不是平衡树
        if ((right - left) != pRoot->_bf || pRoot->_bf > 1)
            return false;

        return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
    }

    size_t _Height(Node* pRoot)
    {
        if (pRoot == NULL)
            return 0;
        if (pRoot->_pLeft == NULL&&pRoot->_pRight == NULL)
            return 1;

        size_t left = _Height(pRoot->_pLeft);
        size_t right = _Height(pRoot->_pRight);

        return left > right ? left + 1 : right + 1;
    }

    void _InOrder(Node* pRoot)
    {
        if (pRoot)
        {
            _InOrder(pRoot->_pLeft);
            cout << pRoot->_key << " ";
            _InOrder(pRoot->_pRight);
        }
    }

    //左单旋——右右
    void _RotateL(Node* parent)
    {
        Node* subR = parent->_pRight;
        Node* subRL = subR->_pLeft;//有可能不存在

        parent->_pRight = subRL;
        if (subRL)
            subRL->_pParent = parent;

        subR->_pLeft = parent;
        Node* gparent = parent->_pParent;//保存parent的双亲
        parent->_pParent = subR;
        subR->_pParent = gparent;

        if (gparent == NULL)//parent是根结点
            _pRoot = subR;
        else if (gparent->_pLeft == parent)
            gparent->_pLeft = subR;
        else
            gparent->_pRight = subR;

        //旋转之后将其清零——结合图理解
        parent->_bf = 0;
        subR->_bf = 0;
    }

    //右单旋——左左(在较高左子树的左插入结点)
    void _RotateR(Node* parent)
    {
        Node* subL = parent->_pLeft;
        Node* subLR = subL->_pRight;

        parent->_pLeft = subLR;
        if (subLR)
            subLR->_pParent = parent;

        subL->_pRight = parent;
        Node* gparent = parent->_pParent;
        parent->_pParent = subL;
        subL->_pParent = gparent;//总共三个,一个都不能忘

        if (gparent == NULL)
            _pRoot = subL;
        else if (gparent->_pLeft == parent)
            gparent->_pLeft = subL;
        else
            gparent->_pRight = subL;

        parent->_bf = 0;
        subL->_bf = 0;
    }

    //左右双旋——左右
    void _RotateLR(Node* parent)
    {
        Node* subL = parent->_pLeft;
        Node* subLR = subL->_pRight;
        int bf = subLR->_bf;
        _RotateL(subL);
        _RotateR(parent);

        if (-1 == bf)
            parent->_bf = 1;
        else if (1 == bf)
            subL->_bf = -1;
        else//只有三个结点的时候
            return;
    }

    //右左双旋——右左
    void _RotateRL(Node* parent)//debug
    {
        Node* subR = parent->_pRight;
        Node* subRL = subR->_pLeft;
        int bf = subRL->_bf;
        _RotateR(subR);
        _RotateL(parent);

        if (1 == bf)
            parent->_bf = -1;
        else if (-1 == bf)
            subR->_bf = 1;
        else
            return;
    }
private:
    Node* _pRoot;
};


void FunTest()
{
    int array[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};
    AVLTree t;
    for (size_t idx = 0; idx < sizeof(array) / sizeof(array[0]); ++idx)
        t.Insert(array[idx], idx);

    t.InOrder();

    if (t.IsBalanceTree())
    {
        cout << "是AVL树" << endl;
    }
    else
    {
        cout << "不是AVL树" << endl;
    }
}

int main()
{
    FunTest();
    return 0;
}

你可能感兴趣的:(数据结构)