12.25 他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

原文地址:https://mp.weixin.qq.com/s/uTkORM6bQeUT2XDHHmOc4A

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

紅黑樹是一種很經典的數據結構,它可以在O(log n)時間內做查找,插入和刪除。所以倍受關注。但是一直以來很多Java程序員對他都不是很重視,直到在JDK 1.8中,HashMap會將其鏈表轉換成紅黑樹,此後,很多人就開始重新學習紅黑樹的有關知識。

作者在學習紅黑樹時,查閱了很多資料都沒有找到解釋的特別清楚的,於是自己總結了這一篇文章,總字數近7k,而且繪製了很多圖,希望可以讓大家更容易理解。

1. 定義

紅黑樹是Avl樹的一個變種,它也是在二叉查找樹的基礎上添加平衡條件,只是它對平衡條件的描述不像AVl樹那樣直接,而是轉化成對節點顏色規則的描述。

顏色規則:

  1. 對於任意節點,要麼是紅色,要麼是黑色;
  2. 根節點是黑色的;
  3. 如果一個節點是紅色的,那麼它的子節點必須是黑色的(即不能有兩個連續的紅色節點);
  4. 任意節點到其下面各個空結點(後面稱為nil節點,並約定其顏色為黑色)的路徑上都包含相同數目的黑色節點(稱為黑高);

通過對任何一條從根到空節點的路徑上各個結點的顏色進行約束,紅黑樹可以確保沒有一條路徑會比其他路徑長出2倍,因而紅黑樹是近似平衡的。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

2. 分析實現

為了便於處理紅黑樹代碼中的邊界條件,使用兩個標記節點header和nil來充當哨兵,它們與樹中的普通節點有相同的屬性,顏色設為黑色。

將header的值設為負無窮,因此它的右子節點就是真正的根,在對樹進行遍歷時我們可以用header作為起點。

nil的值可以為任意值,將它作為所有空節點的統一引用, 即讓所有樹葉的左右子節點都指向nil,那麼在遍歷時就可以將nil視為結束的標誌。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

紅黑樹也是一顆查找二叉樹,所以它的基本操作與查找二叉樹一致,區別也在於插入和刪除後的平衡恢復。只是實現平衡時的目標就是滿足它對顏色規則的定義,至於樹的平衡與否已經由規則本身去保證。

2.1. 插入

與二叉查找樹一樣,插入節點其實就是添加一個樹葉,主要問題是在添加結束時可能要做一些調整來保證紅黑樹的規則不被破壞。

不過,根據規則4, 可以知道,在插入之前任意節點到其下面各個nil的路徑上都包含相同數目的黑節點數,如果能保證在插入節點前後父節點parent或者祖父節點grand到其下面nil的路徑上的黑節點數不變, 即保證parent或者grand的黑高不變,那麼就可以保證紅黑樹的規則不被破壞,這樣就可以將平衡操作時需要考慮的範圍縮小到grand。

由於插入紅色節點不會影響路徑上的黑節點數,因此就先將待插入的節點塗成紅色,如果parent是黑色,那麼插入直接完成。如果parent是紅色,那麼插入之後將違反規則3, 這時再做一些調整。

先分析一下插入之前的可能情形,根據規則3,如果parent是紅色,那麼可以斷定grand以及parent的另一個子節點一定是黑色。不僅如此,因為插入的是樹葉節點在之前一定是nil, 那麼parent的另一個子節點必然也是nil,否則插入之前parent就違反了規則4。

再看parent的兄弟節點,如果它是黑色的,那麼它一定也是nil節點;如果它是紅色的,那麼它的兩個子節點一定都是nil節點,否則插入之前grand就違反了規則4。

所以,如果parent是紅色,那麼我們可以完整地畫出插入之前的所有可能情況,一共有4種,其中待插入節點的位置就是圖中parent下面的兩個nil之一。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

可以看出,1和2,3和4分別是對稱的兩種情況,區別在於parent與grand的大小關係,其實可以概括為parent的兄弟節點uncle是黑色還是紅色的兩種情況。

1. 如果parent為紅色,uncle為黑色

以情況1為例(即parent小於grand):如果current小於parent,那麼插入之後,可以將parent和grand的顏色互換,然後將grand右旋,這樣就能保證原來以grand為根的這個子樹的黑高保持不變,也就保證了整棵樹不被破壞。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

