------------------
本文參考:Google、算法導(dǎo)論、STL源碼剖析、計(jì)算機(jī)程序設(shè)計(jì)藝術(shù)。 本人聲明:個(gè)人原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處。 更多請(qǐng)參考: http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx 詳情,參見My BLog: http://blog.csdn.net/v_JULY_v 一、紅黑樹的介紹 先來看下算法導(dǎo)論對(duì)R-B Tree的介紹: 紅黑樹,一種二叉查找樹,但在每個(gè)結(jié)點(diǎn)上增加一個(gè)存儲(chǔ)位表示結(jié)點(diǎn)的顏色,可以是Red或Black。 通過對(duì)任何一條從根到葉子的路徑上各個(gè)結(jié)點(diǎn)著色方式的限制,紅黑樹確保沒有一條路徑會(huì)比其他路徑長(zhǎng)出倆倍,因而是接近平衡的。 前面說了,紅黑樹,是一種二叉查找樹,既然是二叉查找樹,那么它必滿足二叉查找樹的一般性質(zhì)。 下面,再具體介紹紅黑樹之前,咱們先來了解下 二叉查找樹的一般性質(zhì): 1.在一棵二叉查找樹上,執(zhí)行查找、插入、刪除等操作,的時(shí)間復(fù)雜度為O(lgn)。 因?yàn)椋豢糜蒼個(gè)結(jié)點(diǎn),隨機(jī)構(gòu)造的二叉查找樹的高度為O(lgn),所以順理成章,一般操作的執(zhí)行時(shí)間為O(lgn)。 (至于n個(gè)結(jié)點(diǎn)的二叉樹高度為O(lgn)的證明,可參考算法導(dǎo)論 第12章 二叉查找樹。) 2.但若是一棵具有n個(gè)結(jié)點(diǎn)的線性鏈,則此些操作最壞情況運(yùn)行時(shí)間為O(n)。 而紅黑樹,能保證在最壞情況下,基本的動(dòng)態(tài)幾何操作的時(shí)間均為O(lgn)。 ok,我們知道,紅黑樹上每個(gè)結(jié)點(diǎn)內(nèi)含五個(gè)域,color,key,left,right。如果相應(yīng)的指針域沒有,則設(shè)為NIL。 一般的,紅黑樹,滿足一下性質(zhì),即只有滿足一下性質(zhì)的樹,我們才稱之為紅黑樹: 1)每個(gè)結(jié)點(diǎn)要么是紅的,要么是黑的。 2)根結(jié)點(diǎn)是黑的。 3)每個(gè)葉結(jié)點(diǎn),即空結(jié)點(diǎn)(NIL)是黑的。 4)如果一個(gè)結(jié)點(diǎn)是紅的,那么它的倆個(gè)兒子都是黑的。 5)對(duì)每個(gè)結(jié)點(diǎn),從該結(jié)點(diǎn)到其子孫結(jié)點(diǎn)的所有路徑上包含相同數(shù)目的黑結(jié)點(diǎn)。 下圖所示,即是一顆紅黑樹: ![]() 此圖忽略了葉子和根部的父結(jié)點(diǎn)??傊?,可以這樣表示,就對(duì)了。:D。 二、樹的旋轉(zhuǎn)知識(shí) 當(dāng)我們?cè)趯?duì)紅黑樹進(jìn)行插入和刪除等操作時(shí),對(duì)樹做了修改,那么可能會(huì)違背紅黑樹的性質(zhì)。 為了保持紅黑樹的性質(zhì),我們可以通過對(duì)樹進(jìn)行旋轉(zhuǎn),即修改樹種某些結(jié)點(diǎn)的顏色及指針結(jié)構(gòu),以達(dá)到對(duì)紅黑樹進(jìn)行 插入、刪除結(jié)點(diǎn)等操作時(shí),紅黑樹依然能保持它特有的性質(zhì)(如上文所述的,五點(diǎn)性質(zhì))。 樹的旋轉(zhuǎn),分為左旋和右旋,以下借助圖來做形象的解釋和介紹: 1.左旋 ![]() 如上圖所示: 當(dāng)在某個(gè)結(jié)點(diǎn)pivot上,做左旋操作時(shí),我們假設(shè)它的右孩子y不是NIL[T],pivot可以為樹內(nèi)任意右孩子而不是NIL[T]的結(jié)點(diǎn)。 左旋以pivot到y(tǒng)之間的鏈為“支軸”進(jìn)行,它使y成為該孩子樹新的根,而y的左孩子b則成為pivot的右孩子。 來看算法導(dǎo)論對(duì)此操作的算法實(shí)現(xiàn)(以x代替上述的pivot):
2.右旋 右旋與左旋差不多,再此不做詳細(xì)介紹。 ![]() 對(duì)于樹的旋轉(zhuǎn),能保持不變的只有原樹的搜索性質(zhì),而原樹的紅黑性質(zhì)則不能保持, 在紅黑樹的數(shù)據(jù)插入和刪除后可利用旋轉(zhuǎn)和顏色重涂來恢復(fù)樹的紅黑性質(zhì)。 至于有些書如 STL源碼剖析有對(duì)雙旋的描述,其實(shí)雙旋只是單旋的兩次應(yīng)用,并無新的內(nèi)容, 因此這里就不再介紹了,而且左右旋也是相互對(duì)稱的,只要理解其中一種旋轉(zhuǎn)就可以了。 三、紅黑樹插入、刪除操作的具體實(shí)現(xiàn) ok,接下來,咱們來具體了解紅黑樹的插入操作。 向一棵含有n個(gè)結(jié)點(diǎn)的紅黑樹插入一個(gè)新結(jié)點(diǎn)的操作可以在O(lgn)時(shí)間內(nèi)完成。 算法導(dǎo)論:
咱們來具體分析下,此段代碼: RB-INSERT(T, z),將z插入紅黑樹T 之內(nèi)。 為保證紅黑性質(zhì)在插入操作后依然保持,上述代碼調(diào)用了一個(gè)輔助程序RB-INSERT-FIXUP 來對(duì)結(jié)點(diǎn)進(jìn)行重新著色,并旋轉(zhuǎn)。 14 left[z] ← nil[T] 15 right[z] ← nil[T] //保持正確的樹結(jié)構(gòu) 第16行,將z著為紅色,由于將z著為紅色可能會(huì)違背某一條紅黑樹的性質(zhì), 所以,在第17行,調(diào)用RB-INSERT-FIXUP(T,z)來保持紅黑樹的性質(zhì)。 RB-INSERT-FIXUP(T, z),如下所示:
ok,參考一網(wǎng)友的言論,用自己的語言,再來具體解剖下上述倆段代碼。 為了保證闡述清晰,我再寫下紅黑樹的5個(gè)性質(zhì): 1)每個(gè)結(jié)點(diǎn)要么是紅的,要么是黑的。 2)根結(jié)點(diǎn)是黑的。 3)每個(gè)葉結(jié)點(diǎn),即空結(jié)點(diǎn)(NIL)是黑的。 4)如果一個(gè)結(jié)點(diǎn)是紅的,那么它的倆個(gè)兒子都是黑的。 5)對(duì)每個(gè)結(jié)點(diǎn),從該結(jié)點(diǎn)到其子孫結(jié)點(diǎn)的所有路徑上包含相同數(shù)目的黑結(jié)點(diǎn)。 在對(duì)紅黑樹進(jìn)行插入操作時(shí),我們一般總是插入紅色的結(jié)點(diǎn),因?yàn)檫@樣可以在插入過程中盡量避免對(duì)樹的調(diào)整。 那么,我們插入一個(gè)結(jié)點(diǎn)后,可能會(huì)使原樹的哪些性質(zhì)改變列? 由于,我們是按照二叉樹的方式進(jìn)行插入,因此元素的搜索性質(zhì)不會(huì)改變。 如果插入的結(jié)點(diǎn)是根結(jié)點(diǎn),性質(zhì)2會(huì)被破壞,如果插入結(jié)點(diǎn)的父結(jié)點(diǎn)是紅色,則會(huì)破壞性質(zhì)4。 因此,總而言之,插入一個(gè)紅色結(jié)點(diǎn)只會(huì)破壞性質(zhì)2或性質(zhì)4。 我們的回復(fù)策略很簡(jiǎn)單, 其一、把出現(xiàn)違背紅黑樹性質(zhì)的結(jié)點(diǎn)向上移,如果能移到根結(jié)點(diǎn),那么很容易就能通過直接修改根結(jié)點(diǎn)來恢復(fù)紅黑樹的性質(zhì)。直接通過修改根結(jié)點(diǎn)來恢復(fù)紅黑樹應(yīng)滿足的性質(zhì)。 其二、窮舉所有的可能性,之后把能歸于同一類方法處理的歸為同一類,不能直接處理的化歸到下面的幾種情況, 情況1:插入的是根結(jié)點(diǎn)。 原樹是空樹,此情況只會(huì)違反性質(zhì)2。 對(duì)策:直接把此結(jié)點(diǎn)涂為黑色。 情況2:插入的結(jié)點(diǎn)的父結(jié)點(diǎn)是黑色。 此不會(huì)違反性質(zhì)2和性質(zhì)4,紅黑樹沒有被破壞。 對(duì)策:什么也不做。 情況3:當(dāng)前結(jié)點(diǎn)的父結(jié)點(diǎn)是紅色且祖父結(jié)點(diǎn)的另一個(gè)子結(jié)點(diǎn)(叔叔結(jié)點(diǎn))是紅色。 此時(shí)父結(jié)點(diǎn)的父結(jié)點(diǎn)一定存在,否則插入前就已不是紅黑樹。 與此同時(shí),又分為父結(jié)點(diǎn)是祖父結(jié)點(diǎn)的左子還是右子,對(duì)于對(duì)稱性,我們只要解開一個(gè)方向就可以了。 在此,我們只考慮父結(jié)點(diǎn)為祖父左子的情況。 同時(shí),還可以分為當(dāng)前結(jié)點(diǎn)是其父結(jié)點(diǎn)的左子還是右子,但是處理方式是一樣的。我們將此歸為同一類。 對(duì)策:將當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)和叔叔節(jié)點(diǎn)涂黑,祖父結(jié)點(diǎn)涂紅,把當(dāng)前結(jié)點(diǎn)指向祖父節(jié)點(diǎn),從新的當(dāng)前節(jié)點(diǎn)重新開始算法。 針對(duì)情況3,變化前(圖片來源:saturnman): 變化前: ![]() 變化后: ![]() 情況4:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,叔叔節(jié)點(diǎn)是黑色,當(dāng)前節(jié)點(diǎn)是其父節(jié)點(diǎn)的右子 對(duì)策:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)做為新的當(dāng)前節(jié)點(diǎn),以新當(dāng)前節(jié)點(diǎn)為支點(diǎn)左旋。 如下圖所示,變化前: ![]() 變化后: ![]() 情況5:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,叔叔節(jié)點(diǎn)是黑色,當(dāng)前節(jié)點(diǎn)是其父節(jié)點(diǎn)的左子 解法:父節(jié)點(diǎn)變?yōu)楹谏?,祖父?jié)點(diǎn)變?yōu)榧t色,在祖父節(jié)點(diǎn)為支點(diǎn)右旋 如下圖所示 ![]() 變化后: ![]() -------------------------------- ok,接下來,咱們最后來簡(jiǎn)單了解,紅黑樹的刪除操作: 算法導(dǎo)論一書,給的算法實(shí)現(xiàn): RB-DELETE(T, z)
saturnman: 我們紅黑樹刪除的幾種情況: (注:以下各種情況,不與上述算法導(dǎo)論之上的代碼相對(duì)應(yīng)。) 情況1:當(dāng)前節(jié)點(diǎn)是紅+黑色 解法,直接把當(dāng)前節(jié)點(diǎn)染成黑色,結(jié)束。 此時(shí)紅黑樹性質(zhì)全部恢復(fù)。 情況2:當(dāng)前節(jié)點(diǎn)是黑+黑且是根節(jié)點(diǎn) 解法:什么都不做,結(jié)束 情況3:當(dāng)前節(jié)點(diǎn)是黑+黑且兄弟節(jié)點(diǎn)為紅色(此時(shí)父節(jié)點(diǎn)和兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)分為黑)。 解法:把父節(jié)點(diǎn)染成紅色,把兄弟結(jié)點(diǎn)染成黑色,之后重新進(jìn)入算法(我們只討論當(dāng)前節(jié)點(diǎn)是其父節(jié)點(diǎn)左孩子時(shí)的情況)。此變換后原紅黑樹性質(zhì)5不變,而把問題轉(zhuǎn)化為兄弟節(jié)點(diǎn)為黑色的情況。 3.變化前: .......... =========== 更多請(qǐng)參考: http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx 詳情,參見My BLog: http://blog.csdn.net/v_JULY_v |
|