5分鐘瞭解二叉樹之AVL樹

morningli發表於2022-05-13

轉載請註明出處:https://www.cnblogs.com/morningli/p/16033733.html

 

AVL樹是帶有平衡條件的二叉查詢樹,其每個節點的左子樹和右子樹的高度最多相差1。為了保持AVL樹始終平衡,每次插入和刪除都需要進行額外的平衡操作。

 

上面兩個二叉搜尋樹,A是AVL樹,而B不是。

為什麼需要平衡二叉樹?

二叉搜尋樹一定程度上可以提高搜尋效率,但是因為二叉樹沒有對樹的形狀進行限制,很容易就退化成了一個連結串列,搜尋效率降低為 O(n)。

這裡說明會導致二叉搜尋樹退化的兩種原因:

1、插入的資料是有序地,比如先後插入1,2,3,4,5,會產生下面的二叉搜尋樹:

 

 

 2、因為二叉搜尋樹的刪除操作總是用右子樹的節點替換被刪除的節點,所以在不斷的插入刪除後,左子樹會比右子樹更深。現在已經證明,如果交替插入和刪除O(N2)次,那麼樹的期望深度將是O(√N)。

為了避免二叉查詢樹搜尋效率的惡化,我們需要對二叉樹的深度進行限制,避免過深的二叉搜尋樹。

一種解決辦法是要有一個稱為平衡(balance)的附加結構條件:任何節點的深度均不可以過深。AVL樹就是這樣的加了平衡條件的最古老的平衡查詢樹。

另一種辦法是不對樹的深度做限制,但是每次操作都對樹做一些調整,使後面的操作效率更高,保證連續M次操作在最壞的情況下花費O(MlogN)的時間。這種資料結構叫伸展樹(splay tree)。這種樹我們平時較少遇到,可以有興趣再去研究。

時間複雜度

AVL 樹的只讀操作涉及執行與在不平衡二叉搜尋樹上執行的操作相同的操作,但修改必須觀察和恢復子樹的高度平衡。

平衡係數(balance factor)

在二叉樹中,節點 X 的平衡因子定義為它的兩個子樹的高度差:

如果不變數

對於每個節點成立,二叉樹被定義為AVL 樹。BF(X) < 0 的節點被稱為`left-heavy`,BF(X) > 0 稱為 `right-heavy`,BF(X) = 0 簡單稱為 `balanced`。

最小失衡子樹

每次插入新節點後,只有那些從插入點到根節點的路徑上的節點的平衡有可能被改變,因為只有這些節點的子樹可能發生變化。在新插入的結點向上查詢,以第一個平衡因子的絕對值超過 1 的結點為根的子樹稱為最小不平衡子樹。一棵失衡的樹,是有可能有多棵子樹同時失衡的。可以證明,我們只要調整最小的不平衡子樹,就能夠將不平衡的樹調整為平衡的樹。

新增元素

我們把必須重新平衡的節點叫做 α。由於任意節點最多有兩個兒子,因此出現高度不平衡就需要 | BF(α) | = 2。容易看出,這種不平衡可能出現在下面的4種情況中:

1. 對 α 的左兒子的左子樹進行一次插入

2. 對 α 的左兒子的右子樹進行一次插入

3. 對 α 的右兒子的左子樹進行一次插入

4. 對 α 的右兒子的右子樹進行一次插入

情形1和4是關於 α 點的映象對稱,情形2和3也是關於 α 點的映象對稱。理論上只有兩種基本情況。

第一種是發生在“外邊”的情況(即左左和右右的情況),這種情況只需要做一次單旋轉(single rotation)可以完成調整。第二種情況是發生在“內部”的情況(即左右和右左的情況),這種情況通過稍微複雜的雙旋轉(double rotation)來處理。

 

 

 動畫顯示了將幾個元素插入到 AVL 樹中。它包括左,右,左右和右左旋轉。

