一、紅黑樹的定義和性質
紅黑樹是一種含有紅黑結點並能自平衡的二叉查找樹。它並且滿足下面定義:
定義1. 每個節點要麼是黑色,要麼是紅色。
定義2. 根節點是黑色。
定義3. 每個葉子節點(Nil)是黑色。
定義4. 每個紅色結點的兩個子結點一定都是黑色。
定義5. 任意一結點到每個葉子結點的路徑都包含數量相同的黑結點。
注意:爲了統一名稱,便於理解。本文所稱結點均爲非空,即不是Nil,空節點我們統稱爲葉子結點,也就是Nil。另外結點同節點,雖然兩者不一樣,但是本文暫不做區分。
根據紅黑樹以上五條定義,我們可以推導出以下幾點性質和可能情景:
性質1. 父子節點不可能同時是紅節點,即紅節點不連續。否則違反定義4
性質2. 如果某節點的一個子節點是黑色,那麼該節點必然存在另一個子節點。否則違反定義5
情景1. 一個結點的兩個子節點出現一個是紅色,一個是黑色的情況是可能出現的
情景2. 父子節點同時是黑色的情況是可能出現的
規律1. 新插入的節點初始化爲紅色,能最小化變色操作
下面我們來看一下一個典型的紅黑樹結構,如下圖:
圖1:一個簡單的紅黑樹
上圖就是一顆簡單的紅黑樹。其中 Nil 爲葉子結點,並且它是黑色的。(值得提醒注意的是,在 Java 中,葉子結點是爲 null 的結點。)
紅黑樹並不是一個完美平衡二叉查找樹,從圖 1 可以看到,根結點 P 的左子樹顯然比右子樹高。
但左子樹和右子樹的黑結點的層數是相等的,也即任意一個結點到到每個葉子結點的路徑都包含數量相同的黑結點(性質 5)。
前面講到紅黑樹能自平衡,它靠的是什麼?有如下三種操作:
左旋:以某個結點(例如p)作爲支點(旋轉結點),其右子結點變爲旋轉結點的父結點,右子結點的左子結點變爲旋轉結點的右子結點,左子結點保持不變。如圖 2。
右旋:以某個結點(例如p)作爲支點(旋轉結點),其左子結點變爲旋轉結點的父結點,左子結點的右子結點變爲旋轉結點的左子結點,右子結點保持不變。如圖 3。
變色:結點的顏色由紅變黑或由黑變紅。
圖2:左旋
圖3:右旋
我們先忽略顏色,可以看到旋轉操作不會影響旋轉結點的父結點,父結點以上的結構還是保持不變的。
左旋隻影響旋轉結點和其右子樹的結構,把右子樹的結點往左子樹挪了;右旋隻影響旋轉結點和其左子樹的結構,把左子樹的結點往右子樹挪了。
但要保持紅黑樹的性質,結點不能亂挪,還得靠變色了。怎麼變?那就要紅黑樹通過旋轉和變色達到自平衡。
紅黑樹的查找
因爲紅黑樹是二叉查找樹,查找一個節點是典型的二分查找,log(n)的複雜度,這裏就不再說明了
紅黑樹的插入
假設插入位置已經通過二分查找找到,把插入結點放到葉子結點就可以啦,但插入結點應該是什麼顏色呢?
答案是紅色。理由很簡單,紅色在父結點(如果存在)爲黑色結點時,紅黑樹的黑色平衡沒被破壞,不需要做自平衡操作。
但如果插入結點是黑色,那麼插入位置所在的子樹黑色結點總是多 1,必須做自平衡。
所有插入情景如圖 4 所示:
圖4:紅黑樹平衡插入場景
嗯,插入情景很多呢,8 種插入情景!但情景 1、2 和 3 的處理很簡單,而情景 4.2 和情景 4.3 只是方向反轉而已。
懂得了一種情景就能推出另外一種情景,所以總體來看,並不複雜,後續我們將一個一個情景來看,把它徹底搞懂。
另外,根據二叉樹的性質,除了情景 2,所有插入操作都是在葉子結點進行的。這點應該不難理解,因爲查找插入位置時,我們就是在找子結點爲空的父結點的。
在開始每個情景的講解前,我們還是先來約定下,如圖 5 所示:
圖5:插入操作結點的叫法約定
圖 5 的字母並不代表結點 Key 的大小。I 表示插入結點,P 表示插入結點的父結點,S 表示插入結點的叔叔結點,PP 表示插入結點的祖父結點。
好了,下面讓我們一個一個來分析每個插入的情景以及處理。
情景 1:紅黑樹爲空樹
最簡單的一種情景,直接把插入結點作爲根結點就行,但注意,根據紅黑樹性質 2:根節點是黑色。還需要把插入結點設爲黑色。
處理:把插入結點作爲根結點,並把結點設置爲黑色。
情景 2:插入結點的 Key 已存在
插入結點的 Key 已存在,既然紅黑樹總保持平衡,在插入前紅黑樹已經是平衡的,那麼把插入結點設置爲將要替代結點的顏色,再把結點的值更新就完成插入。
處理:把 I 設爲當前結點的顏色,更新當前結點的值爲插入結點的值。
情景 3:插入結點的父結點爲黑結點
由於插入的結點是紅色的,當插入結點是黑色時,並不會影響紅黑樹的平衡,直接插入即可,無需做自平衡。
處理:直接插入。
情景 4:插入結點的父結點爲紅結點
再次回想下紅黑樹的定義 2:根結點是黑色。如果插入的父結點爲紅結點,那麼該父結點不可能爲根結點,所以插入結點總是存在祖父結點。這點很重要,因爲後續的旋轉操作肯定需要祖父結點的參與。
情景 4 又分爲很多子情景,下面將進入重點部分,各位看官請留神了。
情景 4.1:叔叔結點存在並且爲紅結點。
從紅黑樹定義4 可以看出,祖父結點肯定爲黑結點,因爲不可以同時存在兩個相連的紅結點。
那麼此時該插入子樹的紅黑層數的情況是:黑紅紅。顯然最簡單的處理方式是把其改爲:紅黑紅。如圖 6 和圖 7所示。
處理:將 P 和 S 設置爲黑色,將 PP 設置爲紅色,把 PP 設置爲當前插入結點。
圖6:插入情景 4.1_1
圖7:插入情景 4.1_2
圖6和圖7只是兩種例子,實際有四種,就不一一列舉了,效果一樣。
可以看到,我們把 PP 結點設爲紅色了,如果 PP 的父結點是黑色,那麼無需再做任何處理。
但如果 PP 的父結點是紅色,根據定義 4,此時紅黑樹已不平衡了,所以還需要把 PP 當作新的插入結點,繼續做插入操作自平衡處理,直到平衡爲止。
試想下 PP 剛好爲根結點時,那麼根據定義 2,我們必須把 PP 重新設爲黑色,那麼樹的紅黑結構變爲:黑黑紅。
換句話說,從根結點到葉子結點的路徑中,黑色結點增加了。這也是唯一一種會增加紅黑樹黑色結點層數的插入情景。
我們還可以總結出另外一個經驗:紅黑樹的生長是自底向上的。這點不同於普通的二叉查找樹,普通的二叉查找樹的生長是自頂向下的。
情景 4.2:叔叔結點不存在或爲黑結點,並且插入結點的父親結點是祖父結點的左子結點。
單純從插入前來看,也即不算情景 4.1 自底向上處理時的情況,也就是初次插入節點,不是向上回溯插入情況,叔叔結點非紅即爲葉子結點(Nil)。
因爲如果叔叔結點爲黑結點,而父結點爲紅結點,那麼叔叔結點所在的子樹的黑色結點就比父結點所在子樹的多了,這不滿足紅黑樹的定義 5。後續情景同樣如此,不再多做說明了。
前文說了,需要旋轉操作時,肯定一邊子樹的結點多了或少了,需要租或借給另一邊。插入顯然是多的情況,那麼把多的結點租給另一邊子樹就可以了。
情景 4.2.1:插入結點是其父結點的左子結點。
處理:將 P 設爲黑色,將 PP 設爲紅色,對 PP 進行右旋。
圖8:插入情景 4.2.1
現在我們來分析一下這個右旋,由圖 8 可得,右旋前PP節點是黑色,旋轉後,P節點也是黑色,旋轉過程使PP的父節點變成了P的父節點,而旋轉前後該節點的顏色都是黑色,也就是說旋轉不會影響旋轉前PP父節點及以上的樹結構,再來看右旋會使P的右子樹變成旋轉後PP的左子樹(對右旋不清楚的同學請仔細看右旋對結構的影響),可以看到旋轉前P爲紅色,旋轉後PP也爲紅色,也就是說,旋轉對旋轉前P的子樹也沒有影響。綜上可得,該情形一次性4.2.1的操作處理即可達到自平衡。
情景 4.2.2:插入結點是其父結點的右子結點。
這種情景顯然可以轉換爲情景 4.2.1,如圖 9 所示。
處理:對 P 進行左旋,把 P 設置爲插入結點,得到情景 4.2.1,進行情景 4.2.1 的處理。
圖9:插入情景 4.2.2
情景 4.3:叔叔結點不存在或爲黑結點,並且插入結點的父親結點是祖父結點的右子結點。
該情景對應情景 4.2,只是方向反轉,不做過多說明了,直接看圖。
情景 4.3.1:插入結點是其父結點的右子結點。
處理:將 P 設爲黑色,將 PP 設爲紅色,對 PP 進行左旋。
圖10:插入情景 4.3.1
情景 4.3.2:插入結點是其父結點的右子結點。
處理:對 P 進行右旋,把 P 設置爲插入結點,得到情景 4.3.1,進行情景 4.3.1 的處理。
圖11:插入情景 4.3.2
好了,講完插入的所有情景了。可能有同學會想:上面的情景舉例的都是第一次插入而不包含自底向上處理的情況,那麼上面所說的情景都適合自底向上的情況嗎?答案是肯定的。
理由很簡單,只要每棵子樹都能自平衡,那麼整棵樹最終總是平衡的。
另外,當叔叔節點爲Nil時很好理解,當叔叔節點是黑節點時你們能理解嗎?其實這種情況發生在自底向上的插入處理中,而且Nil也是黑色,在旋轉過程中也是滿足紅黑自平衡特性的,同學們不妨仔細琢磨一下,記得一定要畫圖。