如果current大於parent,則可以先將parent左旋,然後將current視為parent,再與上面進行相同的操作,即可恢復平衡。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

2. 如果parent為紅色,uncle為紅色

這時無論怎麼旋轉都已經解決不了問題,不過可以知道,在插入之前grand到下面各個nil的黑節點數為1,因此,只要保證grand的黑高仍然為1就行, 那麼,通過將grand與其左右子節點的顏色進行翻轉便可達到目標。
以情況3為例:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

但是,翻轉會使grand變成紅色,這時如果曾祖父也是紅色,那麼將違反了規則3。因此可能需要朝著樹根的方向進行逐層分析,直到不再有兩個相連的紅色節點或者到達樹根為止,但這顯然會很麻煩。

事實上,由於顏色翻轉的等價性,我們可以在查找插入節點位置的過程中,通過顏色翻轉來避免掉uncle為紅色的情況,即將顏色翻轉的事情提前做了。

在自上而下的查找過程中,如果看到一個節點X的兩個子節點都為紅色,可以使X變成紅色,而兩個子節點變成黑色(如果X是根,則可以在翻轉之後再將其置為黑色),結果依然滿足紅黑樹的規則。

只有當X的parent為紅色時,才會破壞紅黑樹的規則。不過這時可以斷定X的兄弟節點,X的grand,以及X的uncle都是黑色的(因為如果X的parent和uncle都為紅色, 那麼一定會在自上而下的查找和翻轉過程中被過濾掉)。

那麼,可以將X視為current,然後與上面parent為紅色,uncle為黑色的情形進行相同的處理。下面通過畫圖來說明一種情形(對稱情形類似處理):
先對X與其子節點進行顏色翻轉

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

然後,將X視為之前的current,對parent和grand進行換色和旋轉

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

完成之後,將新的根(即原本grand的位置)視為新的current,然後重新向下遍歷。由於X的孫子節點之前一定為黑色,而X的兒子節點在翻轉之後也為黑色,因此在調整之後, 可以保證在X的下面將有連續兩層不會出現紅節點。

所以,儘管旋轉調整會使得gand的位置變得錯誤,但在接下來的遍歷中,又會立即將grand-parent-current恢復成正確的位置關係。

總結節點插入問題:首先根據二叉查找樹的性質,可以知道插入節點就是添加樹葉,其次對於紅黑樹而言,插入紅色節點不會影響路徑上的黑節點數。

因此, 我們將插入的節點視為紅色的樹葉,這時只要考慮插入的紅色樹葉的parent是紅色還是黑色,如果是黑色問題直接解決,如果是紅色,再看uncle是紅色還是黑色。如果uncle是黑色,可以通過變色旋轉解決;如果是紅色,那麼通過顏色翻轉也能達到目的,但這會使grand變成紅色,需要進一步考慮曾祖父的顏色問題,將問題放大了。於是考慮在一開始向下的遍歷過程中, 提前翻轉顏色來避免掉parent和unlce同時為紅色的情況。

因此,最終在插入節點時,實際需要考慮旋轉調整的情形只有一種,即parent為紅色,uncle為黑色。

2.2. 刪除

對於節點刪除,大概思路也是先將問題轉化成刪除樹葉來簡化問題,如果是紅色樹葉,則可以直接刪除,如果是黑色樹葉,則再分情形進行討論。

先看下如何將刪除目標轉化成對樹葉的刪除,根據二叉查找樹的性質,在刪除節點時可以用右子樹的最小節點或左子樹的最大節點來替換要刪除的節點,然後刪除子樹中用來替換的那個節點。

不過在紅黑樹中,為了保證節點的顏色規則,替換時將只進行值的替換。其實,根據紅黑樹的規則可以得出一個簡單的推論:如果一個樹葉沒有兄弟節點,那麼它一定為紅色,或者說如果一個樹葉為黑色, 則它必然有兄弟節點。

另外,還可以斷定:對於任意節點,其右子樹的最小(即最左)節點最多存在一個紅色右子節點,其左子樹的最大(即最右)節點最多存在一個紅色左子節點。因此,當左子樹的最大節點或右子樹的最小節點不是樹葉時,可以繼續用同樣的思路來轉換要刪除的目標,並且這時可以斷定最後刪除的樹葉一定是紅色。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

