首先,在閱讀文章之前,我希望讀者對二叉樹有一定的了解,因為紅黑樹的本質就是一顆二叉樹。所以本篇博客中不在將二叉樹的增刪查的基本操作了,需要了解的同學可以到我之前寫的一篇關於二叉樹基本操作的博客:https://www.cnblogs.com/rainple/p/9970760.html;
有隨機數節點組成的二叉樹的平均高度為logn,所以正常情況下二叉樹查找的時間復雜度為O(logn)。但是,根據二叉樹的特性,在最壞的情況下,比如存儲的是一個有序的數據的話,那么所以的數據都會形成一條鏈,此時二叉樹的深度為n,時間復雜度為O(n)。紅黑樹就是為了解決這個問題的,它能夠保證在任何情況下樹的深度都保持在logn左右,紅黑樹通過一下約束來完成這個特性:
1、每個節點不是紅色就是黑色。
2、根節點為黑色。
3、每個葉子節點都是黑色的。
4、每個紅色節點的子節點都是黑色。
5、任意節點,到其任意葉節點的所有路徑都包含相同的黑色節點。
結構如下圖:
紅黑樹的基本操作包括刪除和添加。在刪除或者添加一個節點的時候就有可能打破原有的紅黑樹維持的平衡,那么就需要通過着色和旋轉的方式來使紅黑樹重新達到平衡。着色是非常簡單的,直接將節點的顏色改變就可以了,多以要理解紅黑樹,就必須需要懂得如何進行旋轉,旋轉又分為左旋和右轉,兩個操作相反的,所以理解了一個旋轉的操作就很容易理解另一個旋轉了。
左旋:
如圖所示,紅色節點為旋轉支點,支點往左子樹移動即為左旋。左旋之后我們可以看到原支點的位置被原支點的右子節點代替,新支點的左子節點變為了原來為父節點的原支點,新支點的左子節點變為原支點的右子節點,因此左旋操作總共右3個節點,以為旋轉前的結構舉例,分別為紅色節點(原支點),黃色節點(新支點)和L節點。Java代碼實現如下:
/** * 左旋 * @param e 支點 */ private void leftRotate(Entry<K,V> e){ //支點的右子節點 Entry<K,V> right = e.right; //支點右子節點的左子節點 Entry<K,V> rightOfLeft = right.left; //新舊支點的替換 right.parent = e.parent; if (e.parent == null){ root = right; }else { if (e == e.parent.left) e.parent.left = right; else e.parent.right = right; } //將原支點變為新支點的左節點 right.left = e; e.parent = right; //將新支點的左節點變為就支點的右節點 e.right = rightOfLeft; if (rightOfLeft != null) rightOfLeft.parent = e; }
因為在紅黑樹中每個節點都有一個指針指向自己的父節點,父節點也有指針指向子節點,因為在改動一個節點的時候都需要分別改動當前節點和父節點的指向,結合左旋的示意圖,用Java代碼實現起來就不會很困難了。
右旋
右旋操作和左旋相反的,兩者互反。依然是紅色作為旋轉支點,右旋后黃色節點代替了紅色節點原來的位置,黃色節點的右節點旋轉后變為紅色節點的左節點。Java 代碼實現如下:
/** * 右旋 * @param e 旋轉支點 */ private void rightRotate(Entry<K,V> e){ //原支點的左節點 Entry<K,V> left = e.left; //原支點的左節點的右節點 Entry<K,V> leftOfRight = left.right; //新舊支點的替換 left.parent = e.parent; if (e.parent == null){//支點的父節點為根節點的情況 root = left; }else {//非跟節點 if (e == e.parent.left) e.parent.left = left; else e.parent.right = left; } //將原支點變為新支點的右節點 left.right = e; e.parent = left; //將新支點未旋轉前的右節點變為轉換后的原支點的左節點 e.left = leftOfRight; if (leftOfRight != null) leftOfRight.parent = e; }
添加節點
首先,在進入主題之前我們再來回顧一下紅黑樹的5個特點:
1、每個節點不是紅色就是黑色。
2、根節點為黑色。
3、每個葉子節點都是黑色的。
4、每個紅色節點的子節點都是黑色。
5、任意節點,到其任意葉節點的所有路徑都包含相同的黑色節點。
紅黑樹插入節點與二叉樹是一致的,所以每次添加節點肯定是添加到葉子節點上,具體步驟如下:
第一步:將新節點插入到紅黑樹中。
第二步:將新節點設置為紅色。這里為什么需要設置成紅色呢?主要是為了滿足特性5,這樣在插入節點后就少解決了一個沖突,也就少一點麻煩。插入完成后,我們來看一下還有那些特性是有可能發生沖突的,特性1每個節點不是紅色就是黑色的,這明顯沒有沖突,特性2根節點為黑色,當插入節點為根節點的時候就會有沖突了,這種就很簡單了,直接將根節點着色為黑色即可。特性3每個葉子節點都是黑色,這個明顯沒有沖突。特性4每個紅色節點的子節點都是黑色的,這個特性就有可能會沖突,因為在插入新節點的時候我們無法確定新節點的父節點的顏色是黑色的還是紅色,如果新節點的父節為黑色,那么就不會有沖突,否則就會違背了特性4。特性5任意節點,到其任意子節點的所有路徑都包含相同的黑色節點,因為我們插入的新節點被着色為紅色,所以並不會影響到每個路徑的黑色節點的數量,因此也不會有沖突。綜上所訴,那么在插入新節點的時候,只有特性4有可能發生沖突。
第三步:平衡紅黑樹,使之成為新的紅黑樹。
根據第二部得到的結論,我們可以知道只有情況是需要解決沖突的,那就是新節點的父節點為紅色的時候違背了特性4。接下來我們將要討論這個問題,因為在新插入一個節點之前是一顆已經平衡了的紅黑樹,因此根據特新4,新節點的祖父節點必定為黑色。根據這種情況,我們又可以分為以下四種情況:
情況1:新節點為左節點,叔叔節點為紅色;
情況2:新節點為左節點,叔叔節點為黑色;
情況3:新節點為右節點,叔叔節點為紅色;
情況4:新節點為右節點,叔叔節點為黑色;
情況1和情況3的情況是一樣的,所以我們可以將這兩種情況看作是一種情況,這個情況我們稍后再討論,然后看一下情況2和情況4,通過左旋就可以轉換成情況2。
綜上所述,我們可以歸結為3中情況:
情況1:叔叔節點是紅色節點;
情況2:叔叔節點是黑色節點,新節點為右節點;
情況3:叔叔節點是黑色節點,新節點為左節點;
上面我也有提到,當插入新節點時肯定是屬於第一種情況的,然后2、3由1轉換而來,在此之前我希望你之前已經了解過遞歸的原理和思想,把局部看作整體的思想,因為這將有助於下面討論的理解。下面我們將要繼續分析這三種情況,情況1這種情況處理起來比比較簡單,只需要將祖父節點變為紅色節點,父節點和叔叔節點變為黑色即可,這僅僅只是當整個紅黑樹只有這幾個節點的時候是可以了,但事實並非如此,這僅僅只是達到了局部平衡。
上圖,我們看到已經達到了局部的平衡,但是,我們還會有其他的情況,那就是祖父節點有可能也會有父節點。那么又會有兩種情況,1是祖父節點的父節點可能是黑色的,2是可能是紅色的,如果黑色那么整個紅黑樹就達到平衡了。不知道大家根覺到了沒有,這兩種情況是不是跟新插入一個節點的情況是一致的,是不是又回到了插入新節點的問題了?於是我將局部收到影響的部分畫出來,如圖:
圖a就是將情況1從新着色后的部分受影響的節點,當然只是其中的一種情況,此時我們將已經平衡的部分去掉就變成的圖b的情況,這種情況是不是很熟悉呢?我們的祖父節點當成新節點,是不是相當於上面討論的情況1呢?不過與上面討論的情況不同的是,這里3中可能情況都可能出現,因為叔叔節點有可能為紅色或黑色。所以這時候才有可能出現真正的三種情況:
情況1:叔叔節點是紅色節點;
情況2:叔叔節點是黑色節點,新節點為右節點;
情況3:叔叔節點是黑色節點,新節點為左節點;
如果為情況1的話,我們一層一層的往上平衡就可以了,當祖父節點為根節點的時候,我們直接將根節點着色為黑色即可,因為祖父節點的兩個子節點都是黑色的,所以變為黑色后仍然是平衡的。接下來我們來討論下情況2和3。
很明顯的,這兩種情況的右節點多出了一個黑色節點,這種情況是在情況1向上着色的時候造成的,即祖父節點由黑色節點變為了紅色節點。情況2以父節點為支點左旋,然后將父節點和新節點互換可以得到情況3:
情況3進行的操作是,首先將父節點着色為黑色,祖父節點着色為紅色,然后以祖父為支點進行右旋
情況3旋轉結束后整棵紅黑也已經重新恢復平衡了。單從部分其實並看不出已經平衡了,我們可以將三個情況連起來就可以看到了,如下圖:
上圖中都是以n節點為參考點的,其余無關的節點就不標出來了。n節點即為插入節點,但是除了第一次操作n節點為真正的新節點,此后的操作所指的n節點只是有助於我們的理解把他當成新節點。當然,這只是其中的一種情況,其他其他的情況可以通過不斷向上旋轉或着色,最終也會達到這種情況或者頂部是p節點為根節點的時候,第二種情況直接將根節點着色為黑色即可。
總結:
回顧一下紅黑樹的5個特性:
1、節點不是紅色就是黑色。
2、根節點為黑色。
3、葉子節點為黑色。
4、每個紅色節點其子節點必須是黑色節點。
5、任意節點到到其任意的子節點的所有路徑的黑色節點的數量相等。
在插入新節點的時候很顯然,不會違背1和3,如果插入的是根節點直接將根節點着色為黑色即可,這種情況可以忽略不計,所以插入節點時可能會違背了4和5,又因為插入的是紅色節點因此5也不會違背,最后在插入新節點的時候我們只需要關注特性4就可以了。當父節點為紅色的時候跟4有沖突,所以我們接下來討論的就是這種情況。我們知道,在插入新節點之前整顆紅黑樹是平衡的,因此可以得出一個結論就是祖父節點肯定肯定是黑色的。我們現在只關注相關的節點即可,目前,我們知道了祖父的節點為黑色,父節點為紅色,但是叔叔節點的顏色不知道,新節點的位置也不能確定,所以有2x2中情況,當叔叔節點為紅色的時候,兩種情況的處理方式是一致的,所以最后我們可以總結為3中情況:
1、叔叔節點為紅色
2、新節點為右節點,叔叔節點為黑色
3、新節點為左節點,叔叔節點為黑色
類型 | 描述 | 步驟 | 示意圖 |
情況1 | 叔叔節點為紅色 | 1、父節點設為黑色 2、叔叔節點設為黑色 3、祖父節點設為紅色 4、把祖父節點設置為新節點(當前節點) |
![]()
|
情況2 | 新節點為右節點,叔叔節點為黑色 | 1、以父節點為支點左旋 2、父節點和新節點互換位置 3、把父節點設為當前節點 |
![]()
|
情況3 | 新節點為左節點,叔叔節點為黑色 | 1、父節點設為黑色 2、祖父節點設為紅色 3、以祖父節點為支點右旋 |
![]()
|
整合 | 樹a就是表中的情況1,通過着色后直接轉換成了情況3,情況3進行着色旋轉后達到了平衡,當樹b中的叔叔節點為紅色的時候與樹a一致,循環調用樹a的處理方式,直至達到樹b的情況或者樹a中的祖父節點到達了根節點,這時候將祖父節點設為黑色即可。 這種情況就是由情況1轉情況2再轉情況3,由情況3重新着色旋轉后達到平衡。 需要注意的是:不是每次插入節點都會出現3中情況,有可能只出現了2和3,或者只出現了3一種情況。 |
上面是討論左子樹的問題,因為紅黑色具有堆成性,因此在處理右子樹的時候與處理左子樹相反即可。Java代碼示例如下:
/** * 插入新節點后平衡紅黑樹 * @param e 新節點 */ private void fixAfterInsertion(Entry<K, V> e) { //將新插入節點設置為紅色 setRed(e); Entry<K,V> p,g,u;//父節點和祖父節點和叔叔節點 Entry<K,V> current = e;//新節點 /** * 這里通過循環不斷向上平衡 */ while ((p = parentOf(current)) != null && isRed(p)){ g = parentOf(p);//祖父節點 if (p == g.left){ u = g.right; //情況1:叔叔節點為紅色 if (u != null && isRed(u)){ setBlack(p);//父節點設為黑色 setBlack(u);//叔叔節點設為黑色 setRed(g);//祖父節點設為紅色 current = g;//把祖父節點設為當前節點 //繼續向上平衡 continue; } //情況2:當前節點為右節點,叔叔節點為黑色 if (current == p.right){ leftRotate(p);//父節點為支點左旋 Entry<K,V> tmp = p; p = current;//父節點和當前節點互換 current = tmp;//父節點設為當前節點 } //情況3:當前節點為左節點,叔叔節點為黑色 setBlack(p);//父節點設為黑色 setRed(g);//祖父節點設為紅色 rightRotate(g);//祖父節點為支點右旋 }else {//相反的操作 u = g.left; if (u != null && isRed(u)){ setBlack(p); setBlack(u); setRed(g); current = g; continue; } if (current == p.left){ rightRotate(p); Entry<K,V> tmp = p; p = current; current = tmp; } setBlack(p); setRed(g); leftRotate(g); } } //最后將根節點設置為紅色 setBlack(root); }
刪除節點
在二叉樹分析一文中已經說過,刪除一個節點的時候有3中情況:
1、刪除節點沒有子節點
2、刪除節點只有一個子節點
3、刪除節點有兩個子節點
首先,我們逐個來分析每種情況刪除節點后對整顆紅黑樹的平衡性的影響。在刪除節點時候紅黑樹的特性1,2,3肯定不會違背,所以只需要考慮特性4,5即可。
對於情況1,肯定不會違背特性4,如果刪除節點為紅色,那么對整顆紅黑樹的平衡性都不會影響,如果是黑色則違背了特性5,我們先將這種情況記錄下來,稍后再進一步討論。
對於情況2,有可能刪除的是左子樹或右子樹,暫且不討論。如果刪除的節點為紅色,不影響平衡性,如果刪除的是黑色,那么肯定會和特性5有沖突,當刪除節點的父節點為紅色,子節點為紅色是也和特性4有沖突。
對於情況3,其實最后刪除的是它的替代節點,根據替代節點的特點,最終其實是回到了1這種情況或者情況2。
總結上面的3種情況可得到一個結論,只有刪除節點為黑色時才會破壞紅黑樹原來的平衡,因在刪除節點之前紅黑樹是出於平衡狀態的,刪除之后很明顯的其兄弟節點分支必然比刪除節點的分支多了一個黑色的節點,因此我們只需要改變兄弟節點的顏色即可,我們只討論左節點,右節點對稱。
一、刪除節點的兄弟節點是紅色
將兄弟節點設為黑色,父節點設為紅色,以父節點為支點左旋轉,然后將父節點的右節點放到兄弟節點上:
二、兄弟節點是黑色的,兄弟的兩個子節點也都是黑色的
兄弟節點設為紅色,把父節點設置為新的刪除節點:
三、兄弟節點是黑色的,且兄弟節點的左子節點是紅色,右子節點是黑色
將兄弟節點的左子節點設為黑色,兄弟節點設為紅色,以兄弟節點為支點右旋,把父節點的右節點設置為兄弟節點
四、兄弟節點是黑色的,且兄弟節點的右子節點是紅色,左子節點任意顏色
把兄弟節點的設為父節點的顏色,父節點設為黑色,父節點的右節點設為黑色,父節點為支點左旋
刪除的Java代碼示例:
public V remove(Object key){ if (key == null) return null; Entry<K,V> delEntry; delEntry = getEntry(key); if (delEntry == null) return null; size--; Entry<K,V> p = delEntry.parent; if (delEntry.right == null && delEntry.left == null){ if (p == null){ root = null; }else { if (p.left == delEntry){ p.left = null; }else { p.right = null; } } }else if (delEntry.right == null){//只有左節點 Entry<K,V> lc = delEntry.left; if (p == null) { lc.parent = null; root = lc; } else { if (delEntry == p.left){ p.left = lc; }else { p.right = lc; } lc.parent = p; } }else if (delEntry.left == null){//只有右節點 Entry<K,V> rc = delEntry.right; if (p == null) { rc.parent = null; root = rc; }else { if (delEntry == p.left) p.left = rc; else p.right = rc; rc.parent = p; } }else {//有兩個節點,找到后繼節點,將值賦給刪除節點,然后將后繼節點刪除掉即可 Entry<K,V> successor = successor(delEntry);//獲取到后繼節點 boolean color = successor.color; V old = delEntry.value; delEntry.value = successor.value; delEntry.key = successor.key; if (delEntry.right == successor){//后繼節點為右子節點, if (successor.right != null) {//右子節點有右子節點 delEntry.right = successor.right; successor.right.parent = delEntry; }else {//右子節點沒有子節點 delEntry.right = null; } }else { successor.parent.left = null; } if (color == BLACK) //fixUpAfterRemove(child,parent); return old; } V old = delEntry.value; if (delEntry.color == BLACK)//刪除為黑色時,需要重新平衡樹 if (delEntry.right != null)//刪除節點的子節點只有右節點 fixUpAfterRemove(delEntry.right,delEntry.parent); else if (delEntry.left != null)//刪除節點只有左節點 fixUpAfterRemove(delEntry.left,delEntry.parent); else fixUpAfterRemove(null,delEntry.parent); delEntry.parent = null; delEntry.left = null; delEntry.right = null; return old; } private Entry<K, V> getEntry(Object key) { if (key == null) return null; Entry<K, V> delEntry = null; Entry<K, V> current = root; int ret; if (comparator == null){ Comparable<K> k = (Comparable<K>) key; while (current != null){ ret = k.compareTo(current.key); if (ret < 0) current = current.left; else if (ret > 0) current = current.right; else{ delEntry = current; break; } } }else { for (;current != null;){ ret = comparator.compare(current.key, (K) key); if (ret < 0) current = current.left; else if (ret > 0) current = current.right; else{ delEntry = current; break; } } } return delEntry; } //node表示待修正的節點,即后繼節點的子節點(因為后繼節點被挪到刪除節點的位置去了) private void fixUpAfterRemove(Entry<K, V> node,Entry<K,V> parent) { Entry<K,V> other; while((node == null || isBlack(node)) && (node != root)) { if(parent.left == node) { //node是左子節點,下面else與這里的剛好相反 other = parent.right; //node的兄弟節點 if(isRed(other)) { //case1: node的兄弟節點other是紅色的 setBlack(other); setRed(parent); leftRotate(parent); other = parent.right; } //case2: node的兄弟節點other是黑色的,且other的兩個子節點也都是黑色的 if((other.left == null || isBlack(other.left)) && (other.right == null || isBlack(other.right))) { setRed(other); node = parent; parent = parentOf(node); } else { //case3: node的兄弟節點other是黑色的,且other的左子節點是紅色,右子節點是黑色 if(other.right == null || isBlack(other.right)) { setBlack(other.left); setRed(other); rightRotate(other); other = parent.right; } //case4: node的兄弟節點other是黑色的,且other的右子節點是紅色,左子節點任意顏色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.right); leftRotate(parent); node = this.root; break; } } else { //與上面的對稱 other = parent.left; if (isRed(other)) { // Case 1: node的兄弟other是紅色的 setBlack(other); setRed(parent); rightRotate(parent); other = parent.left; } if ((other.left==null || isBlack(other.left)) && (other.right==null || isBlack(other.right))) { // Case 2: node的兄弟other是黑色,且other的倆個子節點都是黑色的 setRed(other); node = parent; parent = parentOf(node); } else { if (other.left==null || isBlack(other.left)) { // Case 3: node的兄弟other是黑色的,並且other的左子節點是紅色,右子節點為黑色。 setBlack(other.right); setRed(other); leftRotate(other); other = parent.left; } // Case 4: node的兄弟other是黑色的;並且other的左子節點是紅色的,右子節點任意顏色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.left); rightRotate(parent); node = this.root; break; } } } if (node!=null) setBlack(node); } private Entry<K, V> successor(Entry<K, V> delEntry) { Entry<K,V> r = delEntry.right;//assert r != null; while (r.left != null){ r = r.left; } return r; }
完整的代碼示例:
public class MyTreeMap<K,V> { private static final boolean BLACK = true; private static final boolean RED = false; private Entry<K,V> root; private int size = 0; private final Comparator<K> comparator; MyTreeMap(){ comparator =null; } public MyTreeMap(Comparator comparator){ this.comparator = comparator; } public V put(K key,V value){ if (root == null){ root = new Entry<>(key,value,null); size++; return null; }else { int ret = 0; Entry<K,V> p = null; Entry<K,V> current = root; if (comparator == null){ if (key == null) throw new NullPointerException("key = null"); Comparable<K> k = (Comparable<K>) key; while (current != null){ p =current; ret = k.compareTo(current.key); if (ret < 0) current = current.left; else if(ret > 0) current = current.right; else { current.value = value; return current.value; } } }else { do { p = current; ret = comparator.compare(key,current.key); if (ret < 0) current = current.left; else if (ret > 0) current = current.right; else { current.value = value; return value; } }while (current != null); } Entry<K,V> e = new Entry<>(key,value,p); if (ret < 0) p.left = e; else p.right = e; size++; fixAfterInsertion(e); return e.value; } } /** * 插入新節點后平衡紅黑樹 * @param e 新節點 */ private void fixAfterInsertion(Entry<K, V> e) { //將新插入節點設置為紅色 setRed(e); Entry<K,V> p,g,u;//父節點和祖父節點和叔叔節點 Entry<K,V> current = e;//新節點 /** * 這里通過循環不斷向上平衡 */ while ((p = parentOf(current)) != null && isRed(p)){ g = parentOf(p);//祖父節點 if (p == g.left){ u = g.right; //情況1:叔叔節點為紅色 if (u != null && isRed(u)){ setBlack(p);//父節點設為黑色 setBlack(u);//叔叔節點設為黑色 setRed(g);//祖父節點設為紅色 current = g;//把祖父節點設為當前節點 //繼續向上平衡 continue; } //情況2:當前節點為右節點,叔叔節點為黑色 if (current == p.right){ leftRotate(p);//父節點為支點左旋 Entry<K,V> tmp = p; p = current;//父節點和當前節點互換 current = tmp;//父節點設為當前節點 } //情況3:當前節點為左節點,叔叔節點為黑色 setBlack(p);//父節點設為黑色 setRed(g);//祖父節點設為紅色 rightRotate(g);//祖父節點為支點右旋 }else {//相反的操作 u = g.left; if (u != null && isRed(u)){ setBlack(p); setBlack(u); setRed(g); current = g; continue; } if (current == p.left){ rightRotate(p); Entry<K,V> tmp = p; p = current; current = tmp; } setBlack(p); setRed(g); leftRotate(g); } } //最后將根節點設置為紅色 setBlack(root); } public boolean containsKey(Object key){ return getEntry(key) != null; } public Set<Entry<K,V>> entrySet(){ Set<Entry<K,V>> list = new HashSet<>(size + 4); entries(root,list); return list; } private void entries(Entry<K,V> e,Set<Entry<K,V>> list){ if (e != null){ entries(e.left,list); list.add(e); entries(e.right,list); } } public boolean containsValue(V v){ return values().contains(v); } public V get(Object key){ Entry<K, V> entry = getEntry(key); return entry == null ? null : entry.getValue(); } private void setColor(Entry<K,V> e,boolean color){ if (e != null) e.color = color; } private void setRed(Entry<K,V> e){ setColor(e,RED); } private void setBlack(Entry<K,V> e){ setColor(e,BLACK); } private void setParent(Entry<K,V> e,Entry<K,V> p){ if (e != null) e.parent = p; } private boolean isBlack(Entry<K,V> e){ return colorOf(e) == BLACK; } private boolean isRed(Entry<K,V> e){ return !isBlack(e); } private Entry<K,V> parentOf(Entry<K,V> e){ return e == null ? null : e.parent; } private boolean colorOf(Entry<K,V> e){ return e == null ? BLACK : e.color; } /** * 右旋 * @param e 旋轉支點 */ private void rightRotate(Entry<K,V> e){ //原支點的左節點 Entry<K,V> left = e.left; //原支點的左節點的右節點 Entry<K,V> leftOfRight = left.right; //新舊支點的替換 left.parent = e.parent; if (e.parent == null){//支點的父節點為根節點的情況 root = left; }else {//非跟節點 if (e == e.parent.left) e.parent.left = left; else e.parent.right = left; } //將原支點變為新支點的右節點 left.right = e; e.parent = left; //將新支點未旋轉前的右節點變為轉換后的原支點的左節點 e.left = leftOfRight; if (leftOfRight != null) leftOfRight.parent = e; } /** * 左旋 * @param e 支點 */ private void leftRotate(Entry<K,V> e){ //支點的右子節點 Entry<K,V> right = e.right; //支點右子節點的左子節點 Entry<K,V> rightOfLeft = right.left; //新舊支點的替換 right.parent = e.parent; if (e.parent == null){ root = right; }else { if (e == e.parent.left) e.parent.left = right; else e.parent.right = right; } //將原支點變為新支點的左節點 right.left = e; e.parent = right; //將新支點的左節點變為就支點的右節點 e.right = rightOfLeft; if (rightOfLeft != null) rightOfLeft.parent = e; } public int getDeep(){ return deep(root); } private int deep(Entry<K,V> e){ int deep = 0; if (e != null){ int leftDeep = deep(e.left); int rightDeep = deep(e.right); deep = leftDeep > rightDeep ? leftDeep + 1 : rightDeep + 1; } return deep; } public V remove(Object key){ if (key == null) return null; Entry<K,V> delEntry; delEntry = getEntry(key); if (delEntry == null) return null; size--; Entry<K,V> p = delEntry.parent; if (delEntry.right == null && delEntry.left == null){ if (p == null){ root = null; }else { if (p.left == delEntry){ p.left = null; }else { p.right = null; } } }else if (delEntry.right == null){//只有左節點 Entry<K,V> lc = delEntry.left; if (p == null) { lc.parent = null; root = lc; } else { if (delEntry == p.left){ p.left = lc; }else { p.right = lc; } lc.parent = p; } }else if (delEntry.left == null){//只有右節點 Entry<K,V> rc = delEntry.right; if (p == null) { rc.parent = null; root = rc; }else { if (delEntry == p.left) p.left = rc; else p.right = rc; rc.parent = p; } }else {//有兩個節點,找到后繼節點,將值賦給刪除節點,然后將后繼節點刪除掉即可 Entry<K,V> successor = successor(delEntry);//獲取到后繼節點 boolean color = successor.color; V old = delEntry.value; delEntry.value = successor.value; delEntry.key = successor.key; if (delEntry.right == successor){//后繼節點為右子節點, if (successor.right != null) {//右子節點有右子節點 delEntry.right = successor.right; successor.right.parent = delEntry; }else {//右子節點沒有子節點 delEntry.right = null; } }else { successor.parent.left = null; } if (color == BLACK) //fixUpAfterRemove(child,parent); return old; } V old = delEntry.value; if (delEntry.color == BLACK)//刪除為黑色時,需要重新平衡樹 if (delEntry.right != null)//刪除節點的子節點只有右節點 fixUpAfterRemove(delEntry.right,delEntry.parent); else if (delEntry.left != null)//刪除節點只有左節點 fixUpAfterRemove(delEntry.left,delEntry.parent); else fixUpAfterRemove(null,delEntry.parent); delEntry.parent = null; delEntry.left = null; delEntry.right = null; return old; } private Entry<K, V> getEntry(Object key) { if (key == null) return null; Entry<K, V> delEntry = null; Entry<K, V> current = root; int ret; if (comparator == null){ Comparable<K> k = (Comparable<K>) key; while (current != null){ ret = k.compareTo(current.key); if (ret < 0) current = current.left; else if (ret > 0) current = current.right; else{ delEntry = current; break; } } }else { for (;current != null;){ ret = comparator.compare(current.key, (K) key); if (ret < 0) current = current.left; else if (ret > 0) current = current.right; else{ delEntry = current; break; } } } return delEntry; } //node表示待修正的節點,即后繼節點的子節點(因為后繼節點被挪到刪除節點的位置去了) private void fixUpAfterRemove(Entry<K, V> node,Entry<K,V> parent) { Entry<K,V> other; while((node == null || isBlack(node)) && (node != root)) { if(parent.left == node) { //node是左子節點,下面else與這里的剛好相反 other = parent.right; //node的兄弟節點 if(isRed(other)) { //case1: node的兄弟節點other是紅色的 setBlack(other); setRed(parent); leftRotate(parent); other = parent.right; } //case2: node的兄弟節點other是黑色的,且other的兩個子節點也都是黑色的 if((other.left == null || isBlack(other.left)) && (other.right == null || isBlack(other.right))) { setRed(other); node = parent; parent = parentOf(node); } else { //case3: node的兄弟節點other是黑色的,且other的左子節點是紅色,右子節點是黑色 if(other.right == null || isBlack(other.right)) { setBlack(other.left); setRed(other); rightRotate(other); other = parent.right; } //case4: node的兄弟節點other是黑色的,且other的右子節點是紅色,左子節點任意顏色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.right); leftRotate(parent); node = this.root; break; } } else { //與上面的對稱 other = parent.left; if (isRed(other)) { // Case 1: node的兄弟other是紅色的 setBlack(other); setRed(parent); rightRotate(parent); other = parent.left; } if ((other.left==null || isBlack(other.left)) && (other.right==null || isBlack(other.right))) { // Case 2: node的兄弟other是黑色,且other的倆個子節點都是黑色的 setRed(other); node = parent; parent = parentOf(node); } else { if (other.left==null || isBlack(other.left)) { // Case 3: node的兄弟other是黑色的,並且other的左子節點是紅色,右子節點為黑色。 setBlack(other.right); setRed(other); leftRotate(other); other = parent.left; } // Case 4: node的兄弟other是黑色的;並且other的左子節點是紅色的,右子節點任意顏色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.left); rightRotate(parent); node = this.root; break; } } } if (node!=null) setBlack(node); } private Entry<K, V> successor(Entry<K, V> delEntry) { Entry<K,V> r = delEntry.right;//assert r != null; while (r.left != null){ r = r.left; } return r; } List<V> values(){ List<V> set = new ArrayList<>(size+4); midIterator(root,set); return set; } private void midIterator(Entry<K,V> e, List<V> values){ if (e != null){ midIterator(e.left,values); values.add(e.value); midIterator(e.right,values); } } public void clear(){ clear(root); root = null; } private void clear(Entry<K,V> node) { if (node != null){ clear(node.left); node.left = null; clear(node.right); node.right = null; } } public int size(){return size;} static final class Entry<K,V>{ private K key; private V value; private Entry<K,V> left; private Entry<K,V> right; private Entry<K,V> parent; private boolean color = BLACK; Entry(K key,V value,Entry<K,V> parent){ this.key = key; this.value = value; this.parent = parent; } public K getKey() { return key; } public V getValue() { return value; } } }
到此,紅黑樹的添加刪除操作已經全部講完了,如果文中有什么錯誤或不懂得地方,隨時歡迎大家指出討論。大家也可以關注公眾號:【Java解憂雜貨鋪】,里面會不定時得發布一些技術干活,和學習視頻。