AVL樹的旋轉操作詳解


【0】README

0.0) 本文部分idea 轉自:http://blog.csdn.net/collonn/article/details/20128205 
0.1) 本文僅針對性地分析AVL樹的單旋轉(左左單旋轉和右右單旋轉)和 雙旋轉(左右雙旋轉和右左單旋轉)的內部核心技巧; 
0.2) 不得不提的是,旋轉有兩個屬性: 軸 和 旋轉方向; (旋轉軸即是原最小樹經過旋轉修正后的符合AVL的最小樹的根節點)
0.3) 旋轉軸的確定 : (干貨——單雙旋轉的旋轉軸確定問題)

  • 0.3.1)單旋轉:旋轉軸為 不滿足AVL條件的最小樹的樹根的相應孩子節點
  • 0.3.2)多旋轉:旋轉軸為 不滿足AVL條件的最小樹的樹根的相應孫子節點

【1】 如何判斷進行單旋轉還是雙旋轉 (什么時候需要單旋轉,而什么時候需要多旋轉?)

1.0)高度不平衡需要α點的兩棵子樹高度差為2,故可得高度不平衡可能出現在下面四種情況中:

①  對α的左兒子的左子樹進行一次插入。

②  對α的左兒子的右子樹進行一次插入。

③  對α的右兒子的左子樹進行一次插入。

④  對α的右兒子的右子樹進行一次插入。

1.1)單旋轉: 插入點不介於 不滿足AVL條件的樹根 和 樹根對應孩子節點之間;  (情形①、③ 即 左-左、右-右)

1.2)雙旋轉:插入點介於 不滿足AVL條件的樹根 和 樹根對應孩子節點之間;(情形②、④ 即 左-右、右-左)


【2】單旋轉

2.1)左左旋轉(順時針旋轉): 從插入點回溯到第一個不滿足AVL條件的節點;本例中,插入點是10, 而第一個不滿足AVL條件的節點是30;將回溯路徑上的節點除節點30外,上移一層,節點30下移一層;

  • case1)
  (這是一個左右雙旋轉特例,當不符合AVL條件的樹根和插入點的父節點只有一個子節點,且相反方向的子節點,當然了,插入點要介於樹根和插入點父節點之間的話,才滿足 雙旋轉特例的條件)

Attention)

  • A1)因為10 小於 20 且 小於30; 所以通過一次單旋轉就可以完成; 
    (也即是, 左左單旋轉時, 不滿足AVL條件的最小樹的根應該下移,該樹的其他節點上移,而不管該樹的左子樹的右孩子或者存在或者不存在,在旋轉過程中,都要把該左子樹的的右孩子添加以作為最小樹根的左孩子,因為即使不存在,添加null 也不影響最后的旋轉效果)

  • case2) 
    這里寫圖片描述

2.2)右右旋轉(逆時針旋轉): 從插入點回溯到第一個不滿足AVL條件的節點;本例中,插入點是10, 而第一個不滿足AVL條件的節點是30;將回溯路徑上的節點除節點30外,上移一層,節點30下移一層;

  • case1 
     
    (這是一個右左雙旋轉特例,當不符合AVL條件的樹根和插入點的父節點只有一個子節點,且相反方向的子節點,當然了,插入點要介於樹根和插入點父節點之間的話,才滿足 雙旋轉特例的條件)

Attention)

  • A1)因為10 小於 20 且 小於30; 所以通過一次單旋轉就可以完成; 
    (也即是, 右右單旋轉時, 不滿足AVL條件的最小樹的根應該下移,該樹的其他節點上移,而不管該樹的右子樹的左孩子或者存在或不存在,在旋轉過程中,都要把該右子樹的左孩子添加以作為最小樹根的右孩子,因為即使不存在,添加null 也不影響最后的 旋轉效果)

  • case2)為什么經過右右單旋轉就可以修正成為 AVL 樹;因為 new point = 13 不在 4 和 7 之間, 所以一次單旋轉就可以了,無需雙旋轉; 
    (也就是說,new point 介於不滿足AVL條件的樹根和其孩子之間的話,那么就需要雙旋轉, 否則, 只需要 單旋轉就可以了) 
    這里寫圖片描述

Conclusion of single rotation)單旋轉有兩個屬性: 軸 和 旋轉方向

  • C1)單旋轉的軸: 相信你也看到了, 單旋轉的軸顯然是不符合AVL條件的樹根的直接孩子;

    • C1.1)左左單旋轉的軸:是不符合AVL條件的樹根的左孩子;
    • C1.2)右右單旋轉的軸:是不符合AVL條件的樹根的右孩子;
  • C2)旋轉方向:

    • C2.1)左左單旋轉方向:順時針方向;
    • C2.2)右右單旋轉方向:逆時針方向;

【3】雙旋轉

3.1)左右雙旋轉: (先左左單旋轉,再右右單旋轉; 即先順時針旋轉,后逆時針旋轉)

  • case1)因為47 介於 40 和 50 之間, 所以肯定需要雙旋轉; 
    這里寫圖片描述 
    這里寫圖片描述

3.2)右左雙旋轉:先將節點15向上提,還是不滿足AVL樹的條件,再把節點7向上提;(先右右單旋轉,再左左單旋轉; 即先逆時針旋轉,后順時針旋轉) 
這里寫圖片描述 
這里寫圖片描述

