1 .基本概念
AVL樹的複雜程度真是比二叉搜尋樹高了整整一個數量級——它的原理並不難弄懂,但要把它用程式碼實現出來還真的有點費腦筋。下面我們來看看:
1.1 AVL樹是什麼?AVL樹本質上還是一棵二叉搜尋樹(因此讀者可以看到我後面的程式碼是繼承自二叉搜尋樹的),它的特點是:
1. 本身首先是一棵二叉搜尋樹。
2. 帶有平衡條件:每個結點的左右子樹的高度之差的絕對值(平衡因子)最多為1。
例如:
5 5
/ \ / \
2 6 2 6
/ \ \ / \
1 4 7 1 4
/ /
3 3
上圖中,左邊的是AVL樹,而右邊的不是。因為左邊的樹的每個結點的左右子樹的高度之差的絕對值都最多為1,而右邊的樹由於結點6沒有子樹,導致根結點5的平衡因子為2。
1.2 為什麼要用AVL樹?有人也許要問:為什麼要有AVL樹呢?它有什麼作用呢?
我們先來看看二叉搜尋樹吧(因為AVL樹本質上是一棵二叉搜尋樹),假設有這麼一種極端的情況:二叉搜尋樹的結點為1、2、3、4、5,也就是:
1
\
2
\
3
\
4
\
5
聰明的你是不是發現什麼了呢?呵呵,顯而易見——這棵二叉搜尋樹其實等同於一個連結串列了,也就是說,它在查詢上的優勢已經全無了——在這種情況下,查詢一個結點的時間複雜度是O(N)!
好,那麼假如是AVL樹(別忘了AVL樹還是二叉搜尋樹),則會是:
2
/ \
1 4
/ \
3 5
可以看出,AVL樹的查詢平均時間複雜度要比二叉搜尋樹低——它是O(logN)。也就是說,在大量的隨機資料中AVL樹的表現要好得多。
1.3 旋轉假設有一個結點的平衡因子為2(在AVL樹中,最大就是2,因為結點是一個一個地插入到樹中的,一旦出現不平衡的狀態就會立即進行調整,因此平衡因子最大不可能超過2),那麼就需要進行調整。由於任意一個結點最多隻有兩個兒子,所以當高度不平衡時,只可能是以下四種情況造成的:
1. 對該結點的左兒子的左子樹進行了一次插入。
2. 對該結點的左兒子的右子樹進行了一次插入。
3. 對該結點的右兒子的左子樹進行了一次插入。
4. 對該結點的右兒子的右子樹進行了一次插入。
情況1和4是關於該點的映象對稱,同樣,情況2和3也是一對映象對稱。因此,理論上只有兩種情況,當然了,從程式設計的角度來看還是四種情況。
第一種情況是插入發生在“外邊”的情況(即左-左的情況或右-右的情況),該情況可以通過對樹的一次單旋轉來完成調整。第二種情況是插入發生在“內部”的情況(即左-右的情況或右-左的情況),該情況要通過稍微複雜些的雙旋轉來處理。
1.3.1 單旋轉情況1:對該結點的左兒子的左子樹進行了一次插入。
左邊為調整前得節點,我們可以看出k2的左右子樹已不再滿足AVL平衡條件,調整後的為右圖。
我們可以看出,解決辦法是將x上移一層,並將z下移一層,由於在原樹中k2 > k1,所以k2成為k1的右子樹,而y是小於k2的,所以成為k2的左子樹。
為了設計演算法,我們這裡來看一個更易理解的:插入的是節點“6”
演算法設計:由於是情形1對該結點的左兒子的左子樹進行了一次插入,該節點是“8”,首先我們不考慮其父節點的情況,因為我們建立節點是遞迴建立的,可以不用考慮其父節點與其的連線,這在後面遞迴建立的時候會說到,由於“8”的右孩子將不會發生變化,但是其左孩子設為“7”的右孩子,將7的右孩子設為“8”及其子樹,然後返回“7”節點的指標。
實現程式碼:
//情形1
AVLTree SingleRotateWithLeft(PAVLNode k2)
{
PAVLNode k1;
k1 = k2->l;
k2->l = k1->r;
k1->r = k2;
k2->h = MAX( Height( k2->l ), Height( k2->r ) ) + 1;
k1->h = MAX( Height( k1->l ), k2->h ) + 1;
return k1; /* New root */
}
情況4:對該結點的右兒子的右子樹進行了一次插入。
左邊為調整前得節點,我們可以看出k1的左右子樹已不再滿足AVL平衡條件,調整後的為右圖。
我們可以看出,解決辦法是將z上移一層,並將x下移一層,由於在原樹中k2 > k1,所以k1成為k2的左子樹,而y是大於k1的,所以成為k1的右子樹。
為了設計演算法,我們這裡來看一個更易理解的:插入的是節點“6”
演算法設計:由於是情形1對該結點的右兒子的右子樹進行了一次插入,該節點為“4”,我們同第一種情形類似。
實現程式碼:
//情形4
AVLTree SingleRotateWithRight(PAVLNode k1)
{
PAVLNode k2;
k2 = k1->r;
k1->r = k2->l;
k2->l = k1;
k1->h = MAX( Height( k1->l ), Height( k1->r ) ) + 1;
k2->h = MAX( Height( k2->r ), k1->h ) + 1;
return k2; /* New root */
}
1.3.2 雙旋轉
情況2:對該結點的左兒子的右子樹進行了一次插入。
這種情況是單旋轉調整不回來的,如下圖:
圖(1)
左--右雙旋轉如下:
圖(2)
這裡我們將圖(1)的Y子樹看成如圖(2),以k2為子樹根節點的樹,我們將其子樹分成比D低,這裡我我先對k3的左子樹進行一次情形四的右旋轉,然後在進行一次情形1的左旋轉,詳細步驟如下:(紅色框裡面的即是要進行單旋轉的)
實現程式碼:
//情形2
AVLTree DoubleRotateWithLeft( PAVLNode k3 )
{
/* Rotate between K1 and K2 */
k3->l = SingleRotateWithRight( k3->l );
/* Rotate between K3 and K2 */
return SingleRotateWithLeft(k3);
}
情況3:對該結點的右兒子的左子樹進行了一次插入。
右—左雙旋轉如下:
我們先對k1的右子樹進行一次左旋轉(情形1,然後再對k1進行一次右旋轉(情形4)。
實現程式碼:
//情形3
AVLTree DoubleRotateWithRight( PAVLNode k1 )
{
/* Rotate between K3 and K2 */
k1->r = SingleRotateWithLeft( k1->r );
/* Rotate between K1 and K2 */
return SingleRotateWithRight( k1 );
}
1.3 插入操作
插入的核心思路是通過遞迴找到合適的位置,插入新結點,然後看新結點是否平衡(平衡因子是否為2),如果不平衡的話,就分成三種大情況以及兩種小情況:
1. 在結點的左兒子(X < T->item)
o 在左兒子的左子樹 (X < T->l-> item),“外邊”,要做單旋轉。
o 在左兒子的右子樹 (X > T->l-> item),“內部”,要做雙旋轉。
2. 在結點的右兒子(X > T->item)
o 在右兒子的左子樹(X < T->r-> item),“內部”,要做雙旋轉。
o 在右兒子的右子樹(X > T->r-> item),“外邊”,要做單旋轉。
3. (X == T->item) ,對該節點的計數進行更新。
當進行了旋轉之後,必定會有結點的“父結點”是需要更新的,例如:
2
/ \
1 4
/ \
3 5
\
6
上圖是調整前的,下圖是調整後的:
4
/ \
2 5
/ \ \
1 3 6
可以看出,根結點2不平衡,是由於它的右兒子的右子樹插入了新的結點6造成的。因此,這屬於“外邊”的情況,要進行一次單旋轉。於是我們就把結點4調整上來作為根結點,再把結點2作為4的左兒子,最後把結點2的右兒子修改為原來的結點4的左兒子。
實現程式碼:
AVLTree Insert(Item X, AVLTree T )
{
if( T == NULL )
{
/* Create and return a one-node tree */
T = (PAVLNode)malloc( sizeof(AVLNode ) );
if( T == NULL )
perror("malloc failed");
else
{
T->item = X;
T->h = 0;
T->l = T->r = NULL;
T->count = 1;
}
}
else if(compare(&X,&T->item) == -1)//插入情況1
{
T->l = Insert( X, T->l );
if( Height( T->l ) - Height( T->r ) == 2 )
if(compare(&X, &T->l->item ) == -1)//左邊左子樹 單旋轉
T = SingleRotateWithLeft( T );
else
T = DoubleRotateWithLeft( T );//左邊右子樹
}
else if( compare(&X,&T->item) == 1 ) //插入情況2
{
T->r = Insert( X, T->r );
if( Height( T->r ) - Height( T->l ) == 2 )
if(compare(&X , &T->r->item) == 1)//右邊右子樹 單旋轉
T = SingleRotateWithRight( T );
else
T = DoubleRotateWithRight( T );//右邊左子樹
}
else//插入情況3
T->count++;
/* Else X is in the tree already; we'll do nothing */
T->h = MAX( Height( T->l ), Height( T->r ) ) + 1;
return T;
}