再來討論刪除黑色樹葉的情形,由於黑色樹葉必然會存在兄弟節點(樹根除外),因此在下面的討論中首先假設要刪除的黑色樹葉總是在左邊(如果在右邊則對稱處理), 這時可以根據其兄弟節點brother和父親節點parent的顏色來將刪除情形分為3種。下面分別進行畫圖分析(圖中省略掉空節點nil):

1.parent為紅色,brother為黑色
由於current是樹葉,那麼可以斷定brother下面不會再有黑色的後代節點,至多隻能有紅色的兒子節點,否則parent在刪除前將違反規則4。

1.1 先看brother沒有兒子的情況,此時看上去比較簡單,直接對parent進行左旋即可解決問題,能夠保證在刪除調整前後以原parent為根的子樹的黑高不變。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

1.2 但是,如果brother有一個紅色左兒子(右兒子沒有影響,因此忽略),那麼直接對parent進行左旋,將會造成兩個連續的紅色的節點而違反規則3, 這時需要先將brother右旋,然後再對parent進行換色並左旋來解決。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

因此,需要根據brother是否有紅色左兒子將情形1再分為2種子情形來進行處理。

2.parent為黑色,brother為紅色
同樣由於current是樹葉,可以斷定brother的兩個黑色的兒子節點下面至多隻能有一層紅色子節點,下面根據brother左兒子的子節點情況(右兒子的子節點沒有影響,因此忽略)來分情形討論。

2.1 還是先看brother的左兒子沒有子節點的情況,這時可以將brother與其左兒子進行換色,然後將parent進行左旋。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

2.2 如果brother的左兒子有一個紅色右子節點,這時需要先將brother左兒子的右子節點塗成黑色,並將brother右旋,然後再將parent左旋。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

2.3 如果brother的左兒子沒有紅色右子節點,但有一個紅色左子節點。這時可以先將brother左兒子的左子節點塗成黑色,並將brother的左兒子右旋,這樣將情形轉化成2.2, 然後依次將brother右旋,parent左旋。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

因此,需要根據brother左兒子的子節點情況將情形2再分為3種子情形來處理。

3.parent為黑色,brother為黑色
還是由於current是樹葉,可以斷定brother下面至多隻能有一層紅色的兒子節點,否則parent在刪除前就違反了規則4。

3.1 還是先看brother沒有兒子的情況,此時通過parent為根的子樹自身已經解決不了問題,想法是將brother變成紅色來降低parent子樹的黑高,然後將parent子樹整個視為一個current進行處理, 下面會再詳細說明。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

3.2 如果brother有一個紅色右兒子,這時可以將brother的右兒子變成黑色,然後將parent左旋。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

3.3 如果brother沒有紅色右兒子,但是存在一個紅色左兒子,這時可以將brother的左兒子變成黑色,然後將brother右旋,再將parent左旋。

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

因此,需要根據brother的子節點情況將情形3再分為3種子情形來處理。

小結一下,上面一共將刪除了黑色樹葉的情形分成了8種,除了3.1,其它都可以在parent子樹內部解決問題。為了進一步分析3.1的問題,需要擴展下思維, 可以想象一下,將current是樹葉這個前提去掉,然後將current視為根為黑色的子樹。假設這時刪除的黑色樹葉落在current子樹下面, 並且遇到了情形3.1,那麼可以看成將current子樹整體的黑高降1,然後再根據brother子樹和parent的情況來進行處理。

如果依然解決不了問題, 那麼可以將parent樹視為新的current子樹, 再將祖父節點視為新的parent,這樣逐層向上分析,直到能夠在新的parent為根的樹內部解決問題或者到達樹根為止。

下面通過畫圖再對上面的情形進行一遍說明,對於空節點nil可以使用黑色節點代替。另外,這時有一個前提假設:即在刪除之前current子樹的黑高一定大於1, 那麼brother子樹的黑高必然也至少為2,也就是說brother到nil的路徑上必然還有黑色節點。

為了更好的說明,圖中用x標出樹根, 並用a,b,c,d,e,f,g,h標出樹根下面的一些相對位置(可以將這些位置看成nil,只是圖中做了省略,沒有畫出current和brother到這些nil的完整路徑)。所有圖的最終目標的都是保證:如果current子樹的黑高降1,那麼通過調整保證x到a,b,c,d,e,f,g,h這些位置的相對黑高不變。

