他一口气写出了这7k字的红黑树总结!看过的都说好!!|文末送书
? ? ? ? ?△Hollis, 一個對Coding有著獨特追求的人△
這是Hollis的第?244篇原創分享
作者 l shanhm1991
來源 l Hollis(ID:hollischuang)
紅黑樹是一種很經典的數據結構,它可以在O(log n)時間內做查找,插入和刪除。所以倍受關注。但是一直以來很多Java程序員對他都不是很重視,直到在JDK 1.8中,HashMap會將其鏈表轉換成紅黑樹,此后,很多人就開始重新學習紅黑樹的有關知識。
作者在學習紅黑樹時,查閱了很多資料都沒有找到解釋的特別清楚的,于是自己總結了這一篇文章,總字數近7k,而且繪制了很多圖,希望可以讓大家更容易理解。
1. 定義
紅黑樹是Avl樹的一個變種,它也是在二叉查找樹的基礎上添加平衡條件,只是它對平衡條件的描述不像AVl樹那樣直接,而是轉化成對節點顏色規則的描述。
顏色規則:?
對于任意節點,要么是紅色,要么是黑色;
根節點是黑色的;
如果一個節點是紅色的,那么它的子節點必須是黑色的(即不能有兩個連續的紅色節點);
任意節點到其下面各個空結點(后面稱為nil節點,并約定其顏色為黑色)的路徑上都包含相同數目的黑色節點(稱為黑高);
通過對任何一條從根到空節點的路徑上各個結點的顏色進行約束,紅黑樹可以確保沒有一條路徑會比其他路徑長出2倍,因而紅黑樹是近似平衡的。
2. 分析實現
為了便于處理紅黑樹代碼中的邊界條件,使用兩個標記節點header和nil來充當哨兵,它們與樹中的普通節點有相同的屬性,顏色設為黑色。
將header的值設為負無窮,因此它的右子節點就是真正的根,在對樹進行遍歷時我們可以用header作為起點。
nil的值可以為任意值,將它作為所有空節點的統一引用, 即讓所有樹葉的左右子節點都指向nil,那么在遍歷時就可以將nil視為結束的標志。
紅黑樹也是一顆查找二叉樹,所以它的基本操作與查找二叉樹一致,區別也在于插入和刪除后的平衡恢復。只是實現平衡時的目標就是滿足它對顏色規則的定義,至于樹的平衡與否已經由規則本身去保證。
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之一。
可以看出,1和2,3和4分別是對稱的兩種情況,區別在于parent與grand的大小關系,其實可以概括為parent的兄弟節點uncle是黑色還是紅色的兩種情況。? ?
1. 如果parent為紅色,uncle為黑色
????以情況1為例(即parent小于grand):如果current小于parent,那么插入之后,可以將parent和grand的顏色互換,然后將grand右旋,這樣就能保證原來以grand為根的這個子樹的黑高保持不變,也就保證了整棵樹不被破壞。
如果current大于parent,則可以先將parent左旋,然后將current視為parent,再與上面進行相同的操作,即可恢復平衡。
2. 如果parent為紅色,uncle為紅色
????這時無論怎么旋轉都已經解決不了問題,不過可以知道,在插入之前grand到下面各個nil的黑節點數為1,因此,只要保證grand的黑高仍然為1就行, 那么,通過將grand與其左右子節點的顏色進行翻轉便可達到目標。
以情況3為例:
但是,翻轉會使grand變成紅色,這時如果曾祖父也是紅色,那么將違反了規則3。因此可能需要朝著樹根的方向進行逐層分析,直到不再有兩個相連的紅色節點或者到達樹根為止,但這顯然會很麻煩。?
事實上,由于顏色翻轉的等價性,我們可以在查找插入節點位置的過程中,通過顏色翻轉來避免掉uncle為紅色的情況,即將顏色翻轉的事情提前做了。
在自上而下的查找過程中,如果看到一個節點X的兩個子節點都為紅色,可以使X變成紅色,而兩個子節點變成黑色(如果X是根,則可以在翻轉之后再將其置為黑色),結果依然滿足紅黑樹的規則。
只有當X的parent為紅色時,才會破壞紅黑樹的規則。不過這時可以斷定X的兄弟節點,X的grand,以及X的uncle都是黑色的(因為如果X的parent和uncle都為紅色, 那么一定會在自上而下的查找和翻轉過程中被過濾掉)。
那么,可以將X視為current,然后與上面parent為紅色,uncle為黑色的情形進行相同的處理。下面通過畫圖來說明一種情形(對稱情形類似處理):
先對X與其子節點進行顏色翻轉
然后,將X視為之前的current,對parent和grand進行換色和旋轉
完成之后,將新的根(即原本grand的位置)視為新的current,然后重新向下遍歷。由于X的孫子節點之前一定為黑色,而X的兒子節點在翻轉之后也為黑色,因此在調整之后, 可以保證在X的下面將有連續兩層不會出現紅節點。
所以,盡管旋轉調整會使得gand的位置變得錯誤,但在接下來的遍歷中,又會立即將grand-parent-current恢復成正確的位置關系。
總結節點插入問題:首先根據二叉查找樹的性質,可以知道插入節點就是添加樹葉,其次對于紅黑樹而言,插入紅色節點不會影響路徑上的黑節點數。
因此, 我們將插入的節點視為紅色的樹葉,這時只要考慮插入的紅色樹葉的parent是紅色還是黑色,如果是黑色問題直接解決,如果是紅色,再看uncle是紅色還是黑色。如果uncle是黑色,可以通過變色旋轉解決;如果是紅色,那么通過顏色翻轉也能達到目的,但這會使grand變成紅色,需要進一步考慮曾祖父的顏色問題,將問題放大了。于是考慮在一開始向下的遍歷過程中, 提前翻轉顏色來避免掉parent和unlce同時為紅色的情況。
因此,最終在插入節點時,實際需要考慮旋轉調整的情形只有一種,即parent為紅色,uncle為黑色。
2.2. 刪除
對于節點刪除,大概思路也是先將問題轉化成刪除樹葉來簡化問題,如果是紅色樹葉,則可以直接刪除,如果是黑色樹葉,則再分情形進行討論。
先看下如何將刪除目標轉化成對樹葉的刪除,根據二叉查找樹的性質,在刪除節點時可以用右子樹的最小節點或左子樹的最大節點來替換要刪除的節點,然后刪除子樹中用來替換的那個節點。
不過在紅黑樹中,為了保證節點的顏色規則,替換時將只進行值的替換。其實,根據紅黑樹的規則可以得出一個簡單的推論:如果一個樹葉沒有兄弟節點,那么它一定為紅色,或者說如果一個樹葉為黑色, 則它必然有兄弟節點。
另外,還可以斷定:對于任意節點,其右子樹的最小(即最左)節點最多存在一個紅色右子節點,其左子樹的最大(即最右)節點最多存在一個紅色左子節點。因此,當左子樹的最大節點或右子樹的最小節點不是樹葉時,可以繼續用同樣的思路來轉換要刪除的目標,并且這時可以斷定最后刪除的樹葉一定是紅色。
再來討論刪除黑色樹葉的情形,由于黑色樹葉必然會存在兄弟節點(樹根除外),因此在下面的討論中首先假設要刪除的黑色樹葉總是在左邊(如果在右邊則對稱處理), 這時可以根據其兄弟節點brother和父親節點parent的顏色來將刪除情形分為3種。下面分別進行畫圖分析(圖中省略掉空節點nil):
1.parent為紅色,brother為黑色
由于current是樹葉,那么可以斷定brother下面不會再有黑色的后代節點,至多只能有紅色的兒子節點,否則parent在刪除前將違反規則4。
??? 1.1 先看brother沒有兒子的情況,此時看上去比較簡單,直接對parent進行左旋即可解決問題,能夠保證在刪除調整前后以原parent為根的子樹的黑高不變。
??? 1.2 但是,如果brother有一個紅色左兒子(右兒子沒有影響,因此忽略),那么直接對parent進行左旋,將會造成兩個連續的紅色的節點而違反規則3, 這時需要先將brother右旋,然后再對parent進行換色并左旋來解決。
因此,需要根據brother是否有紅色左兒子將情形1再分為2種子情形來進行處理。
2.parent為黑色,brother為紅色
同樣由于current是樹葉,可以斷定brother的兩個黑色的兒子節點下面至多只能有一層紅色子節點,下面根據brother左兒子的子節點情況(右兒子的子節點沒有影響,因此忽略)來分情形討論。
??? 2.1 還是先看brother的左兒子沒有子節點的情況,這時可以將brother與其左兒子進行換色,然后將parent進行左旋。
??? 2.2 如果brother的左兒子有一個紅色右子節點,這時需要先將brother左兒子的右子節點涂成黑色,并將brother右旋,然后再將parent左旋。
??? 2.3 如果brother的左兒子沒有紅色右子節點,但有一個紅色左子節點。這時可以先將brother左兒子的左子節點涂成黑色,并將brother的左兒子右旋,這樣將情形轉化成2.2, 然后依次將brother右旋,parent左旋。
因此,需要根據brother左兒子的子節點情況將情形2再分為3種子情形來處理。
3.parent為黑色,brother為黑色
還是由于current是樹葉,可以斷定brother下面至多只能有一層紅色的兒子節點,否則parent在刪除前就違反了規則4。
??? 3.1 還是先看brother沒有兒子的情況,此時通過parent為根的子樹自身已經解決不了問題,想法是將brother變成紅色來降低parent子樹的黑高,然后將parent子樹整個視為一個current進行處理, 下面會再詳細說明。
??? 3.2 如果brother有一個紅色右兒子,這時可以將brother的右兒子變成黑色,然后將parent左旋。
??? 3.3 如果brother沒有紅色右兒子,但是存在一個紅色左兒子,這時可以將brother的左兒子變成黑色,然后將brother右旋,再將parent左旋。
因此,需要根據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的相對黑高保持不變,如圖:
對于1.2,如果current子樹黑高降1,與之前一樣,先將brother右旋,然后將parent變成黑色并左旋,也能保證x到a,b,c,d,e的相對黑高保持不變,如圖:
對于2.1,如果current子樹黑高降1,與之前一樣,將brother與左兒子換色,然后對parent左旋,可以保證x到a,b,c,d,e,f,g,h的相對黑高保持不變,如圖:
對于2.2,如果current子樹黑高降1,與之前一樣,將brother的左兒子的右子節點涂成黑色,然后對brother右旋,再對parent左旋,也可以保證x到a,b,c,d,e,f,g,h的相對黑高保持不變, 如圖(A涂成灰色表示其顏色可以紅或者黑,沒有影響):
對于2.3,如果current子樹黑高降1,與之前一樣,先將brother的左兒子的左子節點涂成黑色,并對將brother的左兒子右旋,這樣將情形轉化成2.2,然后再進行同樣的旋轉調整, 以保證x到a,b,c,d,e,f,g,h的相對黑高保持不變,如圖:
對于3.1,如果current子樹黑高降1,與之前一樣,依然只能降低brother的黑高,使x到a,b,c,d,e,f的相對黑高同時降1,然后將parent視為新的current子樹繼續向樹根追溯,如圖:
對于3.2,如果current子樹黑高降1,與之前一樣,將brother的右兒子涂黑,然后對parent左旋,也能保證x到a,b,c,d,e,f的相對黑高保持不變, 如圖(圖中將left涂成灰色表示其顏色可以紅或者黑,沒有影響):
對于3.3,如果current子樹黑高降1,與之前一樣,先將brother的左兒子涂黑,然后對parent右旋,再對parent左旋,也能保證x到a,b,c,d,e,f的相對黑高保持不變,如圖:
總結節點刪除問題:首先將問題轉化為對樹葉的刪除,如果樹葉是紅色則直接刪除,如果是黑色,則根據兄弟節點和父節點的顏色情況來對情形進行分類。想法是盡量在子樹中解決, 如果實在解決不了,就降低子樹的高度,并將子樹視為整體然后向樹根進行追溯,直到問題解決。
上面的分析中,對于刪除黑色樹葉的刪除,分成了8種情形,其中3.1需要向樹根追溯, 直到轉化成其它7種情形或到樹根為止才能解決。其次,2.3也需要轉化成2.2來解決,因此,對于黑色樹葉的刪除,實際上最后直接解決的情形可以歸結有6種。
3. java實現
繼承之前二叉查找樹的實現,重寫下插入和刪除,分別實現Set和MultiTreeMap
3.1. Set
public class RBTreeSet<E extends Comparable<? super E>> extends BinarySearchTreeSet<E> {private RBNode<E> header;private RBNode<E> nil;public RBTreeSet() {nil = new RBNode<>(null);nil.left = nil;nil.right = nil;header = new RBNode<>(null, nil, nil);}@Overrideprotected Node<E> getRoot() {return header.right;}@Overrideprotected boolean isEmptyNode(Node<E> node) {return nil == node;}@Overridepublic void clear() {header.right = nil;size = 0;}@Overridepublic boolean add(E e) {RBNode<E> current = header;RBNode<E> parent = header;//先將nil的值設為enil.element = e;//然后從header開始自上而下尋找值為e的節點awhile(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,并將其左右子樹指向nilcurrent = 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<E> getLeft(Node<E> node){return (RBNode<E>)node.left;}private RBNode<E> getRight(Node<E> node){return (RBNode<E>)node.right;}private int compare(E e, Node<E> node) {if(node == header){return 1;}else{return e.compareTo(node.element);}}private RBNode<E> handleReorient(RBNode<E> current) {getLeft(current).red = false; getRight(current).red = false; current.red = true;RBNode<E> subRoot = current;//翻轉之后發現parent也是紅色,進行換色和旋轉RBNode<E> parent = current.parent;if(parent.red){RBNode<E> 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);}}//旋轉grandif(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<E> node) {RBNode<E> parent = node.parent;RBNode<E> left = getLeft(node); RBNode<E> 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<E> node) {RBNode<E> parent = node.parent;RBNode<E> right = getRight(node); RBNode<E> 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;}}@Overridepublic boolean remove(Object o) {RBNode<E> current = header;RBNode<E> 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<E> 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);}//刪除樹葉leafif(current == parent.left){parent.left = nil;}else{parent.right = nil;}return true;}private void fixRemove(RBNode<E> current){RBNode<E> parent = current.parent;if(parent == header){return;}if(current == parent.left){RBNode<E> 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<E> 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<E> extends Node<E> {RBNode<E> parent;boolean red;RBNode(E e) {super(e);}RBNode(E e, RBNode<E> left, RBNode<E> right) {super(e);this.left = left;this.right = right;}@Overridepublic String toString() {return red ? element.toString() + "(red)" : element.toString();}} }?
3.2. MultiTreeMap
public class MultiRBTreeMap<K extends Comparable<? super K>, V> extends MultiBinarySearchTreeMap<K, V> {public MultiRBTreeMap() {}public MultiRBTreeMap(boolean deduplication, boolean asc){ super(deduplication, asc);}@Overrideprotected void init() { nil = new RBNode<K, V>(null, null, false, null, null); nil.left = nil; nil.right = nil;header = new RBNode<K, V>(null, null, false, nil, nil);header.next = nil; nil.prev = header;}public V put(K key, V value){ nil.key = key; RBNode<K, V> current = (RBNode<K, V>)header; RBNode<K, V> parent = (RBNode<K, V>)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<K, V> handleReorient(RBNode<K, V> current) {getLeft(current).isRed = false; getRight(current).isRed = false; current.isRed = true;RBNode<K, V> subRoot = current; RBNode<K, V> parent = getParent(current); if(parent.isRed){ RBNode<K, V> 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<K, V> node) { Node<K, V> parent = node.parent; Node<K, V> left = node.left; Node<K, V> 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<K, V> node) { Node<K, V> parent = node.parent; Node<K, V> right = node.right; Node<K, V> 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<K, V> node) { if(node == header){ return 1;}else{ return key.compareTo(node.getKey());}}private RBNode<K, V> getParent(Node<K, V> node){ return (RBNode<K, V>)node.parent;}private RBNode<K, V> getNext(Node<K, V> node){ return (RBNode<K, V>)node.next;}private RBNode<K, V> getLeft(Node<K, V> node){ return (RBNode<K, V>)node.left;}private RBNode<K, V> getRight(Node<K, V> node){ return (RBNode<K, V>)node.right;}@Overrideprotected void remove(Node<K, V> node){ RBNode<K, V> rbnode = (RBNode<K, V>)node; RBNode<K, V> current = rbnode; RBNode<K, V> 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 redparent = 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 redparent = 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<K, V> node, RBNode<K, V> current){ RBNode<K, V> parent = getParent(node); RBNode<K, V> left = getLeft(node); RBNode<K, V> 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<K, V> node){ RBNode<K, V> parent = getParent(node); if(parent == header){ return;}if(node == parent.left){parent.left = nil; RBNode<K, V> 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<K, V> 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<K, V> extends Node<K, V> {boolean isRed;public RBNode(K key, V value) { super(key, value);}RBNode(K key, V value, boolean isRepeat, Node<K, V> left, Node<K, V> right) { super(key, value, isRepeat, left, right);}@Override public String toString() { String str = super.toString(); return isRed ? str + "(red)" : str;}} }3.3. MultiTreeSet
有了Map之后,就可以很容易的依賴它實現一個性質相似的Set:
public class MultiRBTreeSet<E extends Comparable<? super E>> implements Set<E>{private static final Object V = new Object();private MultiRBTreeMap<E, Object> 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<E> iterator() { return map.keySet().iterator();}public Iterator<E> 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> T[] toArray(T[] a) { // TODO Auto-generated method stub throw new UnsupportedOperationException();} }?
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的原因。而在紅黑樹中,則可以將調整問題縮小在父節點為根的子樹中, 這些問題都可以解決,代價只是稍微犧牲了一些平衡性。
參閱:? ??
《算法導論》 ? ?
《數據結構與算法分析》
本文來自作者投稿,原作者:shanhm1991,他目前正在連載數據結構系列文章,原創不易,歡迎轉發支持。
福利時間
GIFT TIME
相信大家一定看過一本關于Java虛擬機的書——《深入理解Java虛擬機》,這本書自2011年上市以來,前兩個版本累計印刷36次,銷量超過30萬冊。
最近剛剛出了第三版,我第一時間找到機械工業出版社給大家申請了一些,作為福利送給大家。
本次送書不需要評論,也不需要轉發,采用最公平的抽獎方式。
Hollis創建了一個適合5 年以內的Java開發者的公眾號,主要定位于深入講解Java的原理知識——Java之道
大家可以掃描以上二維碼,關注后在對話回復:JVM。即可參與抽獎。中獎者即可獲得《深入理解Java虛擬機(第三版)》一本。開獎時間:2019-12-26 10:00
- MORE | 更多精彩文章 -
一條來自面試官的忠告:一定要非常熟悉你負責的業務
微服務必備的調用鏈分析,2w字長文解析技術細節!
CTO被裁,離職前給組了的高級開發們8個建議。
刪庫不必跑路,談數據庫刪除設計
如果你喜歡本文,
請長按二維碼,關注?Hollis.
轉發至朋友圈,是對我最大的支持。
好文章,我在看??
總結
以上是生活随笔為你收集整理的他一口气写出了这7k字的红黑树总结!看过的都说好!!|文末送书的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hdu 1203 I NEED
- 下一篇: 经典背包问题 01背包+完全