數據結構の學習記錄(進階篇3):探密紅黑樹

一提到紅黑樹,你應該是這樣想的。。。

上一期我們詳細分析了AVL樹,相信你已經對二叉平衡樹有了非常棒的理解。這一期我們開始介紹紅黑樹,紅黑樹在網上有很多資源,但是講的不嚴謹,也不全面。筆者初學時也浪費了許多時間,因此我將非常細緻的講解,將自己踩過的坑曬出來,保證你能看懂。

紅黑樹和AVL樹的區別是:AVL樹是嚴格平衡的二叉樹,紅黑樹是弱平衡的二叉樹。和紅黑樹相比,AVL樹是嚴格的平衡二叉樹,平衡條件必須滿足(所有節點的左右子樹高度差不超過1)。通過對任何一條從根到葉子的路徑上各個節點着色的方式的限制,紅黑樹確保沒有一條路徑會比其它路徑長出兩倍,因此相同節點數的前提下,AVL樹的高度往往低於紅黑樹。AVL樹根據節點的平衡因子進行調整,而紅黑樹是根據顏色進行調整。大致地說,紅黑樹理解上較容易,而實現起來,需要注意的細節要更多(比如說考慮叔叔節點)。推薦大佬博客:比較兩者不同。

爲什麼要學二叉樹呢?(斜體表示你問了一個很棒的問題):

  • 紅黑樹的結點增刪改查效率非常優良,都爲log(n) , 應用方面:1. Linux內核進程調度由紅黑樹管理進程控制塊。 2. Epoll用紅黑樹管理事件塊。 3. nginx服務器用紅黑樹管理定時器。 4. C++ STL中的map和set的底層實現爲紅黑樹。 5. Java中的TreeMap和TreeSet由紅黑樹實現。 6. Java8開始,HashMap中,當一個桶的鏈表長度超過8,則會改用紅黑樹。

下面我們看一下紅黑樹的性質

1. 每個節點要麼是黑色,要麼是紅色 

2. 根節點一定爲黑色 

3. 每一個空節點(null / NIL)都是黑色(注意空節點不等於根節點)

4.一旦一個節點爲黑色,它的孩子一定爲黑色,也就是說,不可能有父子節點同時爲紅色。

5. 對每個節點而言,從每個黑色節點到葉子節點的節點數量相同。

因此我們需要對樹可視化函數稍作修改,對每個節點上色。對turtle,使用 t.fillcolor來設置填充顏色,t.begin_fill和t.end_fill來實現開始填充和結束填充,非常方便。

筆者得到的樹如上圖所示,是不是很美觀呢?但是光美觀是不夠的,我們還得實現背後的邏輯,而這也是最酷炫的部分。我保證,如果你能親手成就一個紅黑樹,能讓你“快樂”很長時間。

和AVL樹類似,紅黑樹只需要在節點類加入顏色屬性即可。爲了後期代碼方便,我們實現兩個函數,一個是獲取當前節點的叔叔節點get_uncle,另一個是獲取兄弟節點get_brother

    def get_uncle(self):
        if not self.parent or not self.parent.parent: return None
        if self.parent.parent.hasBothChildren():
            if self.parent.isLeftChild():
                return self.parent.parent.right_child
            else:
                return self.parent.parent.left_child

    def get_brother(self):
        if not self.parent : return None
        if self.parent.hasBothChildren():
            if self.isLeftChild():
                return self.parent.right_child
            else:
                return self.parent.left_child

  好了,我們依舊從插入和刪除來討論算法

1. Put方法實現

我們和AVL樹類比,其實就是將平衡因子改變的過程變爲更新顏色的過程而已,爲此我們需要實現一個函數renew_color。需要注意的是每當創建一個新的葉子節點時候,它都應該是紅色。理由很簡單,紅色在父結點(如果存在)爲黑色結點時,紅黑樹的黑色平衡沒被破壞,不需要做自平衡操作。但如果插入結點是黑色,那麼插入位置所在的子樹黑色結點總是多1,必須做自平衡。

旋轉過程在我上篇的AVL樹博客中已經介紹(需要的讀者自行食用),紅黑樹中的旋轉也完全相同。

我們來作一些簡單的設定。

在這幅圖中,當前節點爲B,那麼其兄弟節點爲E,其父節點爲D,它的叔節點爲A,祖父節點爲C。

我們由淺入深 循序漸進來化解這個問題:

(1)若樹爲空樹,那麼直接添加一個節點作爲根節點,且爲黑色(事實上,根節點一定爲黑色)。

(2)插入節點的key已經存在,那麼我們只需要將節點的val更新成新的val即可。

(3)插入節點的父節點爲黑節點,那麼插入新節點不會影響樹的平衡,直接插入即可。