對於1.1,如果current子樹黑高降1,與之前一樣,直接將parent左旋,可以保證在調整前後,x到a,b,c,d,e的相對黑高保持不變,如圖:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於1.2,如果current子樹黑高降1,與之前一樣,先將brother右旋,然後將parent變成黑色並左旋,也能保證x到a,b,c,d,e的相對黑高保持不變,如圖:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於2.1,如果current子樹黑高降1,與之前一樣,將brother與左兒子換色,然後對parent左旋,可以保證x到a,b,c,d,e,f,g,h的相對黑高保持不變,如圖:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於2.2,如果current子樹黑高降1,與之前一樣,將brother的左兒子的右子節點塗成黑色,然後對brother右旋,再對parent左旋,也可以保證x到a,b,c,d,e,f,g,h的相對黑高保持不變, 如圖(A塗成灰色表示其顏色可以紅或者黑,沒有影響):

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於2.3,如果current子樹黑高降1,與之前一樣,先將brother的左兒子的左子節點塗成黑色,並對將brother的左兒子右旋,這樣將情形轉化成2.2,然後再進行同樣的旋轉調整, 以保證x到a,b,c,d,e,f,g,h的相對黑高保持不變,如圖:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於3.1,如果current子樹黑高降1,與之前一樣,依然只能降低brother的黑高,使x到a,b,c,d,e,f的相對黑高同時降1,然後將parent視為新的current子樹繼續向樹根追溯,如圖:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於3.2,如果current子樹黑高降1,與之前一樣,將brother的右兒子塗黑,然後對parent左旋,也能保證x到a,b,c,d,e,f的相對黑高保持不變, 如圖(圖中將left塗成灰色表示其顏色可以紅或者黑,沒有影響):

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

對於3.3,如果current子樹黑高降1,與之前一樣,先將brother的左兒子塗黑,然後對parent右旋,再對parent左旋,也能保證x到a,b,c,d,e,f的相對黑高保持不變,如圖:

他一口氣寫出了這7k字的紅黑樹總結!看過的都說好

總結節點刪除問題:首先將問題轉化為對樹葉的刪除,如果樹葉是紅色則直接刪除,如果是黑色,則根據兄弟節點和父節點的顏色情況來對情形進行分類。想法是儘量在子樹中解決, 如果實在解決不了,就降低子樹的高度,並將子樹視為整體然後向樹根進行追溯,直到問題解決。

上面的分析中,對於刪除黑色樹葉的刪除,分成了8種情形,其中3.1需要向樹根追溯, 直到轉化成其它7種情形或到樹根為止才能解決。其次,2.3也需要轉化成2.2來解決,因此,對於黑色樹葉的刪除,實際上最後直接解決的情形可以歸結有6種。


3. java實現

3.1. Set

