1. 程式人生 > >真正理解紅黑樹,真正的(Linux核心裡大量用到的資料結構,且常被二貨問到)

真正理解紅黑樹,真正的(Linux核心裡大量用到的資料結構,且常被二貨問到)

作為一種資料結構,紅黑樹可謂不算樸素,因為各種宣傳讓它過於神祕,網上搜羅了一大堆的關於紅黑樹的文章,不外乎千篇一律,介紹概念,分析效能,貼上程式碼,然後給上罪惡的一句話,它最壞情況怎麼怎麼地...
      
       我們想,一棵二叉樹怎麼就是最壞情況,那就是它退化為一個連結串列,這樣查詢就成了遍歷。問題是,平衡二叉樹怎麼會退回連結串列!它是怎麼保持平衡的?能不能簡單地闡述?當然可以!一般的講述紅黑樹的資料都是直接給出黑節點相同,紅節點不連續等來作為一個足夠硬但是又不是太硬的約束來保證樹的平衡,但事實上,它還有更加簡單的理解方式。

1.查詢-在高度不在寬度

對於查詢而言,如果一棵二叉樹的高度是N,那麼最多可以在N步內完成查詢,這個不用解釋,解釋這個有點喧賓奪主了。這就是說,樹的高度要儘可能矮。考慮到查詢的平均情況,葉子節點到根節點的距離不能差別太大。

2.二叉樹的不平衡根源

一棵樹在查詢看來變得不平衡是因為子樹的高度相差很大。
       二叉樹為什麼會這麼容易變得不平衡,很簡單,因為它只有二叉,左右均有50%的概率,那麼插入N個節點全部都是左節點或者右節點的概率就是50%的N次方,如果是8叉樹,那麼這個概率就是12.5%的N次方,哪個概率大,自己算。

3.多叉樹-寬度換高度

在第1節以及第2節,我們已經知道,樹的寬度越大,高度越小,這樣查詢起來越快,Cisco路由器裡不是有256叉乃至1024叉樹嗎?但是這樣真的很好嗎?對於稀疏節點,這樣會嚴重消耗記憶體。
       如果我們考慮CPU的MMU系統,就會知道,二級頁表和三級頁表的區別就在於對付稀疏地址空間的效果不同。

4.權衡-2,3樹

我們發現,道生一,一生二,二叉樹是一個完美的開始,但是我們發現它特別容易傾斜,傾斜的時候別觸控。我們也不能一下子就上256叉樹,即使那樣在海量節點情況下也抗不住,因此這種盲目寬度換高度的方案沒有可擴充套件性。我們需要找出一種動態的機制,讓一棵樹動態調整保持平衡。
       為了更加容易找出這個機制,讓它更加容易現形,暫時不斷增加樹的寬度,如果增加到3叉樹還找不到方案,就增加到4叉樹...我們說的N叉樹並不是說一個節點一定有N個子節點,而是說它最多有N個子節點。
       迄今為止,以前都是我自己形而上的觀點,幾年前我的想法就到此為止,原因在於那段時間特別鬱悶,就想找出些技術上的形而上思想,可是突然自己變好了,就沒有繼續下去。幸運的是,我現在發現確實有這麼一個方案,而紅黑樹就是從3叉樹回退過去的。
       讓我高興的是,我的思路並沒有跑偏。

5.2-3樹的平衡變換

如果是二叉樹,那麼你插入一個節點,你只有最多1次機會保持子樹的高度不變,如果是一個三叉樹,那麼就有2次機會。現在開始,我們為二叉樹添了一叉,變成了三叉樹。

       二叉樹的時候,一個節點有兩個分支,三叉樹的時候,有三個分支。一個點可以將區間分為兩個部分割槽域,要想將一個區間分為三個部分割槽域,就需要兩個點,因此三叉的情形下,節點儲存的是兩個點而不是一個,如下圖所示:



