二叉樹之AVL樹的平衡實現(遞歸與非遞歸)


這篇文章用來復習AVL的平衡操作,分別會介紹其旋轉操作的遞歸與非遞歸實現,但是最終帶有插入示例的版本會以遞歸呈現.

下面這張圖繪制了需要旋轉操作的8種情況.(我要給做這張圖的兄弟一個贊)后面會給出這八種情況對應平衡實現.

 

[1]


 

情況1-2:

  這種需要旋轉的結構一般稱之為LL型,需要右旋 (順時針旋轉).

  我用一個圖來抽象一下這兩個情況,畫的不好,我盡量表達吧.

  

  此時需要對A進行平衡操作,方法為:

  1.     將A的左子樹換為B的右子樹.
  2.   B的右子樹換為A.
  •   非遞歸實現的代碼為:
 1 void rotate_right(AVLTree &A){
 2 
 3   AVLTree leftChild = A->left;
 4 
 5   A->left = leftChild->right;
 6 
 7   leftChild->right = A;
 8 
 9   // 別忘了讓父節點建立平衡后的連接
10 
11   A = leftChild;
12 
13 }

 

  非遞歸的操作在旋轉前會充分考慮所有的旋轉情況,目的是提早調整A下面各節點的高度.

  之后再進行旋轉操作,這一點與遞歸的不同,可見遞歸是平衡完后再進行的高度調整.

  •   遞歸實現代碼為:
 1 Position CAVLTree::singleRotateWithLeft(Position _K){
 2     Position K0;
 3     K0 = _K->left;
 4     _K->left = K0->right;
 5     K0->right = _K;
 6 
 7     _K->Height = max(getHeight(_K->left),getHeight(_K->right)) + 1;
 8     K0->Height = max(getHeight(K0->left),getHeight(K0->right)) + 1;
 9 
10     // 返回新的節點以替換
11     return K0;
12 }

 

情況3-4:

  這種需要旋轉的結構一般稱之為RR型,需要左旋(逆時針旋轉).

  需要對A進行平衡操作,方法為:

  1.   將A的右子樹換為B的左子樹;
  2.   B的左子樹換為A

  

  •   非遞歸的實現為:
void rotate_left(AVLTree &A){

    AVLTree rightChild = A->right;

    A->right = rightChild ->left;

    rightChild->left = A;

    A = rightChild;

}

 

  •   遞歸實現為:
Position CAVLTree::singleRotateWithRight(Position _K){
    Position K0;
    K0 = _K->right;
    _K->right = K0->left;
    K0->left = _K;

    _K->Height = max(getHeight(_K->left),getHeight(_K->right)) + 1;
    K0->Height = max(getHeight(K0->left),getHeight(K0->right)) + 1;
    return K0;
}

 

情況5-6: 

  這種需要旋轉的結構一般稱之為LR型,需要雙旋轉,即兩次單旋.分別為左旋和右旋.

  需要對A進行平衡操作,方法為:

  1.   對B(A->left)做左旋
  2.   對A做右旋

  這個遞歸與非遞歸的方式都是一樣的.

  •   非遞歸:
rotate_left(A->left);

rotate_right(A);

 

  •   遞歸:
Position CAVLTree::doubleRotateWithLeft(Position _K){
    _K->left = singleRotateWithRight(_K->left);
    return singleRotateWithLeft(_K);
}

 

  但是有沒有一次性到位的方法呢?有的

  我把非遞歸的兩個函數展開:

  發現最后一步都是確定與父節點的關系,並不是旋轉中的具體過程,於是可以簡化為這樣:

AVLTree leftChild = A->left;

AVLTree leftRightChild = leftChild->left;

// 左旋

leftChild->right = leftRightChild->left;

leftRightChild->left = leftChild;

// 右旋

A->left = leftRightChild->right;

leftChild->right = A;

 

 

情況7-8:

  這種需要旋轉的結構一般稱之為RL型,需要雙旋轉,即兩次單旋.分別為右旋和左旋.

  需要對A進行平衡操作,方法為:

  1.   對B進行右旋
  2.   對A進行左旋

  同樣,遞歸與非遞歸版本是一樣的.

  •   非遞歸:
rotate_right(A->left);

rotate_left(A);

 

  •   遞歸:
Position  CAVLTree::doubleRotateWithRight(Position _K){
    _K->right = singleRotateWithLeft(_K->right);

    return singleRotateWithRight(_K);
}

 

 

  同樣,也有一次性到位的方法:

AVLTree rightChild = A->right;

AVLTree rightLeftChild = rightChild->left;

// 右旋

rightChild->left = rightLeftChild->right;

rightLeftChild->right = rightChild;

// 左旋

A->right = rightLeftChild->left;

rightLeftChild->left = A;

 

 


 

 

下面是實現部分:

 

0.結構聲明[2]:

struct AvlNode;
typedef AvlNode * AvlTree;
typedef AvlNode * Position;

typedef int ELEMENT;

struct AvlNode
{
    AvlNode():data(),left(nullptr),right(nullptr),Height(){}
    ELEMENT data;
    AvlTree left;
    AvlTree right;
    int Height;
};

 

 

1.類中提供的API

class CAVLTree
{
public:  
    CAVLTree(void);

    ~CAVLTree(void);

    size_t _insert_(ELEMENT &_data);

    int getTreeHeight();

    void showThisTree();

private:
    
    size_t size;

    AvlTree AvlTreeRoot;
private:

    Position insert_specific(ELEMENT &_data,AvlTree &_T);

    void showThisTree_specific(AvlTree _T);

    int getTreeHeight_specific(AvlTree _T);