<code>
public class RBTreeSet> extends BinarySearchTreeSet {

private RBNode header;

private RBNode nil;

public RBTreeSet() {
nil = new RBNode<>(null);
nil.left = nil;
nil.right = nil;
header = new RBNode<>(null, nil, nil);
}

@Override
protected Node getRoot() {
return header.right;
}

@Override
protected boolean isEmptyNode(Node node) {
return nil == node;
}

@Override
public void clear() {
header.right = nil;
size = 0;
}

@Override
public boolean add(E e) {
RBNode current = header;
RBNode parent = header;

//先將nil的值設為e
nil.element = e;
//然後從header開始自上而下尋找值為e的節點a
while(compare(e, current) != 0){
parent = current;
if(compare(e, current) < 0){
current = getLeft(current);
}else{
current = getRight(current);
}
//如果左右子節點都為紅色,則進行顏色翻轉,避免插入時出現父親的兄弟節點為紅色的情況
if(getLeft(current).red && getRight(current).red){
current = handleReorient(current);
}
}

//如果發現了值相同的重複節點,直接覆蓋
if(current != nil){
current.element = e;
return true;
}

//新建一個數據節點代替nil,並將其左右子樹指向nil
current = new RBNode<>(e, nil, nil);
size++;

//當while結束時,必然可以明確待插入節點的parent節點,這裡使parent節點鏈接到新節點
current.parent = parent;
if(compare(e, parent) < 0){
parent.left = current;
}else{
parent.right = current;
}

//將新數據節點設成紅色,如果父親節點為紅色則需要旋轉調整

handleReorient(current);
return true;
}

private RBNode getLeft(Node node){
return (RBNode)node.left;
}

private RBNode getRight(Node node){
return (RBNode)node.right;
}

private int compare(E e, Node node) {
if(node == header){
return 1;
}else{
return e.compareTo(node.element);
}
}

private RBNode handleReorient(RBNode current) {
getLeft(current).red = false;
getRight(current).red = false;
current.red = true;

RBNode subRoot = current;
//翻轉之後發現parent也是紅色,進行換色和旋轉
RBNode parent = current.parent;
if(parent.red){
RBNode grand = parent.parent;
grand.red = true;
//旋轉parent, 向外旋轉到同一側
if(compare(current.element, grand) != compare(current.element, parent)) {
if(compare(current.element, parent) > 0){
rotateLeft(parent);
}else{

rotateRight(parent);
}
}
//旋轉grand
if(compare(current.element, grand) < 0){
subRoot = getLeft(grand);
subRoot.red = false;
rotateRight(grand);
}else{
subRoot = getRight(grand);
subRoot.red = false;
rotateLeft(grand);
}
}
//直接將根節點置為黑色
getRight(header).red = false;
return subRoot;
}

private void rotateRight(RBNode node) {
RBNode parent = node.parent;
RBNode left = getLeft(node);
RBNode leftRight = getRight(left);
left.right = node;
node.parent = left;
node.left = leftRight;
leftRight.parent = node;
left.parent = parent;
if(parent.right == node){
parent.right = left;
}else{
parent.left = left;
}
}

private void rotateLeft(RBNode node) {
RBNode parent = node.parent;
RBNode right = getRight(node);
RBNode rightLeft = getLeft(right);
right.left = node;
node.parent = right;

node.right = rightLeft;
rightLeft.parent = node;
right.parent = parent;
if(parent.right == node){
parent.right = right;
}else{
parent.left = right;
}
}

@Override
public boolean remove(Object o) {
RBNode current = header;
RBNode parent = header;

@SuppressWarnings("unchecked")
E e = (E)o;
nil.element = e;
while(compare(e, current) != 0){
parent = current;
if(compare(e, current) < 0){
current = getLeft(current);
}else{
current = getRight(current);
}
}

if(current == nil){ //沒有找到值為e的數據節點
return true;
}

size--;
RBNode node = current;
if(current.right != nil){ //替換右子樹最小節點
parent = current;
current = getRight(current);
while(current.left != nil){
parent = current;
current = getLeft(current);
}
node.element = current.element;
//右側最小節點不是葉子,則繼續向下遍歷一層,這時可以斷定樹葉是紅色

if(current.right != nil){
parent = current;
current = getRight(current);
parent.element = current.element;
parent.right = nil;
return true;
}
}else if(current.left != nil){ //替換左子樹最大節點
parent = current;
current = getLeft(node);
while(current.right != nil){
parent = current;
current = getRight(current);
}
node.element = current.element;
//左側最大節點不是葉子,則繼續向下遍歷一層,這時可以斷定樹葉是紅色
if(current.left != nil){
parent = current;
current = getLeft(current);
parent.element = current.element;
parent.left = nil;
return true;
}
}

//先調整再刪除,因為後面還需要依賴current與parent的位置關係判斷
if(!current.red){
fixRemove(current);
}

//刪除樹葉leaf
if(current == parent.left){
parent.left = nil;
}else{
parent.right = nil;
}
return true;
}

private void fixRemove(RBNode current){
RBNode parent = current.parent;
if(parent == header){

return;
}

if(current == parent.left){
RBNode brother = getRight(parent);
if(parent.red){ // 1.parent為紅色
if(getLeft(brother).red){
parent.red = false;
rotateRight(brother);
}
}else if(brother.red){ // 2.brother為紅色
if(!getLeft(brother.left).red && !getRight(brother.left).red){
brother.red = false;
getLeft(brother).red = true;
}else if(getRight(brother.left).red){
getRight(brother.left).red = false;
rotateRight(brother);
}else{
getLeft(brother.left).red = false;
rotateRight(getLeft(brother));
rotateRight(brother);
}
}else{ // 3. parent和brother都為黑色
if(!getLeft(brother).red && !getRight(brother).red){
brother.red = true;
fixRemove(parent);
return;
}else if(getRight(brother).red){
getRight(brother).red = false;
}else{
getLeft(brother).red = false;
rotateRight(brother);
}
}
//最後一步的調整都是parent左旋
rotateLeft(parent);
}else{ // 對稱情形
RBNode brother = getLeft(parent);
if(parent.red){
if(getRight(brother).red){
parent.red = false;
rotateLeft(brother);
}
}else if(brother.red){
if(!getLeft(brother.right).red && !getRight(brother.right).red){
brother.red = false;

getRight(brother).red = true;
}else if(getLeft(brother.right).red){
getLeft(brother.right).red = false;
rotateLeft(brother);
}else{
getRight(brother.right).red = false;
rotateLeft(getRight(brother));
rotateLeft(brother);
}
}else{
if(!getLeft(brother).red && !getRight(brother).red){
brother.red = true;
fixRemove(parent);
return;
}else if(getLeft(brother).red){
getLeft(brother).red = false;
}else{
getRight(brother).red = false;
rotateLeft(brother);
}
}
rotateRight(parent);
}
}

static class RBNode extends Node {

RBNode parent;

boolean red;

RBNode(E e) {
super(e);
}

RBNode(E e, RBNode left, RBNode right) {
super(e);
this.left = left;
this.right = right;
}

@Override
public String toString() {
return red ? element.toString() + "(red)" : element.toString();
}

}
}
/<code>