現在考慮插入一個新節點,這個2-3樹怎麼保持平衡。非常簡單,我們知道,插入的位置一定是葉子,假設當前的樹是平衡的,現在分兩種情況:

1).插入的新葉子節點的父節點是一個二叉節點

這種情況最簡單,二叉節點變三叉節點即可,如下圖所示:



2).插入的新葉子節點的父節點是一個三叉節點

這種情況比較複雜。樹總是要長高的,保持平衡的方式就是同時長高,而這是不可能的,插入一個節點只能讓該節點所在的子樹長高。然而,如果能將這個資訊上升到根部,在根部長高,就實現了“同時長高”!
       還是循著上面的那個思路,我們繼續增加樹叉的數量,我們把它增加到4!新節點的插入如下圖所示:


很遺憾,沒有完成任務,但是最終我們提出了兩個問題,只要解決了這兩個問題,所有問題就解決了。

       解決這兩個問題,無疑都要牽扯到節點P的父節點以及再往上的節點,有兩種可能:
可能性1:P的父節點PP是一個二叉節點
這個太爽,我們直接把P以及它的子樹全部提到PP節點即可,類似B插入的情景,如下圖所示:


問題2解決。

可能性2:P的父節點PP是一個三叉節點
這就有點不好辦了,不過有最後一擊!不管怎樣先把P節點以及其子節點全部上提到PP,保持最底部的平衡性,這樣就可以遞迴解決了,此時我們又一次遇到了往一個三叉節點裡面插入子節點的問題了,為了不增加樹高,唯一的方式就是膨脹成一個四叉節點-寬度換高度。如下圖所示:


最後,我們發現,在遞迴的過程中,要麼碰到了P..P是個二叉節點,此時按照問題2的解決方式將當前節點的值直接提到P...P中,其子樹降低一個高度,抵消增加的高度,平衡保持,遞迴結束,要麼遞迴到了根節點,此時只需要一個分裂操作即可完美結束!



6.演進到紅黑樹

很顯然,通過上面的描述,我們似乎找到了一個使樹保持平衡的方案,而且是相當完美的平衡!核心就是寬度和高度之間的博弈。我們總是可以用一個寬度抵消一層高度,整個過程就是一次或者多次的一加一減,最終的結果還是0!
       然而,這也不再是二叉樹了,有的節點變成了三叉,並且儲存了兩個值,該兩個值將區間分割成了三部分,是為三叉!因此在使用上就不如二叉樹方便,比較操作複雜化了。事實上,將三叉節點處理成二叉節點,這棵樹就成了紅黑樹!怎麼處理呢?很簡單!如下圖所示:


看到了吧,紅色節點就是從2-3樹中分出來的,為了維持一棵二叉樹而不是2-3樹,必須將三叉節點變成二叉節點,這是一個寬度換高度得回退,即高度換寬度,當然代價就是不再完美平衡。

       按照以上的這個變換,你自己試試看,可以變出兩個連續的紅節點嗎?NO!還在糾結紅黑樹的性質概念嗎?看了它的演進,你會發現,很多紅黑樹的複雜概念和讓人沒有頭緒的效能都是自然而然的。下面我們來看一下它的最壞情況是什麼。
       還是以2-3樹分析,如果在一棵2-3樹中,最左邊路徑上的節點全部是三叉節點,而最右邊路徑上的節點都是二叉節點,那麼把它變換成二叉紅黑樹之後,就會發現最左邊的路徑上是紅黑間隔的節點,而最右邊的路徑上全部是黑節點,它們的高度差接近2倍。出現這樣的情況是令人悲哀的,但是也是極低概率的。
       紅黑樹的所有包括旋轉等操作,都可以對映到2-3樹中,而我們對2-3樹以及高度和寬度之間的博弈已經足夠理解了。請再次去理解紅黑樹吧,再看看它的性質和概念,together with左旋和右旋,是不是有一種新的體會呢?