(4)插入節點 的父節點爲紅色節點,插入節點也爲紅色,明顯不平衡。且父節點肯定不是根節點,我們需要詳細的討論這種情況:

1. 叔叔節點存在,並且叔叔節點爲紅色:

當前節點爲B,此時父節點的左子樹和右子樹肯定是不平衡的。給你10s請你想一想,如何滿足性質5?

一種直觀的想法是將父節點D\C全部變爲黑色,然後將當前節點B和其兄弟節點I

變爲紅色。

然後我們需要考慮當前節點的祖父節點的顏色F。如果它不是根節點,我們需要將其顏色變爲紅色,然後將祖父節點變爲當前節點繼續向上平衡。注意,只有所有子樹都平衡的情況下,整個樹纔是平衡的。簡言之,紅黑樹平衡是至下而上的,而AVL樹是自上而下的。

2. 插入節點的父節點是左子節點

這時候我們看當前節點是左子節點還是右子節點:

右子節點:我們對祖父節點A進行LR雙旋,祖父節點變爲紅色,父節點變爲黑色 。

左子節點:對祖父節點A進行R單旋,同時將父節點顏色設爲黑色,叔叔節點設爲紅色

3. 插入節點的父節點是右子節點

與左子節點情況剛好相反,故不再演示,但是調試時候注意考慮這種情況。

 

OK,上面便是插入操作,我們來測試一下,數據爲data = {12:'A',1:'B',9:'C',2:'D',3:'E',2.5:'F',11:'G',10:'H',2.4:'I',6:'J'}


 

2. Del方法實現 

現在我們考慮更復雜的刪除情況。你需要一杯咖啡,因爲接下來內容會比較多和乾燥。但是我保證你看完之後會很有啓發。

現在我們寫一個def __delitem__函數,這樣我們就能使用del 關鍵字刪除特定key了。我們先簡單回顧一下,

(1)如果待刪除的結點是葉子結點,則直接刪除即可。

(2)如果待刪除的結點只有一個孩子,則設定currNode.parent.left_child = currNode.left_child或者currNode.parent.right_child = currNode.right_child,替換節點爲其獨子。  

(3)如果待刪除的結點有兩個孩子,則可以找它的後繼,將值覆蓋過來,之後情況轉變成刪除前驅或者後繼結點,回到(1)和          (2)。

刪除的含義在於,刪的不是節點本身,而是它的“替身”。(你可以想象成JOJO裏的替身使者:->)

下面是 正片  即在刪除之前,我們必須更新顏色和進行旋轉,這些在函數update_del_color()中體現:

先考慮幾個邊值條件:

1. 樹爲空,直接返回None即可;

2. 利用get_node函數找到需要刪除的節點,如果沒找到,直接返回None。

3. 如果替換節點是紅色的,將其改爲黑色,因爲刪除紅色節點不會影響平衡

所謂的再平衡無異乎是找兄弟或者父母去“借”,是不是很形象呢{壞笑}:
我們可以將當前節點分爲左子節點和右子節點兩種情況,它們是完全對稱的,

  • 先考慮左子節點情況:

       假設實際被刪除節點爲C,

(a)父節點爲紅色,且兄弟節點沒有子節點

我們將父節點變爲黑色,同時將兄弟節點變爲紅色(兄弟節點一定沒有子節點)。下面這個圖可以直觀展現這點:

 

(b)父節點爲黑色,且兄弟節點沒有子節點

我們將兄弟節點顏色改爲紅色,之後再對父節點執行update_del_color進行遞歸。

 (c)兄弟節點左子節點存在,且爲紅色節點,右節點隨意

 那麼這個時候我們需要考慮父節點B的顏色,因爲兄弟左子節點D爲紅色,我們可以把它借出去,塗成父節點的顏色,再將父節點顏色設爲黑色。之後對父節點B進行RL雙旋,但是不要改變節點顏色,只有在插入時才改變節點顏色。

這裏白色表示顏色不確定。

(d)兄弟節點右子節點存在,且爲紅色節點,左子節點爲黑色

兄弟變爲父節點顏色,同時父節點變爲黑色,而且右侄子變爲黑色。再對父節點進行左旋。

 (e)兄弟節點有兩個兒子,且兄弟爲紅色節點

我們直接對父節點左旋,這就變成上面兄弟爲黑色情況,再調用update_del_color對父節點進行遞歸:

  •  當前節點爲右子節點,剛好相反,故不再贅述

 

下面我們將完整的過程展現出來,根據July的博客https://blog.csdn.net/v_july_v/article/details/6284050

依次刪除12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17。 大家可以作一個對比,如果你有任何問題,請立刻在下方留言!筆者不勝感激。

  • 完整的紅黑樹

 

希望本文對你有幫助,

源代碼在這裏!

碼字不易,希望大家多多轉發,多點贊!郵箱[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章