3.2. MultiTreeMap

<code>
public class MultiRBTreeMap, V> extends MultiBinarySearchTreeMap {


public MultiRBTreeMap() {

}

public MultiRBTreeMap(boolean deduplication, boolean asc){
super(deduplication, asc);
}

@Override
protected void init() {
nil = new RBNode(null, null, false, null, null);
nil.left = nil;
nil.right = nil;
header = new RBNode(null, null, false, nil, nil);
header.next = nil;
nil.prev = header;
}

public V put(K key, V value){
nil.key = key;
RBNode current = (RBNode)header;
RBNode parent = (RBNode)header;
while(compare(key, current) != 0){
parent = current;
if(compare(key, current) < 0){
current = getLeft(current);
}else{
current = getRight(current);
}
if(getLeft(current).isRed && getRight(current).isRed){
current = handleReorient(current);
}
}

if(current != nil){
if(deduplication){
V old = current.value;
current.key = key;
current.value = value;
return old;
}else{
while((current = getNext(current)).isRepeat);
V old = current.prev.value;

linkIn(new RBNode<>(key, value, true, nil, nil), current, false);
return old;
}
}

current = new RBNode<>(key, value, false, nil, nil);
current.parent = parent;
if(compare(key, parent) < 0){
parent.left = current;
linkIn(current, parent, false);
}else{
parent.right = current;
while((parent = getNext(parent)).isRepeat);
linkIn(current, parent, false);
}
//將新數據節點設成紅色,如果父親節點為紅色則需要旋轉調整
handleReorient(current);
return null;
}

private RBNode handleReorient(RBNode current) {
getLeft(current).isRed = false;
getRight(current).isRed = false;
current.isRed = true;

RBNode subRoot = current;
RBNode parent = getParent(current);
if(parent.isRed){
RBNode grand = getParent(parent);
grand.isRed = true;
//旋轉parent, 向外旋轉到同一側
if(compare(current.getKey(), grand) != compare(current.getKey(), parent)) {
if(compare(current.getKey(), parent) > 0){
rotateLeft(parent);
}else{
rotateRight(parent);
}
}
//旋轉grand
if(compare(current.getKey(), grand) < 0){
subRoot = getLeft(grand);
subRoot.isRed = false;

rotateRight(grand);
}else{
subRoot = getRight(grand);
subRoot.isRed = false;
rotateLeft(grand);
}
}
//直接將根節點置為黑色
getRight(header).isRed = false;
return subRoot;
}

private void rotateRight(Node node) {
Node parent = node.parent;
Node left = node.left;
Node leftRight = left.right;
left.right = node;
node.parent = left;
node.left = leftRight;
leftRight.parent = node;
left.parent = parent;
if(parent.right == node){
parent.right = left;
}else{
parent.left = left;
}
}

private void rotateLeft(Node node) {
Node parent = node.parent;
Node right = node.right;
Node rightLeft = right.left;
right.left = node;
node.parent = right;
node.right = rightLeft;
rightLeft.parent = node;
right.parent = parent;
if(parent.right == node){
parent.right = right;
}else{
parent.left = right;

}
}

private int compare(K key, Entry node) {
if(node == header){
return 1;
}else{
return key.compareTo(node.getKey());
}
}

private RBNode getParent(Node node){
return (RBNode)node.parent;
}

private RBNode getNext(Node node){
return (RBNode)node.next;
}

private RBNode getLeft(Node node){
return (RBNode)node.left;
}

private RBNode getRight(Node node){
return (RBNode)node.right;
}

@Override
protected void remove(Node node){
RBNode rbnode = (RBNode)node;
RBNode current = rbnode;
RBNode parent = getParent(current);

if(current.right != nil){
current = getRight(current);
while(current.left != nil){
parent = current;
current = getLeft(current);
}
locationExchange(rbnode, current);
if(rbnode.right != nil){//node.right is red
parent = rbnode;
rbnode = getRight(rbnode);
locationExchange(parent, rbnode);
rbnode.right = nil; //交換過位置
return;
}
}else if(current.left != nil){
current = getLeft(current);
while(current.right != nil){
parent = current;
current = getRight(current);
}
locationExchange(rbnode, current);
if(rbnode.left != nil){//node.left is red
parent = rbnode;
rbnode = getLeft(rbnode);
locationExchange(parent, rbnode);
rbnode.left = nil;
return;
}
}

if(!rbnode.isRed){
fixRemove(rbnode);
}else if(rbnode == parent.left){
parent.left = nil;
}else{
parent.right = nil;
}
}

//這裡不能簡單的交換節點的值,因為要保證鏈表和樹中刪除的是同一個節點實例即node
private void locationExchange(RBNode node, RBNode current){
RBNode parent = getParent(node);
RBNode
left = getLeft(node);
RBNode right = getRight(node);
boolean isRed = node.isRed;
replaceChild(current.parent, current, node);
node.left = current.left;
node.left.parent = node;
node.right = current.right;
node.right.parent = node;
node.isRed = current.isRed;
replaceChild(parent, node, current);
current.left = left;
left.parent = current;
current.right = right;
right.parent = current;
current.isRed = isRed;
}

private void fixRemove(RBNode node){
RBNode parent = getParent(node);
if(parent == header){
return;
}

if(node == parent.left){
parent.left = nil;
RBNode brother = getRight(parent);
if(parent.isRed){ // 1.parent為紅色
if(getLeft(brother).isRed){
parent.isRed = false;
rotateRight(brother);
}
}else if(brother.isRed){ // 2.brother為紅色
if(!getLeft(brother.left).isRed && !getRight(brother.left).isRed){
brother.isRed = false;
getLeft(brother).isRed = true;
}else if(getRight(brother.left).isRed){
getRight(brother.left).isRed = false;
rotateRight(brother);
}else{
getLeft(brother.left).isRed = false;
rotateRight(brother.left);
rotateRight(brother);
}
}else{ // 3. parent和brother都為黑色
if(!getLeft(brother).isRed && !getRight(brother).isRed){

brother.isRed = true;
fixRemove(parent);
return;
}else if(getRight(brother).isRed){
getRight(brother).isRed = false;
}else{
getLeft(brother).isRed = false;
rotateRight(brother);
}
}
//最後一步的調整都是parent左旋
rotateLeft(parent);
}else{ // 對稱情形
parent.right = nil;
RBNode brother = getLeft(parent);
if(parent.isRed){
if(getRight(brother).isRed){
parent.isRed = false;
rotateLeft(brother);
}
}else if(brother.isRed){
if(!getLeft(brother.right).isRed && !getRight(brother.right).isRed){
brother.isRed = false;
getRight(brother).isRed = true;
}else if(getLeft(brother.right).isRed){
getLeft(brother.right).isRed = false;
rotateLeft(brother);
}else{
getRight(brother.right).isRed = false;
rotateLeft(brother.right);
rotateLeft(brother);
}
}else{
if(!getLeft(brother).isRed && !getRight(brother).isRed){
brother.isRed = true;
fixRemove(parent);
return;
}else if(getLeft(brother).isRed){
getLeft(brother).isRed = false;
}else{
getRight(brother).isRed = false;
rotateLeft(brother);
}
}
rotateRight(parent);
}
}

static class RBNode extends Node {

boolean isRed;

public RBNode(K key, V value) {
super(key, value);
}

RBNode(K key, V value, boolean isRepeat, Node left, Node right) {
super(key, value, isRepeat, left, right);
}

@Override
public String toString() {
String str = super.toString();
return isRed ? str + "(red)" : str;
}
}
}
/<code>