    int max(int _a,int _b);

    int getHeight(Position _K);

    // 對於左左的分支,采用右旋 
    Position singleRotateWithLeft(Position _K);

    //對於右右的分支,采用左旋
    Position singleRotateWithRight(Position _K);

    // 對於左右的分支,采用先左旋后右旋
    Position doubleRotateWithLeft(Position _K);

    // 對於右左的分支,采用先右旋后左旋
    Position  doubleRotateWithRight(Position _K);
};

 

 

2.獲取高度:
  因為在max()函數獲取結束后需要+1,所以這里的目的是將葉節點的Height想辦法為0.

int CAVLTree::getHeight(Position _K){
    return (_K == nullptr )?-1:_K->Height;
}

 

 

3.插入操作:

  •   遞歸

  通過回溯的方式找到插入的位置,先平衡后調整高度;

  哈哈,有一個很有趣的細節為什么同時判斷高度差一個是

  if(getHeight(_T->left) - getHeight(_T->right) == 2)

  而另一個是

  if (getHeight(_T->right) - getHeight(_T->left) == 2)

  因為這里已經知道了插入發生在哪邊了,所以肯定是插入的那邊會有破壞平衡的可能,不會造成尷尬的(小-大)的局面.

Position CAVLTree::insert_specific(ELEMENT &_data,AvlTree &_T){
    if (!_T)
    {
        _T = new AvlNode;
        _T->data = _data;
        _T->Height = 0;
        size++;
    }
    else if(_data < _T->data)
    {
        _T->left = insert_specific(_data,_T->left);
        if(getHeight(_T->left) - getHeight(_T->right) == 2)
        {
            // 根據新插入的節點所在位置來判斷使用什么旋轉
            if(_data < _T->left->data)
            {
                // 需要右旋
                _T = singleRotateWithLeft(_T);
            }
            else
            {
                // 需要先左旋后右旋
                _T = doubleRotateWithLeft(_T);
            }
        }
    }
    else if (_data > _T->data)
    {
        _T->right = insert_specific(_data,_T->right);
        if (getHeight(_T->right) - getHeight(_T->left) == 2)
        {
            if (_data > _T->right->data)
            {
                // 需要左旋
                _T = singleRotateWithRight(_T);
            }
            else
            {
                // 需要先右旋再左旋
                _T = doubleRotateWithRight(_T);
            }
        }
    }

    _T->Height = max(getHeight(_T->left) , getHeight(_T->right)) + 1 ;
    
    return _T;
}

 

  

  •   非遞歸[3]:

  可以發現,非遞歸的實現是先調整高度再平衡,但是要提前考慮所有情況.

  考慮左子樹的情況:

void leftBalance(AVLNode* &t)
{
    AVLNode* lc = NULL;
    AVLNode* rd = NULL;
    lc = t->lchild;
    switch(lc->bf)
    {
        case LH:                    //順時針旋轉(即右旋) 
            t->bf = EH;
            lc->bf = EH;
            R_Rotate(t);        
            break;
        
        case EH:                    //刪除節點時會發生,插入不會發生
            t->bf = LH;
            lc->bf = RH;
            R_Rotate(t);
            break;
        
        case RH:                    //先左旋后右旋
            rd = lc->rchild;
            switch(rd->bf)
            {
                case LH:
                    t->bf = RH;
                    lc->bf = EH;
                    break;    
                case EH:
                    t->bf = EH;
                    lc->bf = EH;
                    break;
                case RH:
                    t->bf = EH;
                    lc->bf = LH;
                    break;
            }
            rd->bf = EH;
            L_Rotate(t->lchild);//不能寫L_Rotate(lc);采用的是引用參數 
            R_Rotate(t);
            break;
    }
}

 

 

   考慮右子樹的情況:

void rightBalance(AVLNode* &t)
{
    AVLNode* rc = NULL;
    AVLNode *ld = NULL;

    rc = t->rchild;
    switch(rc->bf)
    {
    case RH:                //逆時針旋轉(即左旋)
        t->bf = EH;
        rc->bf = EH;
        L_Rotate(t);
        break;
    case EH:                //刪除節點時會發生,插入不會發生 
        t->bf = RH;
        rc->bf = LH;
        L_Rotate(t);
        break;
    case LH:                //先右旋后左旋
        ld = rc->lchild; 
        switch(ld->bf)
        {
        case LH:
            t->bf = EH;
            rc->bf = RH;
            break;
        case EH:
            t->bf = EH;
            rc->bf = EH;
            break;
        case RH:
            t->bf = LH;
            rc->bf = EH;
            break;
        }
        ld->bf = EH;
        R_Rotate(t->rchild);//不能寫R_Rotate(rc);采用的是引用參數 
        L_Rotate(t);
        break;
    }
}

 

 

 總結:

  遞歸真是神奇啊,對子樹的處理遞歸的很漂亮,代碼量是一方面,代碼邏輯的清晰性也是非遞歸程序鮮有的.

  用這個來學習遞歸算法真是好工具,希望對於我后面復習圖論有幫助.

  這篇文章中所述的非遞歸程序我並沒有實現,肯定有疏忽的地方,歡迎大家指正.

 

完整示例中還有一個showThisTree(),它可以打印出漂亮的平衡二叉樹.

相關代碼請見我的github

 

[1]   AVL樹的旋轉操作 圖解 最詳細

[2] left 等價 leftChild,同理,right 也等價 rightChild.

[3]   平衡二叉樹(AVL)的插入和刪除詳解(上)

[4]  參考教材 數據結構與算法分析:C語言描述(原書第2版)[美] MarkAllenWeiss 著

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM