在開始寫以前,我很擔憂,能不能把這部分寫清楚,畢竟書上滿天的switch…case,而且還只是一半——有左旋沒有右旋,有插入沒有刪除。後來,我變得有信心了——由於書上都沒有說清楚,都在那裏說夢話。我沒有找到AVL樹的發明者的原著(G. M. Adelson-Velskii and Y. M. Landis. An algorithm for the organization of information. Soviet Math. Dokl., 3:1259--1262, 1962.)也不知道我下面所寫的是否是體現了發明者的本意,但至少,我認爲如今的教科書歪曲了發明者的本意。node
Ø 平衡算法
下面的引文出自Algorithms and Data Structures (Niklaus Wirth, Prentice-Hall, Englewood Cliffs, NJ, 1986 ISBN: 0-13-022005-1 pp. 215 – 226)數據結構
One such definition of balance has been postulated by Adelson-Velskii and Landis [4-1]. The balance criterion is the following:app
A tree is balanced if and only if for every node the heights of its two subtrees differ by at most 1.ide
Trees satisfying this condition are often called AVL-trees (after their inventors). We shall simply call them balanced trees because this balance criterion appears a most suitable one. (Note that all perfectly balanced trees are also AVL-balanced.)函數
The definition is not only simple, but it also leads to a manageable rebalancing procedure and an average search path length practically identical to that of tbe perfectly balanced tree.post
科技文都比較好懂,本人翻譯水平比較差,就不獻醜了,我只想讓你們注意最後一段的畫線部分,平衡化應該是易於操做的,而毫不是如今你在書上看到的鋪天蓋地的switch…case。ui
Ø 旋轉this
平衡化靠的是旋轉。參與旋轉的是3個節點(其中一個多是外部節點NULL),旋轉就是把這3個節點轉個位置。注意的是,左旋的時候p->right必定不爲空,右旋的時候p->left必定不爲空,這是顯而易見的。spa
p |
|
|
|
p |
p |
|
左旋 |
|
t |
|
t |
|
(p) |
|
(NULL) |
|
|
|
(NULL) |
能夠看到,左旋確實是在向「左」旋轉,仍是很形象的。右旋是左旋的鏡像,就再也不另行說明了。下表是左旋和右旋各個節點的指針變換狀況。(括號表示NULL的狀況不執行)
左旋 |
右旋 |
||
t->parent = p->parent |
p->parent = t |
t->parent = p->parent |
p->parent = t |
(t->left->parent = p) |
p->right = t->left |
(t->right->parent = p) |
p->left = t->right |
t->left = p |
p = t |
t->right = p |
p = t |
Ø 平衡因子(bf——balance factor)
AVL樹的平衡化靠旋轉,而是否須要平衡化,取決於樹中是否出現了不平衡。爲了不每次判斷平衡時,都求一下左右子樹的高度,引入了平衡因子。極可能是1962年的時候AV&L沒有親自給出定義,時下里平衡因子的定義亂七八糟——我看了4本書,兩本是bf = 左高-右高,兩本是bf = 右高-左高。最有意思的是兩本中國人(嚴蔚敏和殷人昆)寫的一本左減右,一本右減左;兩本外國人寫的也是這樣。雖然沒什麼原則上的差異,可苦了中國的莘莘學子們——考試的時候可無論你是哪一個門派的。我照顧本身的習慣,下面的bf = 左高-右高,習慣不一樣的請本身注意。
這樣一來,是否須要平衡化的條件就很明瞭了——| bf | > 1。若是從空樹開始創建,並時刻保持平衡,那麼不平衡只會發生在插入刪除操做上,而不平衡的標誌就是出現bf == 2或者 bf == -2的節點。
在AVL樹插入和刪除,實際上就是先按照普通二叉搜索樹插入和刪除,而後再平衡化。能夠確定的說,插入和刪除須要的最多平衡化次數不一樣(下面會給出根本緣由),但這不代表插入和刪除時的平衡化的思路有很大差異。現有的教科書,僅僅從表面上看到了到了平衡化操做次數不一樣的假象,而沒有從根本上認識到插入和刪除對稱的本質,搞得亂七八糟不說(鋪天蓋地的switch…case),還嚴重的誤導了讀者——覺得刪除操做複雜的不可捉摸。
AVL樹體現了一種平衡的美感,兩種旋轉是互爲鏡像的,插入刪除是互爲鏡像的操做,沒理由會有那麼大的差異。實際上,平衡化能夠統一的這樣來操做:
1. while (current != NULL)修改current的平衡因子。
Ø 插入節點時current->bf += (current->data > *p)?1:-1;
Ø 刪除節點時current->bf -= (current->data > *p)?1:-1;
Ø current指向插入節點或者實際刪除節點的父節點,這是普通二叉搜索樹的插入和刪除操做帶來的結果。*p初始值是插入節點或者實際刪除節點的data。由於刪除操做可能實際刪除的不是data。
2. 判斷是否須要平衡化
if (current->bf == -2) L_Balance(c_root); else if (current->bf == 2) R_Balance(c_root);
3. 是否要繼續向上修改父節點的平衡因子
Ø 插入節點時if (!current->bf) break;這時,以current爲根的子樹的高度和插入前的高度相同。
Ø 刪除節點時if (current->bf) break;這時,以current爲根的子樹的高度和刪除前的高度相同
Ø 之因此刪除操做須要的平衡化次數多,就是由於平衡化不會增長子樹的高度,可是可能會減小子樹的高度,在有有可能使樹增高的插入操做中,一次平衡化能抵消掉增高;在有可能使樹減低的刪除操做中,平衡化可能會帶來祖先節點的不平衡。
4. 當前節點移動到父節點,轉1
p = &(current->data); current = current->parent;
完整的插入刪除函數以下:
bool insert(const T &data)
{
if (!BSTree<T>::insert(data)) return false; const T* p = &data;
while (current)
{
current->bf += (current->data > *p)?1:-1;
if (current->bf == -2) L_Balance(c_root);
else if (current->bf == 2) R_Balance(c_root);
if (!current->bf) break;
p = &(current->data); current = current->parent;
}
return true;
}
bool remove(const T &data)
{
if (!BSTree<T>::remove(data)) return false; const T* p = &r_r_data;
//在class BSTree裏添加proteceted: T r_r_data,在BSTree<T>::remove(const T &data)裏修改成實際刪除的節點的data
while (current)
{
current->bf -= (current->data > *p)?1:-1;
if (current->bf == -2) L_Balance(c_root);
else if (current->bf == 2) R_Balance(c_root);
if (current->bf) break;
p = &(current->data); current = current->parent;
}
return true;
}
你能夠看到,他們是多麼的對稱。
定義:平衡二叉樹或爲空樹,或爲以下性質的二叉排序樹:
(1)左右子樹深度之差的絕對值不超過1;
(2)左右子樹仍然爲平衡二叉樹.
平衡因子BF=左子樹深度-右子樹深度.
平衡二叉樹每一個結點的平衡因子只能是1,0,-1。若其絕對值超過1,則該二叉排序樹就是不平衡的。
如圖所示爲平衡樹和非平衡樹示意圖:
2、平衡二叉樹算法思想
若向平衡二叉樹中插入一個新結點後破壞了平衡二叉樹的平衡性。首先要找出插入新結點後失去平衡的最小子樹根結點的指針。而後再調整這個子樹中有關結點之間的連接關係,使之成爲新的平衡子樹。當失去平衡的最小子樹被調整爲平衡子樹後,原有其餘全部不平衡子樹無需調整,整個二叉排序樹就又成爲一棵平衡二叉樹。
失去平衡的最小子樹是指以離插入結點最近,且平衡因子絕對值大於1的結點做爲根的子樹。假設用A表示失去平衡的最小子樹的根結點,則調整該子樹的操做可概括爲下列四種狀況。
(1)LL型平衡旋轉法
因爲在A的左孩子B的左子樹上插入結點F,使A的平衡因子由1增至2而失去平衡。故需進行一次順時針旋轉操做。即將A的左孩子B向右上旋轉代替A做爲根結點,A向右下旋轉成爲B的右子樹的根結點。而原來B的右子樹則變成A的左子樹。
(2)RR型平衡旋轉法
因爲在A的右孩子C的右子樹上插入結點F,使A的平衡因子由-1減至-2而失去平衡。故需進行一次逆時針旋轉操做。即將A的右孩子C向左上旋轉代替A做爲根結點,A向左下旋轉成爲C的左子樹的根結點。而原來C的左子樹則變成A的右子樹。
(3)LR型平衡旋轉法
因爲在A的左孩子B的右子數上插入結點F,使A的平衡因子由1增至2而失去平衡。故需進行兩次旋轉操做(先逆時針,後順時針)。即先將A結點的左孩子B的右子樹的根結點D向左上旋轉提高到B結點的位置,而後再把該D結點向右上旋轉提高到A結點的位置。即先使之成爲LL型,再按LL型處理。
如圖中所示,即先將圓圈部分先調整爲平衡樹,而後將其以根結點接到A的左子樹上,此時成爲LL型,再按LL型處理成平衡型。
(4)RL型平衡旋轉法
因爲在A的右孩子C的左子樹上插入結點F,使A的平衡因子由-1減至-2而失去平衡。故需進行兩次旋轉操做(先順時針,後逆時針),即先將A結點的右孩子C的左子樹的根結點D向右上旋轉提高到C結點的位置,而後再把該D結點向左上旋轉提高到A結點的位置。即先使之成爲RR型,再按RR型處理。
如圖中所示,即先將圓圈部分先調整爲平衡樹,而後將其以根結點接到A的左子樹上,此時成爲RR型,再按RR型處理成平衡型。
平衡化靠的是旋轉。參與旋轉的是3個節點(其中一個多是外部節點NULL),旋轉就是把這3個節點轉個位置。注意的是,左旋的時候p->right必定不爲空,右旋的時候p->left必定不爲空,這是顯而易見的。
若是從空樹開始創建,並時刻保持平衡,那麼不平衡只會發生在插入刪除操做上,而不平衡的標誌就是出現bf == 2或者 bf == -2的節點。