3.3. MultiTreeSet

有了Map之後,就可以很容易的依賴它實現一個性質相似的Set:

<code>public class MultiRBTreeSet> implements Set{

private static final Object V = new Object();

private MultiRBTreeMap map;

public MultiRBTreeSet(){
map = new MultiRBTreeMap<>();
}

@Override
public int size() {
return map.size();
}


@Override
public boolean isEmpty() {
return map.isEmpty();
}

@Override
public boolean contains(Object o) {
return map.containsKey(o);
}

@Override
public Iterator iterator() {
return map.keySet().iterator();
}

public Iterator iterator(E from, boolean fromInclusive, E to, boolean toInclusive) {
return map.keySet(from, fromInclusive, to, toInclusive, false).iterator();
}

@Override
public boolean add(E e) {
map.put(e, V);
return true;
}

@Override
public boolean remove(Object o) {
map.removeList(o);
return true;
}

@Override
public boolean containsAll(Collection> c) {
return map.keySet().containsAll(c);
}

@Override
public void clear() {
map.clear();
}

@Override
public boolean addAll(Collection extends E> collection) {
for(E e : collection){
map.put(e, V);
}
return true;

}

@Override
public boolean removeAll(Collection> collection) {
for(Object o : collection){
map.removeList(o);
}
return true;
}

@Override
public boolean retainAll(Collection> c) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();
}

