滿足下面幾個條件的二叉搜尋樹,稱為紅黑樹:
1. 任何乙個節點都被著色――紅色或是黑色。
2. 根節點是黑色的。
3. 所有的nil 節點都看成黑色(nil 節點是就是乙個假想的或是無實在意義的節點,所有應該指向null 的 指標,都看成指向了nil 節點。包括葉節點的子節點指標或是根節點的父指標)。
4. 如果乙個節點是紅色的,那麼它的子節點一定是黑色的。
5. 對於任何乙個節點而言,從該節點到它的子孫節點中的nil 節 點路徑中,所包含的黑節點個數相同。
黑高度的定義:
從任何乙個節點,向下到底部的路徑中,包含的黑節點的個數,稱為這個節點的黑高度。從紅黑樹的第5條性質可以看出,黑高度是唯一的、確定 的。
只要同時滿足紅黑樹的這些條件,就一定會有「紅黑樹可以保證任何兩條從根部到樹葉的路徑節點個數相差不超過2倍」這個平衡的性質。
我們可以假設,如果存在兩條路徑l 和s ,l 比s 長兩倍以上(s 路徑上有n 個節點,l 上有大於2n 個節點)。可知,s 的黑高度最大只可能是n ,那麼根據第5條性質,l 的黑高度也大也只可能是n ,也就是,l 路徑上一定有超過n 個紅色節點(因為節點不是黑的就必 定是紅的)。所以,肯定會有兩個以上的紅色節點是相鄰的。這就與第4個條件矛盾了。所以,紅黑樹可以保證任何兩條從根部到樹葉的路徑節點個數相差不超過2 倍。
紅黑樹的高度h<2lg(n+1) (這個在《演算法導論》13.1 節中有證明)。也就是說,紅黑樹的操作的時間複雜度是o(lgn) 的。
二叉搜尋樹的操作時間複雜度,取決於樹高h ,因此,我們當然就希望h 盡量地小以提高操作的效率。 從直觀上就可以發現,二叉搜尋樹各子樹的規模越平均,它的高度就會越小。所以在應用中,一般都會將二叉搜尋樹實現成一種平衡樹,以保證最差時間複雜度為o(lgn) 。紅黑樹就是其中的一種,應用得很廣泛(sgi stl 的 set 和 map 就是基於紅黑樹來實現, linux 核心中也用到這個資料結構
) 紅黑樹是二叉搜尋樹的一種。它與普通二叉搜尋樹不同的是,紅 黑樹的每個節點都附加了另乙個屬性――顏色,可以是紅色,也可以是黑色。通過對於每條路徑上節點顏色的規則進行限定,紅黑樹可以保證任何兩條從根部到樹葉 的路徑節點個數相差不超過2倍。所以,紅黑樹是一種近似平衡的二叉搜尋樹。
紅黑樹 的查詢、最大值、最小值、前趨、後繼等操作,與普通的二叉搜尋樹沒有什麼區別。插入和刪除操作需要重新實現。僅僅用普通的二叉搜尋樹的插入和刪除動作,可 能會破壞紅黑樹本身的一些性質,因此,需要進行額外的處理。這些額外的處理主要是改變樹節點的顏色,或是改變樹的結構。
樹的旋轉
改變樹的結構,主要是用旋轉。旋轉有兩種,左旋和右旋,下面 是這兩種旋轉的:
這種旋轉的操作,是在二叉搜尋樹的區域性對樹的結構進行調整的 一種方式,經過旋轉之後,二叉搜尋樹的性質是不會發生改變的。如下圖:
向紅黑樹插入節點,先將需要插入的節點著成紅色,用普通二叉 搜尋樹的方法將這個節點插入到樹中,然後再想辦法把被破壞的紅黑性質恢復。(將新插入的節點顏色著成紅色,呆以儘量減少對紅黑性質的破壞,恢復起來也容 易。因為插入紅色節點的話,那麼被破壞的部分會在區域性,考慮的問題也就比較少,恢復過程也容易形成遞迴,詳細見下文說明)
我們考慮下,如果乙個紅色節點(下文稱用 z
指向它)被插入到樹中,那麼有哪些紅黑性質可能被破壞呢?只有第 2
條(根節點是黑色的)以及第 4
條(紅色節點的子節點一定是黑色節點),其它都不會被破壞。
如果插入的節點的父節點是黑色的,那麼不需要做任何調整,這 紅黑樹是正常的。如果父節點是紅色,或沒有父節點(也就是插在了樹根的位置),那麼就要進行調整以保證紅黑樹是正確的。
首先對第 4
條進行分析,我們知道,插入乙個新的節點,這個節點肯定會被放到樹的底部成為乙個葉節點(參考普通二叉樹的插入過程),那麼這個紅色節點就沒有 可能和自己的子節點同色(因為葉節點的子節點是 nil
節點,都是黑色的),如果第 4
條被破壞的話,肯定是 z
指向的節點的父節點是紅色的。因此,為了使分析和解決更加容易和清晰,我們在對樹進行調整以恢復紅黑 特性時,始終使得 z
總是指向相鄰紅色的節點中的子節點(指標 z
可能會向上公升到樹的中部或根部)。基於這個做法,我們可以知道,如果是第 2
條被破壞了的話,也就是 z
指向根節點了,那麼第 4
條肯定就符合了(因為 z
的父節點是 nil
,黑色的),因此對第 2
條性質的恢復變得很簡單,只需要被根從紅色變為黑色即可。這時,所有性質都會被滿足了(因為是根節 點,所以根節點被著為黑色時,所有路徑都黑高會統一加 1
,也就是第 5
條不會被破壞,操作之後的紅黑樹仍然是正確的)。好了,現在只留下第 4
條性質的恢復的問題了。
如果第 4
條被破壞了,也就是說, z
的父節點是紅色的,那麼,說明, z
一定有祖父節點,而且是黑色的(否則插入前原樹就有問題,又 或是調整時的方法不正確)。因此可以把問題放到以 z
的祖父節點為根節點的子樹內進行解決,這樣可以把調整的範圍最 小化,而且這也是有可能的:只要不改變這子樹的黑高度,那麼就不會對樹的其它部分產生影響。我們要做的就是在這個子樹範圍內把紅黑性質調整回來。再看子樹 的根是否與其父節點同為紅色,是的話,就再次用前面所說的去解決它,一直向上遞迴到紅黑性質被恢復為止。
紅黑樹的節點插入
對第 4
條性質的恢復,根據 z
的父節點是 z
的祖節點的左子節點還是右子節點,分為兩組對稱的情況,每組有 3
種情況。下面我們只對其中一組進行說明,以 z
的父節點是 z
祖節點的左子節點為例:
第一種: z
的「叔父」節點是紅色。
如上圖所示,在這種情況下,將父、叔節點都著為黑色,再將子樹根節點著為紅色,那麼子樹的黑高度沒有發生改變,而且 紅黑性質得得到了調整。此時,再將 z
指向子樹的根節點,向上遞迴恢復紅黑特性。
第二種: z
的「叔父」節點是黑色的, z
的父節點的左子節點。
如上圖,將 z
的父節點與祖節點進行一次右旋,並把父節點著黑色,原來的祖節 點著紅色。這些子樹的紅黑特性得到了恢復,而且子樹的黑高度沒有變化。另外,由於子樹根節點已經是黑色了(這個節點不會出現父子同為紅色的問題了),所以 不必再向上遞迴了,此時整個樹的紅黑特性都已經是正確的了。
第三種: z
的「叔父」節點是黑色的, z
的父節點的右子節點。
如上圖,將 z
本身與其父節點進行一次左旋,讓 z
指向原來的父節點,就可以調整為情況二,而情況二已經得到解決。
這樣,紅黑樹的節點插入問題就得到了解決。
以上引自:
注 摘圖中第三種情況圖可能不對,右邊到了左邊
另附維基:
/** from clr */
private void fixafterinsertion(entryx) else
setcolor(parentof(x), black); // 因此這裡是將x父節點設黑
setcolor(parentof(parentof(x)), red); // 子樹根節點設紅
rotateright(parentof(parentof(x))); // 按子樹根節點右旋
}} else else
setcolor(parentof(x), black);
setcolor(parentof(parentof(x)), red);
rotateleft(parentof(parentof(x))); // 這裡的左旋是基於根節點的左大旋轉,與維基中的case5一致,不過只是鏡面對稱而已}}
}root.color = black;
}
以上程式理解的話真的很經典,關鍵是兩個小else,還有對基於節點的左旋和右旋也要理 紅黑樹(插入)
紅黑樹的插入操作相對刪除操作比較簡單。紅黑樹要滿足 任一節點至null的任何路徑,所含黑節點數必須相同。所以,為了滿足此性質,插入節點應該為紅色。如果插入節點的父親為黑色,則不需要進行調整,若為紅色,有三種情況需要討論。1.父節點為紅色,叔節點為紅色 對於這種情況,同時改變父 叔節點顏色為黑色,並將...
紅黑樹插入
一 什麼是紅黑樹 紅黑樹 red black tree 是一種自平衡二叉查詢樹,是在電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。紅黑樹是一棵二叉搜尋樹,它在每個結點上增加了乙個儲存位來表示結點的顏色,可以是red或black。一棵紅黑樹是滿足下面紅黑性質的二叉搜尋樹 1 每乙個結點或是紅...
紅黑樹 插入
一 滿足下面幾個條件的二叉搜尋樹,稱為紅黑樹 1.任何乙個節點都被著色 紅色或是黑色。2.根節點是黑色的。3.所有的nil節點都看成黑色 nil節點是就是乙個假想的或是無實在意義的節點,所有應該指向null的指標,都看成指向了nil節點。包括葉節點的子節點指標或是根節點的父指標 4.如果乙個節點是紅...