AVL樹的基本概念
AVL樹是一種高度平衡的(height balanced)二叉搜索樹:對每一個結點x,x的左子樹與右子樹的高度差(平衡因子)至多為1。
有人也許要問:為什么要有AVL樹呢?它有什么作用呢?
我們先來看看二叉搜索樹吧(因為AVL樹本質上是一棵二叉搜索樹),假設有這么一種極端的情況:二叉搜索樹結點的插入順序為1,2,3,4,5,也就是:
顯而易見,這棵二叉搜索樹已經其退化成一個鏈表了,也就是說,它在查找上的優勢已經全無了—— 在這種情況下,查找一個結點的時間復雜度是O(n)!
如果這棵二叉搜索樹是AVL樹,在插入順序仍為1,2,3,4,5的情況下,樹的形狀如下圖:
可以看出,AVL樹基本操作的最壞時間復雜度要比普通的二叉搜索樹低—— 除去可能的插入操作外(我們將假設懶惰刪除),它是O(logn)。
而插入操作隱含着困難的原因在於,插入一個節點可能破壞AVL樹的性質(例如,將6插入到上圖的AVL樹中會破壞根節點2的平衡條件),如果發生這種情況,就要在插入操作結束之前恢復平衡的性質。事實上,這總可以通過對樹進行簡單的修正來做到,我們稱其為旋轉。
AVL樹的旋轉
在AVL樹中,假設有一個結點的平衡因子為2(最大就是2,因為結點是一個一個地插入到樹中的,一旦出現不平衡的狀態就會立即進行調整),我們把這個必須重新平衡的結點叫做被破壞點α。這種不平衡只可能是下面四種情況造成的:
1. 對α的左兒子的左子樹進行了一次插入,即LL情況。
2. 對α的左兒子的右子樹進行了一次插入,即LR情況。
3. 對α的右兒子的左子樹進行了一次插入,即RL情況。
4. 對α的右兒子的右子樹進行了一次插入,即RR情況。
情形1和4是關於結點α的鏡像對稱,2和3也是關於結點α的鏡像對稱。因此,理論上只有兩種情況:第一種情況是插入發生在“外邊”的情況(即LL情況或RR情況),第二種情況是插入發生在“內部”的情況(即LR情況或RL情況)。
在AVL樹中插入結點后,用於保持樹的平衡的旋轉操作步驟如下:
步驟一:沿着插入點到根結點的路徑檢查結點的平衡因子,找到途中第一個不滿足AVL樹性質的結點,這個結點就是被破壞點α。
步驟二:從被破壞點α開始沿着該路徑向下再標記連續的兩個結點β、γ,這三個點就是旋轉過程將要涉及的三個點(這些點中不一定包括插入點,旋轉會使β或γ成為新的根,另外兩個點作為根的左右兒子,其他結點根據AVL樹的性質放置即可)。
步驟三:判斷插入點與被破壞點α之間的關系屬於上述四種情況中的哪一種:如果是插入發生在“外邊”的情況(即LL的情況或RR的情況),只需要以β為新的根結點頂替被破壞點α的位置進行進行一次單旋轉即可完成調整;如果是插入發生在“內部”的情形(即LR的情況或RL的情況),只需要以γ為新的根結點頂替被破壞點α的位置進行稍微復雜的雙旋轉即可完成調整。
(1) LL基本情況
(2) RR基本情況
(3) LR基本情況
(4) RL基本情況
實例分析
下面給出了一個向AVL樹中插入關鍵字的實例,在已給AVL樹的基礎上插入9(圖中虛線表示),沿着插入點9到根節點的路徑發現第一個高度不平衡的結點6,即被破壞點;從被破壞點6開始沿着該路徑向下標記6,10,7為α,β,γ;插入點9位於被破壞點6的右兒子10的左子樹上,所以屬於RL狀況;以γ結點7為新的根節點頂替被破壞點6的位置,α結點6和β結點10分別為γ結點7的左右兒子,其他結點根據AVL樹的性質放置即可得到右側的AVL樹。
在上面AVL樹的基礎上繼續插入8(圖中虛線表示),沿着插入點8到根節點的路徑發現第一個高度不平衡的結點為根節點4,即被破壞點;從被破壞點4開始沿着該路徑向下標記4,7,10為α,β,γ;插入點8位於被破壞點4的右兒子7的右子樹上,所以屬於RR狀況;以β結點7為新的根節點頂替被破壞點4的位置,α結點4和γ結點10分別為β結點7的左右兒子,其他結點根據AVL樹的性質放置即可得到右側的AVL樹。
AVL樹是最早的平衡二叉樹之一,應用相對其他數據結構較少。Windows對進程地址空間的管理用到了AVL樹。
參考資料: 《算法導論第3版》—— 習題 13-3 AVL樹
《數據結構與算法分析—Java語言描述》—— 4.4 AVL樹
http://blog.chinaunix.net/uid-25324849-id-2182877.html