@Override
public Object[] toArray() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();
}

@Override
public T[] toArray(T[] a) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();
}
}
/<code>

4. 紅黑樹的高度

根據規則4,任意節點到其下面各個空節點的黑高相等。那麼,記根為x的紅黑樹的黑高為h(x),那麼紅黑樹中至少包含2^h(x) - 1個黑色節點。

根據規則3,在高度為H的紅黑樹中,任何一條路徑至多有一半的紅色節點,即至少有一半的黑色節點數,也就是說黑高至少為H/2。

如果紅黑樹的節點數為N,那麼有:N >= 2^(H/2) - 1, 即H <= 2lg(N+1)。

既然有了Avl樹,為什麼還要紅黑樹?

因為紅黑樹的綜合性能要由於Avl樹,主要原因樹紅黑樹恢復平衡時需要的旋轉次數比Avl樹更少,這個網上有很多,但從之前對Avl樹的討論中可以看出,即便在最差的情形中, 最多也只要4次旋轉便可恢復平衡。

因此,如果說Avl的旋轉次數多不應該是指單次刪除或者添加中發生的旋轉次數,而是很多次插入刪除後的一個統計值。另外在Avl樹中,即便只需要一次旋轉調整,也免不了要依次檢查到根節點,這個過程是自下而上的,即從葉節點追溯到根節點,而插入和刪除首先都是自上而下的。

因此在實現中如果不使用遞歸的方式將發生兩次遍歷過程,這也是之前介紹Avl樹不實現MultiTreeMap的原因。而在紅黑樹中,則可以將調整問題縮小在父節點為根的子樹中, 這些問題都可以解決,代價只是稍微犧牲了一些平衡性。


我目前是在職Java開發,如果你現在正在瞭解Java技術,想要學好Java,渴望成為一名Java開發工程師,在入門學習Java的過程當中缺乏基礎的入門視頻教程,你可以關注並私信我:01。我這裡有一套最新的Java基礎JavaSE的精講視頻教程,這套視頻教程是我在年初的時候,根據市場技術棧需求錄製的,非常的系統完整。


分享到:


相關文章: