平衡二叉樹(AVL)

Part One : 
這個恐怕是整個《數據結構》教科書裏面最難的和最「沒用」的數據結構了(如今的教科書還有部分算法內容)。說它沒用,偏偏是由於它太有用——有着和普通的二叉搜索樹徹底同樣的接口界面,絕大多數狀況下比普通的二叉搜索樹效率高(不少)。所以,一般狀況下,人們都是一勞永逸的——寫完後就重用,而不會再寫了。因此說,你雖然學完了平衡二叉樹,但極可能你永遠也不會親自寫一個。你如今隨便在身邊拉我的,讓他來寫一個,能順利的寫出來的恐怕很少,玩笑之詞,且勿當真。

在開始寫以前,我很擔憂,能不能把這部分寫清楚,畢竟書上滿天的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…caseui

Ø       旋轉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;

}

你能夠看到,他們是多麼的對稱。

Part Two:
平衡二叉樹 (Balanced binary tree) 是由阿德爾森 - 維爾斯和蘭迪斯 (Adelson-Velskii and Landis) 1962 年首先提出的,因此又稱爲 AVL 樹。

定義:平衡二叉樹或爲空樹,或爲以下性質的二叉排序樹:

  1)左右子樹深度之差的絕對值不超過1;

  2)左右子樹仍然爲平衡二叉樹.

      平衡因子BF=左子樹深度-右子樹深度.

平衡二叉樹每一個結點的平衡因子只能是10-1。若其絕對值超過1,則該二叉排序樹就是不平衡的。

如圖所示爲平衡樹和非平衡樹示意圖:

2、平衡二叉樹算法思想

若向平衡二叉樹中插入一個新結點後破壞了平衡二叉樹的平衡性。首先要找出插入新結點後失去平衡的最小子樹根結點的指針。而後再調整這個子樹中有關結點之間的連接關係,使之成爲新的平衡子樹。當失去平衡的最小子樹被調整爲平衡子樹後,原有其餘全部不平衡子樹無需調整,整個二叉排序樹就又成爲一棵平衡二叉樹。

        失去平衡的最小子樹是指以離插入結點最近,且平衡因子絕對值大於1的結點做爲根的子樹。假設用A表示失去平衡的最小子樹的根結點,則調整該子樹的操做可概括爲下列四種狀況。

 1LL型平衡旋轉法

因爲在A的左孩子B的左子樹上插入結點F,使A的平衡因子由1增至2而失去平衡。故需進行一次順時針旋轉操做。即將A的左孩子B右上旋轉代替A做爲根結點,A右下旋轉成爲B的右子樹的根結點。而原來B的右子樹則變成A的左子樹。

2RR型平衡旋轉法

因爲在A的右孩子C的右子樹上插入結點F,使A的平衡因子由-1減至-2而失去平衡。故需進行一次逆時針旋轉操做。即將A的右孩子C左上旋轉代替A做爲根結點,A左下旋轉成爲C的左子樹的根結點。而原來C的左子樹則變成A的右子樹。

3LR型平衡旋轉法

因爲在A的左孩子B的右子數上插入結點F,使A的平衡因子由1增至2而失去平衡。故需進行兩次旋轉操做(先逆時針,後順時針)。即先將A結點的左孩子B的右子樹的根結點D左上旋轉提高到B結點的位置,而後再把該D結點向右上旋轉提高到A結點的位置。即先使之成爲LL型,再按LL型處理

      如圖中所示,即先將圓圈部分先調整爲平衡樹,而後將其以根結點接到A的左子樹上,此時成爲LL型,再按LL型處理成平衡型。

4RL型平衡旋轉法 

因爲在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的節點。


相關文章
相關標籤/搜索