紅黑樹是平衡二叉查找樹的一種。為了深入理解紅黑樹,我們需要從二叉查找樹開始講起隶垮。
BST
二叉查找樹(Binary Search Tree,簡稱BST)是一棵二叉樹秘噪,它的左子節(jié)點(diǎn)的值比父節(jié)點(diǎn)的值要小狸吞,右節(jié)點(diǎn)的值要比父節(jié)點(diǎn)的值大。它的高度決定了它的查找效率指煎。
在理想的情況下蹋偏,二叉查找樹增刪查改的時(shí)間復(fù)雜度為O(logN)(其中N為節(jié)點(diǎn)數(shù)),最壞的情況下為O(N)至壤。當(dāng)它的高度為logN+1時(shí)威始,我們就說二叉查找樹是平衡的。BST的查找操作
T key = a search key
Node root = point to the root of a BST
while(true){
if(root==null){
break;
}
if(root.value.equals(key)){
return root;
}
else if(key.compareTo(root.value)<0){
root = root.left;
}
else{
root = root.right;
}
}
return null;
從程序中可以看出像街,當(dāng)BST查找的時(shí)候黎棠,先與當(dāng)前節(jié)點(diǎn)進(jìn)行比較:
- 如果相等的話就返回當(dāng)前節(jié)點(diǎn);
- 如果少于當(dāng)前節(jié)點(diǎn)則繼續(xù)查找當(dāng)前節(jié)點(diǎn)的左節(jié)點(diǎn)宅广;
- 如果大于當(dāng)前節(jié)點(diǎn)則繼續(xù)查找當(dāng)前節(jié)點(diǎn)的右節(jié)點(diǎn)葫掉。
直到當(dāng)前節(jié)點(diǎn)指針為空或者查找到對(duì)應(yīng)的節(jié)點(diǎn),程序查找結(jié)束跟狱。
BST的插入操作
Node node = create a new node with specify value
Node root = point the root node of a BST
Node parent = null;
//find the parent node to append the new node
while(true){
if(root==null)break;
parent = root;
if(node.value.compareTo(root.value)<=0){
root = root.left;
}else{
root = root.right;
}
}
if(parent!=null){
if(node.value.compareTo(parent.value)<=0){//append to left
parent.left = node;
}else{//append to right
parent.right = node;
}
}
插入操作先通過循環(huán)查找到待插入的節(jié)點(diǎn)的父節(jié)點(diǎn)俭厚,和查找父節(jié)點(diǎn)的邏輯一樣,都是比大小驶臊,小的往左挪挤,大的往右叼丑。找到父節(jié)點(diǎn)后,對(duì)比父節(jié)點(diǎn)扛门,小的就插入到父節(jié)點(diǎn)的左節(jié)點(diǎn)鸠信,大就插入到父節(jié)點(diǎn)的右節(jié)點(diǎn)上。
BST的刪除操作
刪除操作的步驟如下:
- 查找到要?jiǎng)h除的節(jié)點(diǎn)论寨。
- 如果待刪除的節(jié)點(diǎn)是葉子節(jié)點(diǎn)星立,則直接刪除。
- 如果待刪除的節(jié)點(diǎn)不是葉子節(jié)點(diǎn)葬凳,則先找到待刪除節(jié)點(diǎn)的中序遍歷的后繼- 節(jié)點(diǎn)绰垂,用該后繼節(jié)點(diǎn)的值替換待刪除的節(jié)點(diǎn)的值,然后刪除后繼節(jié)點(diǎn)火焰。
BST存在的問題
BST存在的主要問題是劲装,數(shù)在插入的時(shí)候會(huì)導(dǎo)致樹傾斜,不同的插入順序會(huì)導(dǎo)致樹的高度不一樣昌简,而樹的高度直接的影響了樹的查找效率占业。理想的高度是logN,最壞的情況是所有的節(jié)點(diǎn)都在一條斜線上纯赎,這樣的樹的高度為N谦疾。
RBTree
基于BST存在的問題,一種新的樹——平衡二叉查找樹(Balanced BST)產(chǎn)生了址否。平衡樹在插入和刪除的時(shí)候餐蔬,會(huì)通過旋轉(zhuǎn)操作將高度保持在logN。其中兩款具有代表性的平衡樹分別為AVL樹和紅黑樹佑附。AVL樹由于實(shí)現(xiàn)比較復(fù)雜樊诺,而且插入和刪除性能差,在實(shí)際環(huán)境下的應(yīng)用不如紅黑樹音同。
紅黑樹(Red-Black Tree词爬,以下簡稱RBTree)的實(shí)際應(yīng)用非常廣泛,比如Linux內(nèi)核中的完全公平調(diào)度器权均、高精度計(jì)時(shí)器顿膨、ext3文件系統(tǒng)等等,各種語言的函數(shù)庫如Java的TreeMap和TreeSet叽赊,C++ STL的map恋沃、multimap、multiset等必指。
RBTree也是函數(shù)式語言中最常用的持久數(shù)據(jù)結(jié)構(gòu)之一囊咏,在計(jì)算幾何中也有重要作用。值得一提的是,Java 8中HashMap的實(shí)現(xiàn)也因?yàn)橛肦BTree取代鏈表梅割,性能有所提升霜第。
RBTree的定義
RBTree的定義如下:
- 任何一個(gè)節(jié)點(diǎn)都有顏色,黑色或者紅色
- 根節(jié)點(diǎn)是黑色的
- 父子節(jié)點(diǎn)之間不能出現(xiàn)兩個(gè)連續(xù)的紅節(jié)點(diǎn)
- 任何一個(gè)節(jié)點(diǎn)向下遍歷到其子孫的葉子節(jié)點(diǎn)户辞,所經(jīng)過的黑節(jié)點(diǎn)個(gè)數(shù)必須相等
- 空節(jié)點(diǎn)被認(rèn)為是黑色的
數(shù)據(jù)結(jié)構(gòu)表示如下:
class Node<T>{
public T value;
public Node<T> parent;
public boolean isRed;
public Node<T> left;
public Node<T> right;
}
RBTree在理論上還是一棵BST樹泌类,但是它在對(duì)BST的插入和刪除操作時(shí)會(huì)維持樹的平衡,即保證樹的高度在[logN,logN+1](理論上底燎,極端的情況下可以出現(xiàn)RBTree的高度達(dá)到2*logN刃榨,但實(shí)際上很難遇到)。這樣RBTree的查找時(shí)間復(fù)雜度始終保持在O(logN)從而接近于理想的BST书蚪。RBTree的刪除和插入操作的時(shí)間復(fù)雜度也是O(logN)喇澡。RBTree的查找操作就是BST的查找操作。
RBTree的旋轉(zhuǎn)操作
旋轉(zhuǎn)操作(Rotate)的目的是使節(jié)點(diǎn)顏色符合定義殊校,讓RBTree的高度達(dá)到平衡。Rotate分為left-rotate(左旋)和right-rotate(右旋)读存,區(qū)分左旋和右旋的方法是:待旋轉(zhuǎn)的節(jié)點(diǎn)從左邊上升到父節(jié)點(diǎn)就是右旋为流,待旋轉(zhuǎn)的節(jié)點(diǎn)從右邊上升到父節(jié)點(diǎn)就是左旋。
RBTree的查找操作
RBTree的查找操作和BST的查找操作是一樣的让簿。請(qǐng)參考BST的查找操作代碼敬察。
RBTree的插入操作
RBTree的插入與BST的插入方式是一致的,只不過是在插入過后尔当,可能會(huì)導(dǎo)致樹的不平衡莲祸,這時(shí)就需要對(duì)樹進(jìn)行旋轉(zhuǎn)操作和顏色修復(fù)(在這里簡稱插入修復(fù)),使得它符合RBTree的定義椭迎。
新插入的節(jié)點(diǎn)是紅色的锐帜,插入修復(fù)操作如果遇到父節(jié)點(diǎn)的顏色為黑則修復(fù)操作結(jié)束。也就是說畜号,只有在父節(jié)點(diǎn)為紅色節(jié)點(diǎn)的時(shí)候是需要插入修復(fù)操作的缴阎。
插入修復(fù)操作分為以下的三種情況,而且新插入的節(jié)點(diǎn)的父節(jié)點(diǎn)都是紅色的:
叔叔節(jié)點(diǎn)也為紅色简软。
叔叔節(jié)點(diǎn)為空蛮拔,且祖父節(jié)點(diǎn)、父節(jié)點(diǎn)和新節(jié)點(diǎn)處于一條斜線上痹升。
叔叔節(jié)點(diǎn)為空建炫,且祖父節(jié)點(diǎn)、父節(jié)點(diǎn)和新節(jié)點(diǎn)不處于一條斜線上疼蛾。
插入操作-case 1
插入操作-case 2
case 2的操作是將B節(jié)點(diǎn)進(jìn)行右旋操作,并且和父節(jié)點(diǎn)A互換顏色眷柔。通過該修復(fù)操作RBTRee的高度和顏色都符合紅黑樹的定義期虾。如果B和C節(jié)點(diǎn)都是右節(jié)點(diǎn)的話,只要將操作變成左旋就可以了驯嘱。
插入操作-case 3
case 3的操作是將C節(jié)點(diǎn)進(jìn)行左旋镶苞,這樣就從case 3轉(zhuǎn)換成case 2了,然后針對(duì)case 2進(jìn)行操作處理就行了鞠评。case 2操作做了一個(gè)右旋操作和顏色互換來達(dá)到目的茂蚓。如果樹的結(jié)構(gòu)是下圖的鏡像結(jié)構(gòu),則只需要將對(duì)應(yīng)的左旋變成右旋剃幌,右旋變成左旋即可聋涨。
插入操作的總結(jié)
插入后的修復(fù)操作是一個(gè)向root節(jié)點(diǎn)回溯的操作,一旦牽涉的節(jié)點(diǎn)都符合了紅黑樹的定義负乡,修復(fù)操作結(jié)束牍白。之所以會(huì)向上回溯是由于case 1操作會(huì)將父節(jié)點(diǎn),叔叔節(jié)點(diǎn)和祖父節(jié)點(diǎn)進(jìn)行換顏色抖棘,有可能會(huì)導(dǎo)致祖父節(jié)點(diǎn)不平衡(紅黑樹定義3)茂腥。這個(gè)時(shí)候需要對(duì)祖父節(jié)點(diǎn)為起點(diǎn)進(jìn)行調(diào)節(jié)(向上回溯)。
祖父節(jié)點(diǎn)調(diào)節(jié)后如果還是遇到它的祖父顏色問題切省,操作就會(huì)繼續(xù)向上回溯最岗,直到root節(jié)點(diǎn)為止,根據(jù)定義root節(jié)點(diǎn)永遠(yuǎn)是黑色的数尿。在向上的追溯的過程中仑性,針對(duì)插入的3中情況進(jìn)行調(diào)節(jié)。直到符合紅黑樹的定義為止右蹦。直到牽涉的節(jié)點(diǎn)都符合了紅黑樹的定義诊杆,修復(fù)操作結(jié)束。
如果上面的3中情況如果對(duì)應(yīng)的操作是在右子樹上何陆,做對(duì)應(yīng)的鏡像操作就是了晨汹。
RBTree的刪除操作
刪除操作首先需要做的也是BST的刪除操作,刪除操作會(huì)刪除對(duì)應(yīng)的節(jié)點(diǎn)贷盲,如果是葉子節(jié)點(diǎn)就直接刪除淘这,如果是非葉子節(jié)點(diǎn)剥扣,會(huì)用對(duì)應(yīng)的中序遍歷的后繼節(jié)點(diǎn)來頂替要?jiǎng)h除節(jié)點(diǎn)的位置。刪除后就需要做刪除修復(fù)操作铝穷,使的樹符合紅黑樹的定義钠怯,符合定義的紅黑樹高度是平衡的。
刪除修復(fù)操作在遇到被刪除的節(jié)點(diǎn)是紅色節(jié)點(diǎn)或者到達(dá)root節(jié)點(diǎn)時(shí)曙聂,修復(fù)操作完畢晦炊。
刪除修復(fù)操作是針對(duì)刪除黑色節(jié)點(diǎn)才有的,當(dāng)黑色節(jié)點(diǎn)被刪除后會(huì)讓整個(gè)樹不符合RBTree的定義的第四條宁脊。需要做的處理是從兄弟節(jié)點(diǎn)上借調(diào)黑色的節(jié)點(diǎn)過來断国,如果兄弟節(jié)點(diǎn)沒有黑節(jié)點(diǎn)可以借調(diào)的話,就只能往上追溯榆苞,將每一級(jí)的黑節(jié)點(diǎn)數(shù)減去一個(gè)稳衬,使得整棵樹符合紅黑樹的定義。
刪除操作的總體思想是從兄弟節(jié)點(diǎn)借調(diào)黑色節(jié)點(diǎn)使樹保持局部的平衡坐漏,如果局部的平衡達(dá)到了薄疚,就看整體的樹是否是平衡的,如果不平衡就接著向上追溯調(diào)整仙畦。
刪除修復(fù)操作分為四種情況(刪除黑節(jié)點(diǎn)后):
待刪除的節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是紅色的節(jié)點(diǎn)输涕。
待刪除的節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色的節(jié)點(diǎn),且兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色的慨畸。
待調(diào)整的節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色的節(jié)點(diǎn),且兄弟節(jié)點(diǎn)的左子節(jié)點(diǎn)是紅色的衣式,右節(jié)點(diǎn)是黑色的(兄弟節(jié)點(diǎn)在右邊)寸士,如果兄弟節(jié)點(diǎn)在左邊的話,就是兄弟節(jié)點(diǎn)的右子節(jié)點(diǎn)是紅色的碴卧,左節(jié)點(diǎn)是黑色的弱卡。
待調(diào)整的節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色的節(jié)點(diǎn),且右子節(jié)點(diǎn)是是紅色的(兄弟節(jié)點(diǎn)在右邊)住册,如果兄弟節(jié)點(diǎn)在左邊婶博,則就是對(duì)應(yīng)的就是左節(jié)點(diǎn)是紅色的。
刪除操作-case 1
由于兄弟節(jié)點(diǎn)是紅色節(jié)點(diǎn)的時(shí)候荧飞,無法借調(diào)黑節(jié)點(diǎn)凡人,所以需要將兄弟節(jié)點(diǎn)提升到父節(jié)點(diǎn),由于兄弟節(jié)點(diǎn)是紅色的叹阔,根據(jù)RBTree的定義挠轴,兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)是黑色的,就可以從它的子節(jié)點(diǎn)借調(diào)了耳幢。
case 1這樣轉(zhuǎn)換之后就會(huì)變成后面的case 2岸晦,case 3,或者case 4進(jìn)行處理了。上升操作需要對(duì)C做一個(gè)左旋操作启上,如果是鏡像結(jié)構(gòu)的樹只需要做對(duì)應(yīng)的右旋操作即可邢隧。
之所以要做case 1操作是因?yàn)樾值芄?jié)點(diǎn)是紅色的,無法借到一個(gè)黑節(jié)點(diǎn)來填補(bǔ)刪除的黑節(jié)點(diǎn)冈在。
刪除操作-case 2
case 2的刪除操作是由于兄弟節(jié)點(diǎn)可以消除一個(gè)黑色節(jié)點(diǎn)倒慧,因?yàn)樾值芄?jié)點(diǎn)和兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色的,所以可以將兄弟節(jié)點(diǎn)變紅讥邻,這樣就可以保證樹的局部的顏色符合定義了迫靖。這個(gè)時(shí)候需要將父節(jié)點(diǎn)A變成新的節(jié)點(diǎn),繼續(xù)向上調(diào)整兴使,直到整顆樹的顏色符合RBTree的定義為止系宜。
case 2這種情況下之所以要將兄弟節(jié)點(diǎn)變紅,是因?yàn)槿绻研值芄?jié)點(diǎn)借調(diào)過來发魄,會(huì)導(dǎo)致兄弟的結(jié)構(gòu)不符合RBTree的定義盹牧,這樣的情況下只能是將兄弟節(jié)點(diǎn)也變成紅色來達(dá)到顏色的平衡。當(dāng)將兄弟節(jié)點(diǎn)也變紅之后励幼,達(dá)到了局部的平衡了汰寓,但是對(duì)于祖父節(jié)點(diǎn)來說是不符合定義4的。這樣就需要回溯到父節(jié)點(diǎn)苹粟,接著進(jìn)行修復(fù)操作有滑。
刪除操作-case 3
case 3的刪除操作是一個(gè)中間步驟,它的目的是將左邊的紅色節(jié)點(diǎn)借調(diào)過來嵌削,這樣就可以轉(zhuǎn)換成case 4狀態(tài)了毛好,在case 4狀態(tài)下可以將D,E節(jié)點(diǎn)都階段過來苛秕,通過將兩個(gè)節(jié)點(diǎn)變成黑色來保證紅黑樹的整體平衡肌访。
之所以說case-3是一個(gè)中間狀態(tài),是因?yàn)楦鶕?jù)紅黑樹的定義來說艇劫,下圖并不是平衡的吼驶,他是通過case 2操作完后向上回溯出現(xiàn)的狀態(tài)。之所以會(huì)出現(xiàn)case 3和后面的case 4的情況店煞,是因?yàn)榭梢酝ㄟ^借用侄子節(jié)點(diǎn)的紅色蟹演,變成黑色來符合紅黑樹定義4.
刪除操作-case 4
Case 4的操作是真正的節(jié)點(diǎn)借調(diào)操作,通過將兄弟節(jié)點(diǎn)以及兄弟節(jié)點(diǎn)的右節(jié)點(diǎn)借調(diào)過來浅缸,并將兄弟節(jié)點(diǎn)的右子節(jié)點(diǎn)變成紅色來達(dá)到借調(diào)兩個(gè)黑節(jié)點(diǎn)的目的轨帜,這樣的話,整棵樹還是符合RBTree的定義的衩椒。
Case 4這種情況的發(fā)生只有在待刪除的節(jié)點(diǎn)的兄弟節(jié)點(diǎn)為黑蚌父,且子節(jié)點(diǎn)不全部為黑哮兰,才有可能借調(diào)到兩個(gè)節(jié)點(diǎn)來做黑節(jié)點(diǎn)使用,從而保持整棵樹都符合紅黑樹的定義苟弛。
刪除操作的總結(jié)
紅黑樹的刪除操作是最復(fù)雜的操作喝滞,復(fù)雜的地方就在于當(dāng)刪除了黑色節(jié)點(diǎn)的時(shí)候,如何從兄弟節(jié)點(diǎn)去借調(diào)節(jié)點(diǎn)膏秫,以保證樹的顏色符合定義右遭。由于紅色的兄弟節(jié)點(diǎn)是沒法借調(diào)出黑節(jié)點(diǎn)的,這樣只能通過選擇操作讓他上升到父節(jié)點(diǎn)缤削,而由于它是紅節(jié)點(diǎn)窘哈,所以它的子節(jié)點(diǎn)就是黑的,可以借調(diào)亭敢。
對(duì)于兄弟節(jié)點(diǎn)是黑色節(jié)點(diǎn)的可以分成3種情況來處理滚婉,當(dāng)所以的兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色節(jié)點(diǎn)時(shí),可以直接將兄弟節(jié)點(diǎn)變紅帅刀,這樣局部的紅黑樹顏色是符合定義的让腹。但是整顆樹不一定是符合紅黑樹定義的,需要往上追溯繼續(xù)調(diào)整扣溺。
對(duì)于兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)為左紅右黑或者 (全部為紅骇窍,右紅左黑)這兩種情況,可以先將前面的情況通過選擇轉(zhuǎn)換為后一種情況锥余,在后一種情況下腹纳,因?yàn)樾值芄?jié)點(diǎn)為黑,兄弟節(jié)點(diǎn)的右節(jié)點(diǎn)為紅驱犹,可以借調(diào)出兩個(gè)節(jié)點(diǎn)出來做黑節(jié)點(diǎn)只估,這樣就可以保證刪除了黑節(jié)點(diǎn),整棵樹還是符合紅黑樹的定義的着绷,因?yàn)楹谏?jié)點(diǎn)的個(gè)數(shù)沒有改變。
紅黑樹的刪除操作是遇到刪除的節(jié)點(diǎn)為紅色锌云,或者追溯調(diào)整到了root節(jié)點(diǎn)荠医,這時(shí)刪除的修復(fù)操作完畢。
RBTree的Java實(shí)現(xiàn)
public class RBTreeNode<T extends Comparable<T>> { private T value;//node value private RBTreeNode<T> left;//left child pointer private RBTreeNode<T> right;//right child pointer private RBTreeNode<T> parent;//parent pointer private boolean red;//color is red or not red public RBTreeNode(){} public RBTreeNode(T value){this.value=value;} public RBTreeNode(T value,boolean isRed){this.value=value;this.red = isRed;} public T getValue() { return value; } void setValue(T value) { this.value = value; } RBTreeNode<T> getLeft() { return left; } void setLeft(RBTreeNode<T> left) { this.left = left; } RBTreeNode<T> getRight() { return right; } void setRight(RBTreeNode<T> right) { this.right = right; } RBTreeNode<T> getParent() { return parent; } void setParent(RBTreeNode<T> parent) { this.parent = parent; } boolean isRed() { return red; } boolean isBlack(){ return !red; } /** * is leaf node / boolean isLeaf(){ return left==null && right==null; } void setRed(boolean red) { this.red = red; } void makeRed(){ red=true; } void makeBlack(){ red=false; } @Override public String toString(){ return value.toString(); }}public class RBTree<T extends Comparable<T>> { private final RBTreeNode<T> root; //node number private java.util.concurrent.atomic.AtomicLong size = new java.util.concurrent.atomic.AtomicLong(0); //in overwrite mode,all node's value can not has same value //in non-overwrite mode,node can have same value, suggest don't use non-overwrite mode. private volatile boolean overrideMode=true; public RBTree(){ this.root = new RBTreeNode<T>(); } public RBTree(boolean overrideMode){ this(); this.overrideMode=overrideMode; } public boolean isOverrideMode() { return overrideMode; } public void setOverrideMode(boolean overrideMode) { this.overrideMode = overrideMode; } / * number of tree number * @return / public long getSize() { return size.get(); } /* * get the root node * @return / private RBTreeNode<T> getRoot(){ return root.getLeft(); } /* * add value to a new node,if this value exist in this tree, * if value exist,it will return the exist value.otherwise return null * if override mode is true,if value exist in the tree, * it will override the old value in the tree * * @param value * @return / public T addNode(T value){ RBTreeNode<T> t = new RBTreeNode<T>(value); return addNode(t); } /* * find the value by give value(include key,key used for search, * other field is not used,@see compare method).if this value not exist return null * @param value * @return / public T find(T value){ RBTreeNode<T> dataRoot = getRoot(); while(dataRoot!=null){ int cmp = dataRoot.getValue().compareTo(value); if(cmp<0){ dataRoot = dataRoot.getRight(); }else if(cmp>0){ dataRoot = dataRoot.getLeft(); }else{ return dataRoot.getValue(); } } return null; } /* * remove the node by give value,if this value not exists in tree return null * @param value include search key * @return the value contain in the removed node / public T remove(T value){ RBTreeNode<T> dataRoot = getRoot(); RBTreeNode<T> parent = root; while(dataRoot!=null){ int cmp = dataRoot.getValue().compareTo(value); if(cmp<0){ parent = dataRoot; dataRoot = dataRoot.getRight(); }else if(cmp>0){ parent = dataRoot; dataRoot = dataRoot.getLeft(); }else{ if(dataRoot.getRight()!=null){ RBTreeNode<T> min = removeMin(dataRoot.getRight()); //x used for fix color balance RBTreeNode<T> x = min.getRight()==null ? min.getParent() : min.getRight(); boolean isParent = min.getRight()==null; min.setLeft(dataRoot.getLeft()); setParent(dataRoot.getLeft(),min); if(parent.getLeft()==dataRoot){ parent.setLeft(min); }else{ parent.setRight(min); } setParent(min,parent); boolean curMinIsBlack = min.isBlack(); //inherit dataRoot's color min.setRed(dataRoot.isRed()); if(min!=dataRoot.getRight()){ min.setRight(dataRoot.getRight()); setParent(dataRoot.getRight(),min); } //remove a black node,need fix color if(curMinIsBlack){ if(min!=dataRoot.getRight()){ fixRemove(x,isParent); }else if(min.getRight()!=null){ fixRemove(min.getRight(),false); }else{ fixRemove(min,true); } } }else{ setParent(dataRoot.getLeft(),parent); if(parent.getLeft()==dataRoot){ parent.setLeft(dataRoot.getLeft()); }else{ parent.setRight(dataRoot.getLeft()); } //current node is black and tree is not empty if(dataRoot.isBlack() && !(root.getLeft()==null)){ RBTreeNode<T> x = dataRoot.getLeft()==null ? parent :dataRoot.getLeft(); boolean isParent = dataRoot.getLeft()==null; fixRemove(x,isParent); } } setParent(dataRoot,null); dataRoot.setLeft(null); dataRoot.setRight(null); if(getRoot()!=null){ getRoot().setRed(false); getRoot().setParent(null); } size.decrementAndGet(); return dataRoot.getValue(); } } return null; } /* * fix remove action * @param node * @param isParent / private void fixRemove(RBTreeNode<T> node,boolean isParent){ RBTreeNode<T> cur = isParent ? null : node; boolean isRed = isParent ? false : node.isRed(); RBTreeNode<T> parent = isParent ? node : node.getParent(); while(!isRed && !isRoot(cur)){ RBTreeNode<T> sibling = getSibling(cur,parent); //sibling is not null,due to before remove tree color is balance //if cur is a left node boolean isLeft = parent.getRight()==sibling; if(sibling.isRed() && !isLeft){//case 1 //cur in right parent.makeRed(); sibling.makeBlack(); rotateRight(parent); }else if(sibling.isRed() && isLeft){ //cur in left parent.makeRed(); sibling.makeBlack(); rotateLeft(parent); }else if(isBlack(sibling.getLeft()) && isBlack(sibling.getRight())){//case 2 sibling.makeRed(); cur = parent; isRed = cur.isRed(); parent=parent.getParent(); }else if(isLeft && !isBlack(sibling.getLeft()) && isBlack(sibling.getRight())){//case 3 sibling.makeRed(); sibling.getLeft().makeBlack(); rotateRight(sibling); }else if(!isLeft && !isBlack(sibling.getRight()) && isBlack(sibling.getLeft()) ){ sibling.makeRed(); sibling.getRight().makeBlack(); rotateLeft(sibling); }else if(isLeft && !isBlack(sibling.getRight())){//case 4 sibling.setRed(parent.isRed()); parent.makeBlack(); sibling.getRight().makeBlack(); rotateLeft(parent); cur=getRoot(); }else if(!isLeft && !isBlack(sibling.getLeft())){ sibling.setRed(parent.isRed()); parent.makeBlack(); sibling.getLeft().makeBlack(); rotateRight(parent); cur=getRoot(); } } if(isRed){ cur.makeBlack(); } if(getRoot()!=null){ getRoot().setRed(false); getRoot().setParent(null); } } //get sibling node private RBTreeNode<T> getSibling(RBTreeNode<T> node,RBTreeNode<T> parent){ parent = node==null ? parent : node.getParent(); if(node==null){ return parent.getLeft()==null ? parent.getRight() : parent.getLeft(); } if(node==parent.getLeft()){ return parent.getRight(); }else{ return parent.getLeft(); } } private boolean isBlack(RBTreeNode<T> node){ return node==null || node.isBlack(); } private boolean isRoot(RBTreeNode<T> node){ return root.getLeft() == node && node.getParent()==null; } /* * find the successor node * @param node current node's right node * @return / private RBTreeNode<T> removeMin(RBTreeNode<T> node){ //find the min node RBTreeNode<T> parent = node; while(node!=null && node.getLeft()!=null){ parent = node; node = node.getLeft(); } //remove min node if(parent==node){ return node; } parent.setLeft(node.getRight()); setParent(node.getRight(),parent); //don't remove right pointer,it is used for fixed color balance //node.setRight(null); return node; } private T addNode(RBTreeNode<T> node){ node.setLeft(null); node.setRight(null); node.setRed(true); setParent(node,null); if(root.getLeft()==null){ root.setLeft(node); //root node is black node.setRed(false); size.incrementAndGet(); }else{ RBTreeNode<T> x = findParentNode(node); int cmp = x.getValue().compareTo(node.getValue()); if(this.overrideMode && cmp==0){ T v = x.getValue(); x.setValue(node.getValue()); return v; }else if(cmp==0){ //value exists,ignore this node return x.getValue(); } setParent(node,x); if(cmp>0){ x.setLeft(node); }else{ x.setRight(node); } fixInsert(node); size.incrementAndGet(); } return null; } /* * find the parent node to hold node x,if parent value equals x.value return parent. * @param x * @return / private RBTreeNode<T> findParentNode(RBTreeNode<T> x){ RBTreeNode<T> dataRoot = getRoot(); RBTreeNode<T> child = dataRoot; while(child!=null){ int cmp = child.getValue().compareTo(x.getValue()); if(cmp==0){ return child; } if(cmp>0){ dataRoot = child; child = child.getLeft(); }else if(cmp<0){ dataRoot = child; child = child.getRight(); } } return dataRoot; } /* * red black tree insert fix. * @param x / private void fixInsert(RBTreeNode<T> x){ RBTreeNode<T> parent = x.getParent(); while(parent!=null && parent.isRed()){ RBTreeNode<T> uncle = getUncle(x); if(uncle==null){//need to rotate RBTreeNode<T> ancestor = parent.getParent(); //ancestor is not null due to before before add,tree color is balance if(parent == ancestor.getLeft()){ boolean isRight = x == parent.getRight(); if(isRight){ rotateLeft(parent); } rotateRight(ancestor); if(isRight){ x.setRed(false); parent=null;//end loop }else{ parent.setRed(false); } ancestor.setRed(true); }else{ boolean isLeft = x == parent.getLeft(); if(isLeft){ rotateRight(parent); } rotateLeft(ancestor); if(isLeft){ x.setRed(false); parent=null;//end loop }else{ parent.setRed(false); } ancestor.setRed(true); } }else{//uncle is red parent.setRed(false); uncle.setRed(false); parent.getParent().setRed(true); x=parent.getParent(); parent = x.getParent(); } } getRoot().makeBlack(); getRoot().setParent(null); } /* * get uncle node * @param node * @return / private RBTreeNode<T> getUncle(RBTreeNode<T> node){ RBTreeNode<T> parent = node.getParent(); RBTreeNode<T> ancestor = parent.getParent(); if(ancestor==null){ return null; } if(parent == ancestor.getLeft()){ return ancestor.getRight(); }else{ return ancestor.getLeft(); } } private void rotateLeft(RBTreeNode<T> node){ RBTreeNode<T> right = node.getRight(); if(right==null){ throw new java.lang.IllegalStateException("right node is null"); } RBTreeNode<T> parent = node.getParent(); node.setRight(right.getLeft()); setParent(right.getLeft(),node); right.setLeft(node); setParent(node,right); if(parent==null){//node pointer to root //right raise to root node root.setLeft(right); setParent(right,null); }else{ if(parent.getLeft()==node){ parent.setLeft(right); }else{ parent.setRight(right); } //right.setParent(parent); setParent(right,parent); } } private void rotateRight(RBTreeNode<T> node){ RBTreeNode<T> left = node.getLeft(); if(left==null){ throw new java.lang.IllegalStateException("left node is null"); } RBTreeNode<T> parent = node.getParent(); node.setLeft(left.getRight()); setParent(left.getRight(),node); left.setRight(node); setParent(node,left); if(parent==null){ root.setLeft(left); setParent(left,null); }else{ if(parent.getLeft()==node){ parent.setLeft(left); }else{ parent.setRight(left); } setParent(left,parent); } } private void setParent(RBTreeNode<T> node,RBTreeNode<T> parent){ if(node!=null){ node.setParent(parent); if(parent==root){ node.setParent(null); } } } /* * debug method,it used print the given node and its children nodes, * every layer output in one line * @param root */ public void printTree(RBTreeNode<T> root){ java.util.LinkedList<RBTreeNode<T>> queue =new java.util.LinkedList<RBTreeNode<T>>(); java.util.LinkedList<RBTreeNode<T>> queue2 =new java.util.LinkedList<RBTreeNode<T>>(); if(root==null){ return ; } queue.add(root); boolean firstQueue = true; while(!queue.isEmpty() || !queue2.isEmpty()){ java.util.LinkedList<RBTreeNode<T>> q = firstQueue ? queue : queue2; RBTreeNode<T> n = q.poll(); if(n!=null){ String pos = n.getParent()==null ? "" : ( n == n.getParent().getLeft() ? " LE" : " RI"); String pstr = n.getParent()==null ? "" : n.getParent().toString(); String cstr = n.isRed()?"R":"B"; cstr = n.getParent()==null ? cstr : cstr+" "; System.out.print(n+"("+(cstr)+pstr+(pos)+")"+"\t"); if(n.getLeft()!=null){ (firstQueue ? queue2 : queue).add(n.getLeft()); } if(n.getRight()!=null){ (firstQueue ? queue2 : queue).add(n.getRight()); } }else{ System.out.println(); firstQueue = !firstQueue; } } } public static void main(String[] args) { RBTree<String> bst = new RBTree<String>(); bst.addNode("d"); bst.addNode("d"); bst.addNode("c"); bst.addNode("c"); bst.addNode("b"); bst.addNode("f"); bst.addNode("a"); bst.addNode("e"); bst.addNode("g"); bst.addNode("h"); bst.remove("c"); bst.printTree(bst.getRoot()); }}
代碼調(diào)試的時(shí)候桑涎,printTree輸出格式如下:d(B)b(B d LE) g(R d RI)a(R b LE) e(B g LE) h(B g RI)f(R e RI)
括號(hào)左邊表示元素的內(nèi)容彬向。括號(hào)內(nèi)的第一個(gè)元素表示顏色,B表示black攻冷,R表示red娃胆;第二個(gè)元素表示父元素的值;第三個(gè)元素表示左右等曼,LE表示在父元素的左邊里烦。RI表示在父元素的右邊凿蒜。
第一個(gè)元素d是root節(jié)點(diǎn),由于它沒有父節(jié)點(diǎn)胁黑,所以括號(hào)內(nèi)只有一個(gè)元素废封。
總結(jié)
作為平衡二叉查找樹里面眾多的實(shí)現(xiàn)之一,紅黑樹無疑是最簡潔丧蘸、實(shí)現(xiàn)最為簡單的漂洋。紅黑樹通過引入顏色的概念,通過顏色這個(gè)約束條件的使用來保持樹的高度平衡力喷。作為平衡二叉查找樹刽漂,旋轉(zhuǎn)是一個(gè)必不可少的操作。通過旋轉(zhuǎn)可以降低樹的高度弟孟,在紅黑樹里面還可以轉(zhuǎn)換顏色贝咙。
紅黑樹里面的插入和刪除的操作比較難理解,這時(shí)要注意記住一點(diǎn):操作之前紅黑樹是平衡的披蕉,顏色是符合定義的颈畸。在操作的時(shí)候就需要向兄弟節(jié)點(diǎn)、父節(jié)點(diǎn)没讲、侄子節(jié)點(diǎn)借調(diào)和互換顏色眯娱,要達(dá)到這個(gè)目的,就需要不斷的進(jìn)行旋轉(zhuǎn)爬凑。所以紅黑樹的插入刪除操作需要不停的旋轉(zhuǎn)徙缴,一旦借調(diào)了別的節(jié)點(diǎn),刪除和插入的節(jié)點(diǎn)就會(huì)達(dá)到局部的平衡(局部符合紅黑樹的定義)嘁信,但是被借調(diào)的節(jié)點(diǎn)就不會(huì)平衡了于样,這時(shí)就需要以被借調(diào)的節(jié)點(diǎn)為起點(diǎn)繼續(xù)進(jìn)行調(diào)整,直到整棵樹都是平衡的潘靖。在整個(gè)修復(fù)的過程中穿剖,插入具體的分為3種情況,刪除分為4種情況卦溢。
整個(gè)紅黑樹的查找糊余,插入和刪除都是O(logN)的,原因就是整個(gè)紅黑樹的高度是logN单寂,查找從根到葉贬芥,走過的路徑是樹的高度,刪除和插入操作是從葉到根的宣决,所以經(jīng)過的路徑都是logN蘸劈。
參考文獻(xiàn)
Cormen T H, Leiserson C E, Rivest R L, 等. 算法導(dǎo)論(第3版). 殷建平, 等. 機(jī)械工業(yè)出版社, 2012.
Sedgewick R, Wayne K. 算法(第4版). 謝路云 譯. 人民郵電出版社, 2012.
Weiss M A. 數(shù)據(jù)結(jié)構(gòu)與算法分析(第2版). 馮舜璽 譯. 機(jī)械工業(yè)出版社, 2004.
Knuth D E. 計(jì)算機(jī)程序設(shè)計(jì)藝術(shù) 卷3:排序與查找(英文版 第2版). 人民郵電出版社, 2010.