相對友好的 AVL Tree 教程

平衡的意義

以前學習了二叉搜索樹,知道這種結構基於折半的原理,在查找的時候效率很高,理想的狀況下時間複雜度爲 O(log n) ,那不理想的狀況又是怎樣的呢?舉個例子,根據二叉搜索樹的特性,插入 { 6,5,4,3,2,1 } 這組數據,最終生成的二叉樹以下:bash

要判斷這棵樹中是否存在 1 。 1 處在這棵樹的最底部,而且這個棵樹呈現出一邊倒的形狀,致使找 1 時遍歷了全部的節點,這種狀況下時間複雜度爲 O(n) 。可見一旦二叉搜索樹失去了平衡也就失去了效率,理想的二叉搜索樹,是樹的節點「均勻」分佈在根節點兩側,才能知足時間複雜度 O(log n) 。學習

平衡的定義

怎樣纔算「均勻」分佈呢?對於樹中的節點,不能只讓左或右孩子獨得恩寵,雨露均沾纔是王道。 Wikipedia 給出了定義:spa

二叉搜索樹中,對於任意節點,子樹與子樹高度差不超過 1 ,則認爲這棵樹是平衡的。3d

這個定義有個官方的名字 平衡因子(Balance factor),平衡因子只多是「1,0,-1」,注意是右子樹的高度 - 左子樹的高度。有了這個規定,失衡的現象就能有所緩解了。俗話說不患貧而患不均,雖然「1,-1」目前是可接受的,卻爲未來的失衡埋下伏筆。這種致使失衡的隱患 Wikipedia 給出了定義:code

平衡因子爲 1 則該節點右重(right-heavy),平衡因子爲 -1 則該節點左重(left-heavy)cdn

4 種失衡

上面說到可能致使失衡的隱患,分別是右重和左重。你可能在不少地方看到 LL(左左)、RR(右右)、LR(左右)、RL(右左),搞得跟祕籍鍵似的這 TM 到底指的是啥?其實就是下面的 4 種失衡狀況:blog

LL(左左):2 是 3 的 子樹,2 重;ip

RR(右右):2 是 1 的子樹,2 get

LR(左右):1 是 3 的子樹,1 it

RL(右左):3 是 1 的子樹,3

樹的旋轉

「症狀」有了,就須要對症下藥了。正常的思路是,哪邊高了就下降其高度,可是要注意二叉搜索樹中的節點是有順序的(左<中<右),如何下降高度也是有講究的。這裏就引入一個很重要的操做——旋轉旋轉能知足只改變樹的結構,又符合節點的排列順序。你可能在不少地方看到,爲了保證樹的平衡,會有左旋或右旋的操做,這裏的左旋、右旋具體指的又是啥? Wikipedia 上的介紹

當說到旋轉時,是指對某個節點旋轉(上圖對 Q 右旋,對 P 左旋),仔細觀察發現,右旋使得 Q 的左孩子 P 取代了本身原來的位置,左旋使得 P 的右孩子 Q 取代了本身原來的位置,記住這一點很重要哈。

上面動圖直觀的感覺就是右旋後右子樹高度升高,左子樹高度下降;左旋後左子樹升高,右子樹高度下降;除此以外,旋轉的過程當中也涉及到節點的交換

從上圖能夠看到,當簡單地說右旋,其實展開來講是指:

  • 根節點 5 右旋,首先將左子樹 3 的右孩子 4 做爲此時根節點 5 的左孩子;
  • 再將 5 這棵樹做爲新根節點 3 的右子樹;

左旋反之;由於這樣很囉嗦,平時不會這麼說,但這背後的原理得知道。此外旋轉後節點仍是符合大小排列順序,這正是咱們所但願的。

AVL 樹

說了半天,這 AVL 樹是個啥?這個有點「黃」的名字來源於它的發明者 G. M. Adelson-Velsky 和 Evgenii Landis,名字不重要,功能才重要,它能在失衡的狀況下經過旋轉從新實現平衡,所以它的時間複雜度爲 O(log n)。上面介紹了 4 種失衡的狀況,如今分別介紹 AVL 樹是如何作到從新平衡的:

LL(左左): 假設要在下面這棵樹中插入 3

9
     / \
    7   10
   / \
  6   8
複製代碼

首先要作的是先肯定各個節點的平衡因子:

9(-1)
         /       \
       7(0)      10(0)
      / \
  6(0)   8(0)
複製代碼

插入 3 後:

9(-1?)
          /      \
        7 (0?)   10(0)
       /   \
   6(-1)   8(0)
   /
 3(0)
複製代碼

注意這裏對可能影響到的路徑後面加了個 ?,是由於此時他們的平衡因子還不肯定,須要從新計算,因爲 7 的左子樹高度加 1 ,7 的平衡因子也變了:

9(-1?)
          /      \
        7(-1)    10(0)
       /   \
   6(-1)   8(0)
   /
 3(0)
複製代碼

最後,相應的 9 的平衡因子也變了:

9(-2)
          /      \
        7(-1)    10(0)
       /   \
   6(-1)   8(0)
   /
 3(0)
複製代碼

根據上面學的內容,這種左重的狀況右旋後能夠達到平衡,找到負載因子爲 -2 的節點(9)右旋,剩下的就是上面已經介紹過的,節點交換什麼的。以下:

RR(右右): 假設要在下面這棵樹種插入 12

8
     / \
    7   10
        / \
       9   11
複製代碼

先肯定各個節點的平衡因子:

8 (+1)
         /       \
       7(0)      10(0)
                  / \
               9(0)  11(0)
複製代碼

插入 12 後,直接跳到最後一步:

8(+2)
         /       \
       7(0)      10(+1)
                  / \
               9(0)  11(+1)
                       \
                       12(0)
複製代碼

這種右重的狀況左旋後能夠達到平衡,找到負載因子爲 +2 的節點(8)左旋:

LR(左右):假設要在下面這棵樹中插入 9

10
     / 
    7   
複製代碼

先肯定各個節點的平衡因子:

10(-1)
         /       
       7(0)    
複製代碼

插入 9 後,直接跳到最後一步:

10(-2)
         /      
       7(+1)    
        \         
        9(0)
複製代碼

按照以前的套路,這種左重的狀況須要右旋,找到負載因子爲 -2 的節點(10)右旋,結果咋樣呢?

7(+2)
           \     
           10(-1)    
           /    
         9(0)
複製代碼

發現套路很差使了,這裏就要用到兩次旋轉,第一次將旋轉將 LR(左右)變成熟悉的 LL(左左),第二次旋轉就能夠用以前的套路了

10                              10                                9
         /                               /                                 /  \ 
       7          (將 7 左旋) --->       9          (將 10 右旋) --->       7   10 
        \                              /
        9                             7
複製代碼

RL(右左):假設要在下面這棵樹中插入 9

8
        \  
         10  
複製代碼

先肯定各個節點的平衡因子:

8(+1)
        \  
         10(0)  
複製代碼

插入 9 後,直接跳到最後一步:

8(+2)
        \  
         10(-1)  
         /
        9(0)
複製代碼

一樣要用到兩次旋轉,第一次將旋轉將RL(右左)變成熟悉的 RR(右右),第二次旋轉就能夠用以前的套路了

8                                8                                9
        \                               \                              /  \
         10       (將 10 右旋) --->        9       (將 8 左旋) --->     8    10
         /                                 \ 
        9                                   10
複製代碼

Enjoy –☺

相關文章
相關標籤/搜索