【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); }