單旋轉

上圖顯示單旋轉如何調整情形1。左邊是旋轉前,右邊是旋轉後。節點k2不滿足AVL平衡性質,因為他的左子樹比右子樹深2層,RF(k2) = -2 。該圖描述的只是情形1的一種可能的情況,在插入之前k2滿足AVL性質,但是在插入之後這種性質被破壞了。子樹X已經長出一層,這使得他比子樹Z深出2層。下面分析Y可能所處的層數:

  1. Y不可能與新X在同一水平上,因為這樣k2在插入之前已經失去平衡了。
  2. Y也不可能與Z在同一層,因為那樣k1就會是在通向根的路徑上破壞AVL平衡條件的第一個節點。

為了使樹恢復平衡,我們需要把X上移一層,並把Z下移一層。此時二叉樹已經不符合AVL樹的要求,我們需要重新安排節點形成一棵等價的樹,如圖右邊的樹所示。

 

 如圖,把k2左二子k1提升為新的根,這樣左子樹高度會減去1。二叉樹的屬性告訴我們k2>k1,所以在新樹中k2應該是k1的右兒子,子樹Y包含了大於k1小於k2的節點,把他放到k2的左兒子的位置上就可以滿足二叉查詢樹的屬性。通過這樣的調整,新樹稱為了一棵等效的新的AVL樹。因為X向上移動了一層,Y保持在原來的高度上,Z下移了一層。k2和k1不僅滿足AVL樹的要求,而且他們的子樹恰好處在同一高度上。不僅如此,整棵樹的新高度恰恰與插入前原樹的高度相同,而插入操作卻使得子樹X升高了。因此,通向根節點的路徑的高度不需要進一步的修正,因而也不需要進一步的旋轉。

情形4代表的是對稱的情形。情形1的調整是從左往右旋轉,稱為右旋,情形4需要反過來,稱為左旋。

雙旋轉

上面描述的單旋轉對於情形2和情形3無效,需要使用雙旋轉來解決。

 

 上圖表示了對於情形2進行單旋轉的情形。單旋轉只會讓子樹Y保持高度不變,不會減少Y的高度。圖中Y已經有一個資料插入其中,可以保證子樹Y非空,因此可以假設子樹Y有一個根和兩棵子樹,如下圖所示。

 

 可以把整棵樹看成是由三個節點連線的4棵子樹。如圖所示,恰好樹B或者樹C中有一棵比D深兩層(除非他們都是空的),但是我們不能肯定是哪一棵。事實上這並不要緊。圖裡B和C都畫得比D低了2層。為了重新平衡,我們看到不能再讓k3做根了,而上面已經說明了在k1和k3之間的旋轉解決不了問題,唯一的選擇是把k2作為新的根。這迫使k1成為k2的左兒子,k3成為k2的右兒子,從而決定4棵樹的位置,如上圖3所示,最後得到的樹滿足AVL的性質,與單旋轉類似,我們也把樹的高度恢復到插入以前的水平,這就保證了所有重新平衡和高度更新是完善的。情形3也可以通過雙旋轉進行處理,方向跟情形2相反。

刪除元素

AVL 樹和二叉查詢樹的刪除操作基本相同,只是在二叉查詢樹的刪除邏輯後後需要重新檢查平衡性並修正。刪除操作與插入操作後的平衡修正區別在於,插入操作後只需要對路徑上最深的一個非平衡節點進行修正,而刪除操作需要修正路徑上所有非平衡節點。

對於刪除操作造成的非平衡狀態的修正,可以這樣理解:對左或者右子樹的刪除操作相當於對右或者左子樹的插入操作,然後再對應上插入的四種情況選擇相應的旋轉就好了。

 

引用:

 https://zhuanlan.zhihu.com/p/56066942

《資料結構與演算法分析——C++語言描述(第四版)》

https://en.wikipedia.org/wiki/AVL_tree

相關文章