Conclusion of double rotations) 雙旋轉有兩個屬性: 軸 和 旋轉方向

    • C1)雙旋轉的軸:相信你也看到了, 雙旋轉的軸顯然是插入點的直接父節點;(除了兩個特例) (干貨——雙旋轉的軸顯然是插入點的直接父節點(除了兩個特例, 而兩個特例的軸是插入點本身))

      • C1.1)左右單旋轉的軸:插入點的父節點;
      • C1.2)右左單旋轉的軸:插入點的父節點;
    • C2)旋轉方向:

      • C2.1)左右單旋轉方向:先右右單旋轉,再左左單旋轉;即先逆時針旋轉,再順時針旋轉;
      • C2.2)右左單旋轉方向:先左左單旋轉,再右右單旋轉;即先順時針旋轉,再逆時針旋轉; 
        這里寫圖片描述

 

 

【4】示例圖與代碼參考

4.1)LL的旋轉

  圖中左邊是旋轉之前的樹,右邊是旋轉之后的樹。從中可以發現,旋轉之后的樹又變成了AVL樹,而且該旋轉只需要一次即可完成。 

  LL的旋轉代碼:

/*
 * LL:左左對應的情況(左單旋轉)。
 *
 * 返回值:旋轉后的根節點
 */
private AVLTreeNode<T> leftLeftRotation(AVLTreeNode<T> k2) {
    AVLTreeNode<T> k1;

    k1 = k2.left;
    k2.left = k1.right;
    k1.right = k2;

    k2.height = max( height(k2.left), height(k2.right)) + 1;
    k1.height = max( height(k1.left), k2.height) + 1;

    return k1;
}

  4.2)RR的旋轉

  圖中左邊是旋轉之前的樹,右邊是旋轉之后的樹。RR旋轉也只需要一次即可完成。

  RR的旋轉代碼:

/*
 * RR:右右對應的情況(右單旋轉)。
 *
 * 返回值:旋轉后的根節點
 */
private AVLTreeNode<T> rightRightRotation(AVLTreeNode<T> k1) {
    AVLTreeNode<T> k2;

    k2 = k1.right;
    k1.right = k2.left;
    k2.left = k1;

    k1.height = max( height(k1.left), height(k1.right)) + 1;
    k2.height = max( height(k2.right), k1.height) + 1;

    return k2;
}

  4.3)LR的旋轉

  第一次旋轉是圍繞"k1"進行的"RR旋轉",第二次是圍繞"k3"進行的"LL旋轉"。

  LR的旋轉代碼:

 

/*
 * LR:左右對應的情況(左雙旋轉)。
 *
 * 返回值:旋轉后的根節點
 */
private AVLTreeNode<T> leftRightRotation(AVLTreeNode<T> k3) {
    k3.left = rightRightRotation(k3.left);

    return leftLeftRotation(k3);
}

 

  4.4)RL的旋轉

  第一次旋轉是圍繞"k3"進行的"LL旋轉",第二次是圍繞"k1"進行的"RR旋轉"。

  RL的旋轉代碼:

/*
 * RL:右左對應的情況(右雙旋轉)。
 *
 * 返回值:旋轉后的根節點
 */
private AVLTreeNode<T> rightLeftRotation(AVLTreeNode<T> k1) {
    k1.right = leftLeftRotation(k1.right);

    return rightRightRotation(k1);
}

  綜上,最后的整體插入代碼為:

/* 
 * 將結點插入到AVL樹中,並返回根節點
 *
 * 參數說明:
 *     tree AVL樹的根結點
 *     key 插入的結點的鍵值
 * 返回值:
 *     根節點
 */
private AVLTreeNode<T> insert(AVLTreeNode<T> tree, T key) {
    if (tree == null) {
        // 新建節點
        tree = new AVLTreeNode<T>(key, null, null);
        if (tree==null) {
            System.out.println("ERROR: create avltree node failed!");
            return null;
        }
    } else {
        int cmp = key.compareTo(tree.key);

           if (cmp < 0) {    // 應該將key插入到"tree的左子樹"的情況
            tree.left = insert(tree.left, key);
            // 插入節點后,若AVL樹失去平衡,則進行相應的調節。
            if (height(tree.left) - height(tree.right) == 2) {
                if (key.compareTo(tree.left.key) < 0)
                    tree = leftLeftRotation(tree);
                else
                    tree = leftRightRotation(tree);
            }
        } else if (cmp > 0) {    // 應該將key插入到"tree的右子樹"的情況
            tree.right = insert(tree.right, key);
            // 插入節點后,若AVL樹失去平衡,則進行相應的調節。
            if (height(tree.right) - height(tree.left) == 2) {
                if (key.compareTo(tree.right.key) > 0)
                    tree = rightRightRotation(tree);
                else
                    tree = rightLeftRotation(tree);
            }
        } else {    // cmp==0
            System.out.println("添加失敗:不允許添加相同的節點!");
        }
    }

    tree.height = max( height(tree.left), height(tree.right)) + 1;

    return tree;
}

public void insert(T key) {
    mRoot = insert(mRoot, key);
}

 


免責聲明!

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



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