平衡二叉樹 AVL 的插入節點後旋轉方法分析
平衡二叉樹 AVL( 發明者為Adel'son-Vel'skii 和 Landis)是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等於1。
首先我們知道,當插入一個節點,從此插入點到樹根節點路徑上的所有節點的平衡都可能被打破,如何解決這個問題呢?
這裡不講大多數書上提的什麼平衡因子,什麼最小不平衡子樹,實際上讓人(me)更加費解。實際上你首要做的就是先找到第一個出現不平衡的節點,也就是從插入點到root節點的路徑上第一個出現不平衡的節點,即深度最深的那個節點A,對以它為根的子樹做一次旋轉或者兩次旋轉,此時這個節點的平衡問題解決了,整個往上路徑經過的節點平衡問題也隨之解決。
注:AVL 樹也是一種二叉查詢樹,故刪除策略可以參照前面文章來實現,只是刪除節點後,如果平衡被打破,則也需要進行旋轉以保持平衡。
After deletion, retrace the path back up the tree (parent of the replacement) to the root, adjusting the balance factors as needed.
下面先來分析下不平衡發生的四種情況:
1、An insertion into the left subtree of the left child of A; (LL)
2、An insertion into the right subtree of the left child of A;(RL)
3、An insertion into the left subtree of the right child of A;(LR)
4、An insertion into the right subtree of the right child of A;(RR)
旋轉方法:
1、A 和 A's child 順時針旋轉 singlerotateLL()
4、A 和 A's child 逆時針旋轉 singlerotateRR()
2、A's child 和 A's grandchild 逆時針旋轉,A 和 A's new child 順時針旋轉 doublerotateRL()
3、A's child 和 A's grandchild 順時針旋轉,A 和 A's new child 逆時針旋轉 doublerotateLR()
可以看出1,4對稱;2,3對稱,實際在實現 rotate 函式的時候實現1和4 即可,2和3直接呼叫1&4的實現。在實現1&4時需要傳遞需要旋轉的子樹的root node 作為引數,如 nodePtr doublerotateRL(A) { A->left = singlerotateRR(A->left); return singlerotateLL(A);}
當然,這樣說還是對應不上,下面上圖分析。
第一種情況舉例:
現在想要插入的點是6,請看是否符合第一種情況的描述。8是不是深度最深的發生不平衡的點?6是不是插入在A的左孩子的左子樹?符合是吧,那就直接按上述方法順時針旋轉7和8,效果是右圖。當然這只是邏輯上的檢視而已,真正函式實現也不難,就是修改兩個指標指向,待會再談。第4種情況是對稱的,就不說了。
下面來看第三種情況示例:
現在要插入的點是14,請看是否符合第3種情況的描述。6是不是深度最深的發生不平衡的點?14是不是插入在A的右孩子的左子樹?符合是吧,那就先順時針旋轉7和15,中間結果如下圖所示:
現在7是A的new child了是吧,那就逆時針旋轉6和7就可以了。
接著來分析singlerotateLL() 和doublerotateRL() 的實現,剩下兩個函式就是對稱的了。
首先是singlerotateLL(),看下圖:
改動其實很簡單,先把Y解綁當k2的左孩子,接著k2成為k1的右孩子,程式碼如下:
1
2 3 4 5 6 7 8 9 10 11 |
/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2) { AVLNodePtr 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; } |
接著是doublerotateRL()的實現,看下圖:
很明顯B或者C的插入使得k3(A)不平衡了,那麼首先應該是k1和k2逆時針旋轉,所以呼叫
k3->left = singlerotateRR(k3->left);
接著是k3和new child 順時針旋轉,呼叫singlerotateLL(k3);
so easy, 程式碼如下:
1
2 3 4 5 |
static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{ k3->left = singlerotateRR(k3->left); return singlerotateLL(k3); } |
完整的測試程式碼如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
#include <stdio.h>
#include <stdlib.h> struct AVLNode; typedef struct AVLNode *AVLNodePtr; struct AVLNode { int element; AVLNodePtr left; AVLNodePtr right; int height; }; void makeempty(AVLNodePtr T) { if (T == NULL) return; else { makeempty(T->left); makeempty(T->right); free(T); } } static int height(AVLNodePtr p) { if (p == NULL) return -1; else return p->height; } static int Max(int ln, int rn) { return ln > rn ? ln : rn; } /* return pointer to the new root */ static AVLNodePtr singlerotateLL(AVLNodePtr k2) { AVLNodePtr 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; } static AVLNodePtr singlerotateRR(AVLNodePtr k1) { AVLNodePtr k2 = k1->right; k1->right = k2->left; k2->left = k1; k1->height = Max(height(k1->left), height(k1->right)) + 1; k2->height = Max(k1->height, height(k2->right)) + 1; return k2; } static AVLNodePtr doublerotateRL(AVLNodePtr k3) { k3->left = singlerotateRR(k3->left); return singlerotateLL(k3); } static AVLNodePtr doublerotateLR(AVLNodePtr k3) { k3->right = singlerotateLL(k3->right); return singlerotateRR(k3); } AVLNodePtr insert(int X, AVLNodePtr T) { if (T == NULL) { /* create and return a one-node tree */ T = (AVLNodePtr)malloc(sizeof(struct AVLNode)); if (T == NULL) { printf("out of space!"); exit(1); } else { T->element = X; T->height = 0; T->left = T->right = NULL; } } else if (X < T->element) { T->left = insert(X, T->left); if (height(T->left) - height(T->right) == 2) { if (X < T->left->element) T = singlerotateLL(T); else T = doublerotateRL(T); } } else if (X > T->element) { T->right = insert(X, T->right); if (height(T->right) - height(T->left) == 2) { if (X > T->right->element) T = singlerotateRR(T); else T = doublerotateLR(T); } } /* else X is in the tree already; we'll do nothing */ T->height = Max(height(T->left), height(T->right)) + 1; return T; } void inorder(AVLNodePtr T) { if (T == NULL) return; else { inorder(T->left); printf("%d ", T->element); inorder(T->right); } } int main(void) { int arr[] = {3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8, 9}; AVLNodePtr T = NULL; for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) T = insert(arr[i], T); inorder(T); makeempty(T); return 0; } |
程式碼將陣列元素插入後,中序遍歷後輸出,即1~16的順序排列。
注意:輸入陣列元素就不要搞成有序的了,如果程式碼中沒有調整的實現,整個樹就是個右斜樹,但即使實現了調整,也會使得每插入一次就調整一次,何必內耗啊。很顯然,平衡二叉樹的優勢在於不會出現普通二叉查詢樹的最差情況。其查詢的時間複雜度為O(logN)。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
注:理解旋轉函式的實現關鍵在於理解實現時的限制:
The key to understanding how a rotation functions
is to understand its constraints. In particular the order of the leaves of the tree (when read left to right for example) cannot change (another way to think of it is that the order that the leaves would be visited in a depth first search must be the same
after the operation as before ).
Another constraint is the main property of
a binary search tree, namely that the right child is greater than the parent and the left child is lesser than the parent.
Detailed illustration:
Using the terminology of Root for
the parent node of the subtrees to rotate, Pivot for the node which will become the new parent node,RS for rotation side upon to rotate and OS for opposite side of rotation. In the above diagram for the root
Q, the RS is C and the OS is P. The pseudo code for the rotation is:
Pivot
= Root.OS
Root.OS = Pivot.RS
Pivot.RS = Root
Root = Pivot
參考:
《data structure and algorithm analysis in c》
《Data Structures》
相關文章
- 平衡二叉樹(AVL)二叉樹
- 平衡二叉樹AVL二叉樹
- 手擼二叉樹——AVL平衡二叉樹二叉樹
- 平衡二叉樹(AVL樹),原來如此!!!二叉樹
- 十三、Mysql之平衡二叉樹(AVL樹)MySql二叉樹
- 自動平衡二叉樹的構建-AVL樹二叉樹
- 平衡二叉樹(AVL樹)和 二叉排序樹轉化為平衡二叉樹 及C語言實現二叉樹排序C語言
- Java集合原始碼分析之基礎(五):平衡二叉樹(AVL Tree)Java原始碼二叉樹
- Java 樹結構實際應用 四(平衡二叉樹/AVL樹)Java二叉樹
- 演算法與資料結構——AVL樹(平衡二叉搜尋樹)演算法資料結構
- 詳解什麼是平衡二叉樹(AVL)(修訂補充版)二叉樹
- 資料結構與演算法-二叉查詢樹平衡(AVL)資料結構演算法
- 資料結構高階--AVL(平衡二叉樹)(圖解+實現)資料結構二叉樹圖解
- 看動畫學演算法之:平衡二叉搜尋樹AVL Tree動畫演算法
- 二叉樹兩個節點的公共節點二叉樹
- 平衡二叉樹二叉樹
- 資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)資料結構二叉樹
- 排序二叉樹和平衡二叉樹排序二叉樹
- 平衡樹和二叉樹的區別二叉樹
- 平衡二叉樹,B樹,B+樹二叉樹
- 5分鐘瞭解二叉樹之AVL樹二叉樹
- 【筆記】平衡二叉樹筆記二叉樹
- AVL樹(查詢、插入、刪除)——C語言C語言
- 平衡二叉查詢樹:紅黑樹
- 紅黑樹和平衡二叉樹的區別二叉樹
- JZ-039-平衡二叉樹二叉樹
- 二叉堆、BST 與平衡樹
- 夜刷:平衡二叉樹的基本操作二叉樹
- 建立二叉樹:層次遍歷--樹的寬度高度,後序遍歷--祖先節點二叉樹
- Java實現紅黑樹(平衡二叉樹)Java二叉樹
- 二叉樹的深度、寬度遍歷及平衡樹二叉樹
- 008,二叉樹的下一個節點二叉樹
- 快速求完全二叉樹的節點個數二叉樹
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- day 15|二叉樹part03|110.平衡二叉樹|257. 二叉樹的所有路徑|404.左葉子之和| 222.完全二叉樹的節點個數二叉樹
- Java中在二叉搜尋樹中查詢節點的父節點Java
- 資料結構-平衡二叉樹資料結構二叉樹
- 110. 平衡二叉樹二叉樹