AVL樹旋轉

烏衣行者發表於2020-08-19

  什麼是AVL樹?

  AVL樹是帶有平衡條件的二叉查詢樹,一顆AVL樹首先是二叉查收樹(每個節點如果有左子樹或右子樹,那麼左子樹中資料小於該節點資料,右子樹資料大於該節點資料),其次,AVL樹必須滿足平衡條件:每個節點的左子樹和右子樹的高度最多相差1(空樹的高度定義為-1)。

  

  什麼是旋轉?AVL樹為什麼需要用到旋轉?

  由於AVL樹本身的性質,當我們插入節點時,有可能會破壞AVL樹的平衡性,使一棵樹的左子樹和右子樹的高度相差大於1,此時就需要對樹進行一些簡單的修正來恢復其性質,這個修正的過程就叫做旋轉

  我們來看一個簡單的例子,比如這棵樹,他在插入節點之後不滿足AVL樹的性質,這時我們可以使用一個旋轉來使他成為一顆AVL樹。

  旋轉前:              

                                                                                  3

                     /

                    2

                   /

                  1

  這棵樹根節點為3,插入2之後左右子樹高度相差1,再插入1之後左右子樹高度相差2(左子樹高度為1,右子樹高度為-1),此時這棵樹不滿足AVL樹的條件,對這棵樹進行旋轉操作。

  旋轉後:                    

                                                                              2

                   /  \

                    1    3

  在經過一次旋轉之後,這棵樹的根節點為2,左右子樹分別為1和3,滿足AVL樹的條件,插入完成。
  

  如何對結點進行旋轉,使其滿足AVL樹的條件?

  ·單旋轉:

  當新插入的節點在二叉樹的外側(左子樹的左側或右子樹的右側),並且此時破壞了AVL樹的平衡,我們使用一個單旋轉來恢復AVL樹的性質。

  以左側單旋轉為例,比如剛才那個例子中,旋轉前根節點為3,左子樹高度為1,右子樹高度為-1。此時我們先讓左子樹2的右子樹(在這裡為NULL)變為根節點的新左子樹

                    3

         2                /  \

      /         NULL    NULL

     1

 

  再讓原來的根節點3變為節點2的右子樹

                   2

                  /        \

                 1    3

  此時可以算是完成了一次單旋轉,2變為新的根節點。這個旋轉後的樹滿足AVL樹的條件。

  左側單旋轉程式碼:

  

 1 typedef struct TreeNode
 2 {
 3     ElementType Element;
 4     struct TreeNode *Left;
 5     struct TreeNode *Right;
 6     int Height; 
 7 }*AvlTree; 
 8 int NodeHeight(AvlTree P)
 9 {
10     if(P == NULL) return -1;
11     else return P->Height;
12 }
13 AvlTree SingleRotateWithLeft(AvlTree T)
14 {
15     /* T指向原來的根節點,T1指向旋轉後的根節點 */
16      AvlTree T1;
17      T1 = T->Left;
18     
19     /* 根節點的左子樹等於其原來左子樹的右子樹 */
20     T->Left = T1->Right;
21 
22     /* 讓原來的根節點成為新的根節點的右子樹 */
23     T1->Right = T;
24 
25     /* 重新設定節點高度 */
26     T->Height = Max(NodeHeight(T->Left),NodeHeight(T->Right))+1;
27     T1->Height = Max(NodeHeight(T1->Left),T->Height)+1;
28 
29     /* 將新的根節點返回 */
30     return T1;
31 }

 

   右側單旋轉和左側差不多:

          1                

           \                     

            2            

             \

               3

 

                                             2

                                        /        \

                                     1             3     

 1 AvlTree SingleRotateWithRight(AvlTree T)
 2 {
 3     AvlTree T1;
 4     T1 = T->Right;
 5     T->Right = T1->Left;
 6     T1->Left = T;
 7     T->Height = Max(NodeHeight(T->left),NodeHeight(T->Right))+1;
 8     T1->Height = Max(T->Height,NodeHeight(T1->Right))+1;
 9     return T1;
10 }

 

  ·雙旋轉

  當新插入的節點在二叉樹的內側(左子樹的右側或右子樹的左側),並且此時破壞了AVL樹的平衡,我們使用一個單旋轉來恢復AVL樹的性質。

  這裡還是先以左側雙旋轉為例,我們來嘗試建立一棵樹並初始化,並設根節點為3

                                3

                               /  \

                              NULL  NULL

  我們插入一個1,由於這個樹應滿足二叉查詢樹的條件,所以1應該插入根節點3的左側

                                3

                               /  \

                              1    NULL

  再插入一個2,由二叉查詢樹條件,2應該插在1的右側

                                3

                               /  \

                              1    NULL

                               \

                                2

  此時,由於根節點左子樹和右子樹高度相差大於一,所以此時不滿足AVL樹的條件,此時需要一個雙旋轉來使這棵樹成為AVL樹

   首先,我們對根節點的左子樹1進行右側單旋轉:

  (根據單旋轉的方法,令 1 的右子樹等於原來右子樹 2 的左子樹 NULL ,再讓 1 成為 2 的左子樹,原來指向 1 的指標指向 2)

                                  3

                                 / 

                                2

                               / 

                              1   

  然後,再對根節點3進行左側單旋轉:

  (根據單旋轉的方法,令 3 的左子樹等於原來左子樹 2 的右子樹 NULL ,再讓 3 成為 2 的右子樹,2成為根節點)

                                

                                2

                               /  \

                               1   3

  此時,完成了一個雙旋轉,這棵樹滿足AVL樹的條件。

  看程式碼:

1 AvlTree DoubleRotateWithLeft(AvlTree T)
2 {
3     // 在根節點的左子樹進行右側單旋轉
4     T->Left = SingleRotateWithRight(T->Left);
5     
6     // 在根節點處進行左側單旋轉
7     return SingleRotateWithLeft(T);
8 }

 

  在右側進行雙旋轉和左側類似:

                               1

                                \

                                  3

                                /

                               2

  對根節點的右子樹進行左側單旋轉:

                               1

                                \

                                   2

                                  \

                                   3

  對根節點進行右側單旋轉:

                               2

                              /   \

                             1     3

  

1 AvlTree DoubleRotateWithRight(AvlTree T)
2 {
3     // 在T的右子樹進行左側單旋轉
4     T->Right = SingleRotateWithLeft(T->Right);
5     
6     // 在根節點T處進行右側單旋轉
7     return SingleRotateWithRight(T); 
8 }

  至此,我們已經看到了AVL樹的四種旋轉(左右單旋轉,左右雙旋轉),有了這些旋轉的方法,我們就可以在插入節點時進行判斷,判斷當前插入節點之後的樹是否需要進行旋轉,以及需要哪種旋轉,進而實現任意在AVL樹中插入節點。

  具體的插入節點程式碼實現不在這裡放出,可以參考《資料結構與演算法分析-C語言描述版》(本文中的觀點與程式碼大都來自此書,稍有改動,加入自己的理解)。

  看一下程式碼實現後的執行結果:

  

 

   注: 這裡輸入的最後一個引數 -1000 是輸入的結束條件,輸出的樹是逆時針旋轉90°之後的樹。

相關文章