AVL樹(旋轉問題詳解)

二叉搜索樹給我們了一個搜索一個點的方法,同時,也將二叉樹中的節點排序了,但是,對於一些特殊的二叉樹來說,使用二叉搜索樹會很費空間,比如說:

左單支的這種情況,你說是二叉樹,還不如說是單鏈表呢,還節省了一大波的空間呢(右指針域),同樣的,對於右單支的情況也是如此,那麼現在我們就要想能不能避免這個問題。

可以,一個平衡因子就可以搞定,加了平衡因子,那麼這顆二叉搜索樹就是AVLTree了

那麼現在我們就來分析一下AVLTree,首先還是看一下平衡因子吧:平衡因子的實質就是右子樹的高度減去左子樹的高度的差值(當然你也可以用左減去右),既然是平衡因子,那麼就有一個範圍,這裏的範圍就是:-1,0,1。每一個結點的平衡因子都是這三個值中的一個,不是,那麼就不平衡。那麼爲什麼會是這三個呢?

下面我們來分析一下,首先0就不用說了,表示樹的左右子樹高度相等的,當然是平衡的,那麼1和-1呢,這其實都是一個類型的,我們之說一個就行了,就按照-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<K, V> *_pLeft;  //左子樹
    AVLTreeNode<K, V> *_pRight;  // 右子樹
    AVLTreeNode<K, V> *_pParent;  //雙親
    K _key; 
    V _value;
    int _bf;       // 平衡因子:right-left
};

然後我們按照一定的規則來插入元素,其實這些規則都是二叉搜索樹的,無非就是當你的Key小於當前節點的Key的時候,從左邊遍歷,大於就從右邊遍歷,找到之後就插入,不過這裏在插入之後要判斷是否滿足平衡樹的條件,如果不滿足的話,那麼需要調平。

那麼我們就重點來看一下怎樣調平的吧:

首先插入進去之後平衡因子會變化,那麼都有哪些結點的平衡因子變化了呢,我們不妨仔細看看,

這裏寫圖片描述

這張圖中紅色的線鏈接的那個結點是新插入的結點,在插入之前結點:30、40、50的平衡因子都是0,但是插入之後,他們就變成了-1,他們還有一個共同點,那就是,他們都是新插入節點的祖先結點,也就是說,新插入一個結點,那麼從根結點到此節點這條鏈的所有節點的平衡因子都變化。

至於說,如何變化的,顯然如果是其左子樹,那麼-1,右子樹+1

最後就是調平了,我們二叉樹中的一條鏈上的結點的平衡因子變化了,那麼就得看一下變化之後是不是還滿足AVLTree的條件。

總體來說還是得分情況:

1、如果此節點的平衡因子變成0,那麼直接就不用調了。

這裏寫圖片描述

2、如果此節點是1或-1的話,還是平衡樹,那麼繼續向上邊遍歷,看是否有超過1的

3、如果此節點的平衡因子的絕對值超過1,顯然不滿足平衡樹的條件,這就需要我們做一下旋轉處理了。

旋轉大致分爲四種:左單旋——右右,右單旋——左左,左右雙旋——左右,右左雙旋——右左。

那麼我們來一個一個看,首先來看一下左單旋

右右的意思是:在此節點右子樹的右側插入一個節點。

這裏寫圖片描述

如圖所示,其中65或80這兩個結點都滿足這個旋轉條件,爲了方便起見,我們只取一個,來看一下它的旋轉處理:

這裏寫圖片描述

我們將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;
    }

右單旋——左左

左左的意思是:在此節點的左子樹的左側,其實如果你理解了左單旋,你會發現,右單旋就和左單旋互換一個左右即可。

這裏寫圖片描述

這裏的20和35都是新插入的結點,同樣的 ,我們也取出一個作爲例子來看它的旋轉

這裏寫圖片描述

這個理念和做單選的一樣,在這裏做出一個建議,不要去嘗試記憶兩個,而要試着理解一個,那麼另一個就像鏡子裏的自己,顯而易見的。

雙旋也就是兩個單旋的組合,而左右雙旋和右左雙旋的區別就是左單旋和右單旋的順序問題。

左右單旋:先進行左單旋,再進行右單旋

這裏寫圖片描述

這就是一個可以進行左右單旋的例子,那麼來看一下它的旋轉吧

這裏寫圖片描述

同樣的右左雙旋的理念也是這樣的:先進行右旋,在進行左旋。

這裏寫圖片描述

這裏寫圖片描述

那麼接下來我們就來看一下實現的過程吧,這個過程我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<int, int> 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;
}
發佈了121 篇原創文章 · 獲贊 90 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章