Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹(shù)的原理及實(shí)現(xiàn)
為什么要有紅黑樹(shù)這種數(shù)據(jù)結(jié)構(gòu)
我們知道ALV樹(shù)是一種嚴(yán)格按照定義來(lái)實(shí)現(xiàn)的平衡二叉查找樹(shù),所以它查找的效率非常穩(wěn)定,為O(log n),由于其嚴(yán)格按照左右子樹(shù)高度差不大于1的規(guī)則,插入和刪除操作中需要大量且復(fù)雜的操作來(lái)保持ALV樹(shù)的平衡(左旋和右旋),因此ALV樹(shù)適用于大量查詢,少量插入和刪除的場(chǎng)景中
那么假設(shè)現(xiàn)在假設(shè)有這樣一種場(chǎng)景:大量查詢,大量插入和刪除,現(xiàn)在使用ALV樹(shù)就不太合適了,因?yàn)锳LV樹(shù)大量的插入和刪除會(huì)非常耗時(shí)間,那么我們是否可以降低ALV樹(shù)對(duì)平衡性的要求從而達(dá)到快速的插入和刪除呢?
答案肯定是有的,紅黑樹(shù)這種數(shù)據(jù)結(jié)構(gòu)就應(yīng)運(yùn)而生了(因?yàn)锳LV樹(shù)是高度平衡的,所以查找起來(lái)肯定比紅黑樹(shù)快,但是紅黑樹(shù)在插入和刪除方面的性能就遠(yuǎn)遠(yuǎn)不是ALV樹(shù)所能比的了)
紅黑樹(shù)的簡(jiǎn)介
紅黑樹(shù)是一種特殊的二叉查找樹(shù),每個(gè)結(jié)點(diǎn)都要儲(chǔ)存位表示結(jié)點(diǎn)的顏色,或紅或黑
紅黑樹(shù)的特性(紅黑樹(shù)必須滿足的5個(gè)條件):
1)每個(gè)結(jié)點(diǎn)或紅或黑
2)根結(jié)點(diǎn)是黑色
3)空葉子結(jié)點(diǎn)是黑色
4)如果一個(gè)結(jié)點(diǎn)是紅色,那么它的子節(jié)點(diǎn)是黑色
5)從任意一個(gè)結(jié)點(diǎn)出發(fā)到空的葉子結(jié)點(diǎn)經(jīng)過(guò)的黑結(jié)點(diǎn)個(gè)數(shù)相同
示意圖:(紅黑樹(shù)首先是一顆搜索樹(shù),所以左子樹(shù)點(diǎn)小于根結(jié)點(diǎn),右子樹(shù)大于根節(jié)點(diǎn),等于根結(jié)點(diǎn)的按照自己的需要放左邊和右邊都是可以的)(紅黑樹(shù)不僅是一顆搜索樹(shù)還是一顆非嚴(yán)格的平衡樹(shù))

現(xiàn)在有個(gè)問(wèn)題:紅黑樹(shù)是怎么通過(guò)上面5條性質(zhì)保證任意結(jié)點(diǎn)到空的葉子結(jié)點(diǎn)的所有路徑中,沒(méi)有一條路徑會(huì)大于其他路徑的兩倍的呢?(換句話說(shuō)就是它是如何確保任何一個(gè)結(jié)點(diǎn)的左右子樹(shù)的高度差不會(huì)超過(guò)二者中較低那個(gè)的一倍的呢?)
先結(jié)合紅黑樹(shù)的5個(gè)性質(zhì)分析一下紅黑樹(shù)的一些操作,我們可能就明白了!
建議學(xué)習(xí)紅黑樹(shù)前了解AVL樹(shù)
紅黑樹(shù)的基本操作之旋轉(zhuǎn)
紅黑樹(shù)的基本操作是添加和刪除,在對(duì)紅黑樹(shù)進(jìn)行添加和刪除之后,都會(huì)用到旋轉(zhuǎn)方法,為什么呢?道理很簡(jiǎn)單,因?yàn)樘砑踊蛘邉h除紅黑樹(shù)中的結(jié)點(diǎn)之后,紅黑樹(shù)就發(fā)生了變化,可能不滿足上面的5條性質(zhì)了,這個(gè)時(shí)候就需要通過(guò)旋轉(zhuǎn)操作來(lái)保證它依舊是一棵紅黑樹(shù),旋轉(zhuǎn)分為左旋和右旋(旋轉(zhuǎn)操作僅僅只是用來(lái)調(diào)節(jié)結(jié)點(diǎn)的位置的,就是為了滿足紅黑樹(shù)的性質(zhì)5)
左旋

左旋是將X的右子樹(shù)繞X逆時(shí)針旋轉(zhuǎn),使得X的右子樹(shù)成為X的父親,同時(shí)修改相關(guān)結(jié)點(diǎn)的引用,旋轉(zhuǎn)之后,要求二叉查找樹(shù)的屬性依然滿足
右旋

右旋是將X的左子樹(shù)繞X順時(shí)針旋轉(zhuǎn),使得X的左子樹(shù)成為X的父親,同時(shí)注意修改相關(guān)結(jié)點(diǎn)的引用,旋轉(zhuǎn)之后要求仍然滿足搜索樹(shù)的屬性
紅黑樹(shù)之添加元素
添加操作宏觀過(guò)程:首先將紅黑樹(shù)當(dāng)作一顆查找樹(shù)一樣將結(jié)點(diǎn)插入,然后將結(jié)點(diǎn)著為紅色,最后通過(guò)旋轉(zhuǎn)和重新著色的方法使之重新成為紅黑樹(shù)
將新加入的結(jié)點(diǎn)涂成紅色的原因:
1)不違背紅黑樹(shù)的性質(zhì)5:從任意一個(gè)結(jié)點(diǎn)出發(fā)到空葉子結(jié)點(diǎn),經(jīng)過(guò)的黑色結(jié)點(diǎn)個(gè)數(shù)相同
2)按照紅黑樹(shù)的性質(zhì)4我們知道紅黑樹(shù)中黑結(jié)點(diǎn)的個(gè)數(shù)至少是紅結(jié)點(diǎn)個(gè)數(shù)的兩倍,所以新增結(jié)點(diǎn)的父親結(jié)點(diǎn)是黑結(jié)點(diǎn)的概率比較大,如果新增結(jié)點(diǎn)的父節(jié)點(diǎn)為黑色,那么此時(shí)不需要再去進(jìn)行任何調(diào)整操作,因此效率很高,所以新結(jié)點(diǎn)應(yīng)該涂成紅色
少違背一條性質(zhì),意味著我們后續(xù)的旋轉(zhuǎn)和重新著色操作會(huì)簡(jiǎn)單很多
現(xiàn)在我們來(lái)看看新增一個(gè)紅色的結(jié)點(diǎn)會(huì)違背紅黑樹(shù)的5條性質(zhì)中的哪些?
1)每個(gè)結(jié)點(diǎn)或紅或黑
2)根結(jié)點(diǎn)是黑色
3)空葉子結(jié)點(diǎn)是黑色
4)如果一個(gè)結(jié)點(diǎn)是紅色,那么它的子節(jié)點(diǎn)是黑色
5)從任意一個(gè)結(jié)點(diǎn)出發(fā)到空的葉子結(jié)點(diǎn)經(jīng)過(guò)的黑結(jié)點(diǎn)個(gè)數(shù)相同
1.顯然沒(méi)有違背
2.根據(jù)查找樹(shù)的特定,插入操作不好改變根結(jié)點(diǎn),所以也沒(méi)有違背
3.插入的肯定不是空葉子結(jié)點(diǎn),所以也沒(méi)有違背
4.有可能違背?。?!
5.插入結(jié)點(diǎn)涂成紅色就是為了不違背第5條性質(zhì)
現(xiàn)在我們來(lái)分析一下新增的結(jié)點(diǎn)(紅色)插入之后可能面臨的幾種情況,以及他們的處理措施
1.插入的結(jié)點(diǎn)為根結(jié)點(diǎn)
將新插入的紅色結(jié)點(diǎn)變成黑色結(jié)點(diǎn),滿足根結(jié)點(diǎn)為黑色結(jié)點(diǎn)的要求!
2.父親結(jié)點(diǎn)為黑色結(jié)點(diǎn)
這個(gè)時(shí)候不需要進(jìn)行任何調(diào)整操作,此時(shí)的樹(shù)仍然是一顆標(biāo)準(zhǔn)的紅黑樹(shù)
3.父親結(jié)點(diǎn)為紅色結(jié)點(diǎn)的情況下,叔叔結(jié)點(diǎn)為紅色結(jié)點(diǎn)(不用考慮左右)
解決方案:將叔叔和父親結(jié)點(diǎn)改為黑色,爺爺結(jié)點(diǎn)改為紅色,然后又將爺爺結(jié)點(diǎn)當(dāng)作插入結(jié)點(diǎn)看待,一直進(jìn)行上面的操作,直到當(dāng)前結(jié)點(diǎn)為根結(jié)點(diǎn),然后將根結(jié)點(diǎn)變成黑色

插入一個(gè)125的結(jié)點(diǎn):

現(xiàn)在125結(jié)點(diǎn)和130結(jié)點(diǎn)都是紅色的,顯然違背了規(guī)則4,所以將新插入結(jié)點(diǎn)的父親130結(jié)點(diǎn)和插入結(jié)點(diǎn)的叔叔結(jié)點(diǎn)150變成黑色,并將新插入結(jié)點(diǎn)的爺爺結(jié)點(diǎn)140變成紅色,圖如下:

然后又將140結(jié)點(diǎn)當(dāng)作新插入結(jié)點(diǎn)處理(因?yàn)?40結(jié)點(diǎn)和新插入結(jié)點(diǎn)面臨的情況都是一樣的:和父親結(jié)點(diǎn)都是紅色),也就是做如下處理:將140結(jié)點(diǎn)的父親結(jié)點(diǎn)120和140的叔叔結(jié)點(diǎn)60變成黑色結(jié)點(diǎn),將140結(jié)點(diǎn)的爺爺結(jié)點(diǎn)變成紅色,因?yàn)楸闅v到了根結(jié)點(diǎn),要滿足根結(jié)點(diǎn)是黑色的性質(zhì)要求,所以又將140的爺爺結(jié)點(diǎn)也就是根結(jié)點(diǎn)變成黑色,圖如下:

到這里,為新插入結(jié)點(diǎn)125所做的某些結(jié)點(diǎn)重新著色的操作就完成了,現(xiàn)在該樹(shù)是標(biāo)準(zhǔn)的紅黑樹(shù)了!
4.新插入的結(jié)點(diǎn)的父親結(jié)點(diǎn)為紅色,其叔叔結(jié)點(diǎn)為黑色
1)父親結(jié)點(diǎn)為爺爺結(jié)點(diǎn)的左孩子,新插入結(jié)點(diǎn)為父節(jié)點(diǎn)的左孩子(左左情況)
2)父親結(jié)點(diǎn)為爺爺結(jié)點(diǎn)的右孩子,新插入結(jié)點(diǎn)為父親結(jié)點(diǎn)的右孩子(右右情況)
上述兩種情況都是同一個(gè)處理辦法
比如下圖,新插入結(jié)點(diǎn)為25,其父親結(jié)點(diǎn)30為紅色,其叔叔結(jié)點(diǎn)為空黑色葉子結(jié)點(diǎn),且新插入結(jié)點(diǎn)和其父節(jié)點(diǎn)都是左孩子:

我們將其父親結(jié)點(diǎn)和爺爺結(jié)點(diǎn)顏色互換,然后針對(duì)爺爺結(jié)點(diǎn)進(jìn)行一次左旋,圖如下:

現(xiàn)在這顆樹(shù)完全滿足紅黑樹(shù)的5個(gè)性質(zhì)了(最好自己對(duì)照5個(gè)性質(zhì)看一下)
現(xiàn)在又一個(gè)問(wèn)題,我們?yōu)槭裁匆M(jìn)行旋轉(zhuǎn)?
假設(shè)我們只將新增結(jié)點(diǎn)的父親結(jié)點(diǎn)和其爺爺結(jié)點(diǎn)的顏色互換了,圖如下:

我們發(fā)現(xiàn)上述兩條到葉子結(jié)點(diǎn)的路徑經(jīng)過(guò)的黑色結(jié)點(diǎn)數(shù)量不一樣?。?!,所以它不滿足紅黑樹(shù)的第5條性質(zhì),所以這就是我們旋轉(zhuǎn)的意義所在!?。。ㄒ?yàn)闊o(wú)論你這么旋轉(zhuǎn)都沒(méi)有改變結(jié)點(diǎn)顏色,改變的是結(jié)點(diǎn)的位置,而這位置改變剛好能使得樹(shù)滿足紅黑樹(shù)的第5條性質(zhì)?。?/p>
5.新插入的結(jié)點(diǎn)的父親結(jié)點(diǎn)是紅色,其叔叔結(jié)點(diǎn)是黑色
1)插入結(jié)點(diǎn)是右結(jié)點(diǎn),父節(jié)點(diǎn)是左結(jié)點(diǎn)
2)插入結(jié)點(diǎn)是左結(jié)點(diǎn),父親結(jié)點(diǎn)是右結(jié)點(diǎn)
上述兩種情況都是同一個(gè)處理辦法
比如下圖,新插入結(jié)點(diǎn)是126,其父結(jié)點(diǎn)125為紅色,其叔叔結(jié)點(diǎn)為空的黑色結(jié)點(diǎn),而且插入結(jié)點(diǎn)是右結(jié)點(diǎn),父結(jié)點(diǎn)是左結(jié)點(diǎn)

我們將父親結(jié)點(diǎn)125看作當(dāng)前結(jié)點(diǎn)進(jìn)行左旋,旋轉(zhuǎn)結(jié)果如下:

現(xiàn)在我們的當(dāng)前結(jié)點(diǎn)是125,現(xiàn)在125的處境和上面的情況4是一樣的(父節(jié)點(diǎn)為紅,叔叔結(jié)點(diǎn)為黑,插入結(jié)點(diǎn)為左結(jié)點(diǎn),父親結(jié)點(diǎn)也為左孩子)現(xiàn)在我們繼續(xù)按照情況4的處理辦法處理上述情況(措施和情況4一樣,父親結(jié)點(diǎn)和爺爺結(jié)點(diǎn)互換顏色,然后針對(duì)爺爺結(jié)點(diǎn)進(jìn)行左旋),處理后情況如下:

現(xiàn)在樹(shù)就是一顆標(biāo)準(zhǔn)的紅黑樹(shù)了!
我們現(xiàn)在總結(jié)一下插入結(jié)點(diǎn)面臨的幾種情況以及采取的措施:
1.樹(shù)為空,插入的結(jié)點(diǎn)為根結(jié)點(diǎn)
直接將插入的結(jié)點(diǎn)變成黑色
2.父親結(jié)點(diǎn)為黑色結(jié)點(diǎn)
不需要任何操作
3.父親結(jié)點(diǎn)為紅色結(jié)點(diǎn)的情況下:
3.1 叔叔結(jié)點(diǎn)也為紅色結(jié)點(diǎn)
將叔叔和父親結(jié)點(diǎn)改為黑色,爺爺結(jié)點(diǎn)改為紅色,未完,然后又將爺爺結(jié)點(diǎn)當(dāng)作插入結(jié)點(diǎn)看待,一直進(jìn)行上
面的操作,直到當(dāng)前結(jié)點(diǎn)為根結(jié)點(diǎn),然后將根結(jié)點(diǎn)變成黑色
3.2 叔叔結(jié)點(diǎn)為黑色結(jié)點(diǎn)的情況下:
3.2.1 (父親結(jié)點(diǎn)為左孩子,插入結(jié)點(diǎn)也為左孩子)||(父親結(jié)點(diǎn)為右孩子,插入結(jié)點(diǎn)也為右孩子)
將父親結(jié)點(diǎn)和爺爺結(jié)點(diǎn)的顏色互換,然后針對(duì)爺爺結(jié)點(diǎn)進(jìn)行一次左旋
3.2.2 (父親結(jié)點(diǎn)為左孩子,插入結(jié)點(diǎn)為右孩子)||(父親結(jié)點(diǎn)為右孩子,插入結(jié)點(diǎn)為左孩子)
針對(duì)父結(jié)點(diǎn)進(jìn)行左旋,此時(shí)左旋后的情況必定是3.2.1的情況,然后按照3.2.1的情況處理
現(xiàn)在我們來(lái)推導(dǎo)一下,為什么插入的情況只有上面這些:
1.爺爺結(jié)點(diǎn)為紅色結(jié)點(diǎn)的情況下,父親結(jié)點(diǎn)只能為黑色(紅黑樹(shù)的性質(zhì)4),處理操作:上面情況2
2.爺爺結(jié)點(diǎn)為黑色的情況下,父親結(jié)點(diǎn)和叔叔結(jié)點(diǎn):可以為紅色,也可以為黑色
2.1 父親結(jié)點(diǎn)為黑,叔叔結(jié)點(diǎn)為黑:處理操作:上面情況2
2.2 父親結(jié)點(diǎn)為黑,叔叔結(jié)點(diǎn)為紅:處理操作:上面情況2
2.3 父親結(jié)點(diǎn)為紅,叔叔結(jié)點(diǎn)為紅:處理操作:上面情況3.1
(上面3種情況都是不用考慮左右的)
2.4 父親結(jié)點(diǎn)為紅,叔叔結(jié)點(diǎn)為黑:
- 2.4.1 父親結(jié)點(diǎn)為左孩子,叔叔結(jié)點(diǎn)為左孩子:處理操作:上面情況3.2.1
- 2.4.2 父親結(jié)點(diǎn)為右孩子,叔叔結(jié)點(diǎn)為右孩子:處理操作:上面情況3.2.1
- 2.4.3 父親結(jié)點(diǎn)為左孩子,插入結(jié)點(diǎn)為右孩子:處理操作:上面情況3.2.2
- 2.4.4 父親結(jié)點(diǎn)為右孩子,插入結(jié)點(diǎn)為左孩子:處理操作:上面情況3.2.2
總結(jié):可以發(fā)現(xiàn)我們沒(méi)有遺漏任何情況,所有可能面臨的情況我們都處理了
紅黑樹(shù)之刪除結(jié)點(diǎn)
先說(shuō)一個(gè)刪除結(jié)點(diǎn)的過(guò)程原理:首先將紅黑樹(shù)當(dāng)作一個(gè)二叉查找樹(shù),將該結(jié)點(diǎn)從二叉查找樹(shù)種刪除,然后通過(guò)一些列重新著色操作等一系列措施來(lái)修正該樹(shù),使之重新成為一顆紅黑樹(shù), 刪除結(jié)點(diǎn)其實(shí)很容易,難的是如何使得刪除結(jié)點(diǎn)后的樹(shù)重新成為一個(gè)紅黑樹(shù)
我們可以根據(jù)刪除結(jié)點(diǎn)N的兒子個(gè)數(shù)分為三種情況:
刪除結(jié)點(diǎn)沒(méi)有兒子
刪除結(jié)點(diǎn)有1個(gè)兒子
刪除結(jié)點(diǎn)有2個(gè)兒子
接下來(lái)我們又可以對(duì)以上三種情況繼續(xù)進(jìn)行細(xì)分:
一.刪除結(jié)點(diǎn)沒(méi)有兒子的情況:
- 1)刪除結(jié)點(diǎn)為紅色
- 2)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)沒(méi)有兒子
- 3)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有一個(gè)孩子不空,并且該孩子為右孩子
- 4)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有一個(gè)孩子不空,并且該孩子為左孩子
- 5)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有兩個(gè)孩子,而且兄弟結(jié)點(diǎn)為紅色
- 6)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有兩個(gè)孩子,而且兄弟結(jié)點(diǎn)為黑色
二. 刪除結(jié)點(diǎn)只有一個(gè)兒子的情況:
- 1)刪除結(jié)點(diǎn)為黑色,其唯一的兒子結(jié)點(diǎn)為紅色(必定是紅色,要不然不符合紅黑樹(shù)的第5條性質(zhì))
- 2)刪除結(jié)點(diǎn)為紅色,其兒子結(jié)點(diǎn)只能為黑:(紅黑樹(shù)中不存在這種情況,要不然無(wú)法滿足紅黑樹(shù)第5條性質(zhì))
三. 刪除結(jié)點(diǎn)有兩個(gè)兒子的情況: 找到刪除結(jié)點(diǎn)的右子樹(shù)中最左的結(jié)點(diǎn),兩兩值交換,然后刪除結(jié)點(diǎn)的情況就變成了上面兩種情況中的一種了
現(xiàn)在我們就具體分析一下面臨不同的操作到達(dá)該這么操作:
刪除結(jié)點(diǎn)沒(méi)有兒子的情況
1)刪除結(jié)點(diǎn)為紅色
直接刪除,比如下圖,想要?jiǎng)h除130結(jié)點(diǎn)

直接刪除130結(jié)點(diǎn),結(jié)果圖如下:

因?yàn)閯h除的是紅色結(jié)點(diǎn),不會(huì)影響紅黑樹(shù)第5條性質(zhì),所以可以直接刪除
2)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)沒(méi)有兒子
這種情況下其兄弟結(jié)點(diǎn)也肯定是黑色的(要滿足紅黑樹(shù)第5條性質(zhì)),假設(shè)現(xiàn)在要?jiǎng)h除的是150這個(gè)結(jié)點(diǎn),原圖如下:

先刪除結(jié)點(diǎn)150,然后將兄弟結(jié)點(diǎn)126變成紅色,父親結(jié)點(diǎn)140變成黑色,結(jié)果如下:

這樣做的目的是為了滿足紅黑樹(shù)的第5條性質(zhì),要不然根到最右邊的葉子結(jié)點(diǎn)經(jīng)過(guò)的黑色結(jié)點(diǎn)只有3個(gè),而其他路徑有4個(gè)
3)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有一個(gè)孩子不空,并且該孩子和兄弟結(jié)點(diǎn)在同一邊(同為左子樹(shù)或者同為右子樹(shù))
假設(shè)現(xiàn)在要?jiǎng)h除的結(jié)點(diǎn)為110,其兄弟結(jié)點(diǎn)140只有一個(gè)孩子150,而且都是右子樹(shù),滿足上述條件,原圖如下:

先把需要?jiǎng)h除的結(jié)點(diǎn)110刪除,然后這個(gè)時(shí)候需要交換兄弟結(jié)點(diǎn)140和父親結(jié)點(diǎn)120的顏色,并且把父親結(jié)點(diǎn)120涂成黑色,把兄弟結(jié)點(diǎn)的子節(jié)點(diǎn)150涂成黑色
如果兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子都在右子樹(shù)的話:對(duì)父親結(jié)點(diǎn)進(jìn)行左旋如果兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子都在左子樹(shù)的話:對(duì)父親結(jié)點(diǎn)進(jìn)行右旋
上圖是第一種情況,所以對(duì)父結(jié)點(diǎn)120進(jìn)行左旋,結(jié)果如下:

通過(guò)對(duì)某些結(jié)點(diǎn)重新著色和旋轉(zhuǎn),又將該樹(shù)變成了一個(gè)標(biāo)準(zhǔn)的紅黑樹(shù)了
4)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有一個(gè)孩子不空,并且該孩子和兄弟結(jié)點(diǎn)不在同一邊(右左或者左右的情況)
假設(shè)我們現(xiàn)在要?jiǎng)h除的結(jié)點(diǎn)是80結(jié)點(diǎn),其兄弟結(jié)點(diǎn)只有一個(gè)兒子,而且兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子是左右的情況(兄弟結(jié)點(diǎn)為左結(jié)點(diǎn),兄弟結(jié)點(diǎn)的兒子為右結(jié)點(diǎn)),符合上述要求,原圖如下:

現(xiàn)在我們先將需要?jiǎng)h除的80結(jié)點(diǎn)刪除,然后將兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)顏色互換
如果兄弟結(jié)點(diǎn)是左子樹(shù),兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)是右子樹(shù):對(duì)兄弟結(jié)點(diǎn)進(jìn)行左旋
如果兄弟結(jié)點(diǎn)是右子樹(shù),兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)是左子樹(shù):對(duì)兄弟結(jié)點(diǎn)進(jìn)行右旋
上圖的情況是進(jìn)行左旋,也就是對(duì)兄弟結(jié)點(diǎn)30進(jìn)行左旋,結(jié)果如下圖:

注意??!,現(xiàn)在還沒(méi)有結(jié)束變換,我們發(fā)現(xiàn)變換之后的紅黑樹(shù)和情況3中的情況很相似,兄弟結(jié)點(diǎn)50和兄弟結(jié)點(diǎn)的子節(jié)點(diǎn)30處在同一邊,我們可以按照情況3的處理辦法進(jìn)行處理:
交換兄弟結(jié)點(diǎn)50和父親結(jié)點(diǎn)60的顏色,把父親結(jié)點(diǎn)60和兄弟結(jié)點(diǎn)的子節(jié)點(diǎn)30涂成黑色
如果兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子都在右子樹(shù)的話:對(duì)父親結(jié)點(diǎn)進(jìn)行左旋如果兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子都在左子樹(shù)的話,對(duì)父親結(jié)點(diǎn)進(jìn)行右旋
上圖的情況是第2中,所以對(duì)父親結(jié)點(diǎn)60進(jìn)行右旋,結(jié)果如下:

5)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有兩個(gè)孩子,兄弟結(jié)點(diǎn)為黑色而且兩個(gè)孩子結(jié)點(diǎn)也為黑色
現(xiàn)在我們假設(shè)要?jiǎng)h除的結(jié)點(diǎn)是130結(jié)點(diǎn),其兄弟結(jié)點(diǎn)有兩個(gè)孩子(可以把空的葉子結(jié)點(diǎn)看成黑色的兒子結(jié)點(diǎn)),而且兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)都是黑色,符合上述情況,原圖如下:

先直接刪除需要?jiǎng)h除的結(jié)點(diǎn)130,然后將父親結(jié)點(diǎn)140和兄弟結(jié)點(diǎn)150顏色互換即可,結(jié)果如下:

6)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有兩個(gè)孩子,而且兄弟結(jié)點(diǎn)為紅色
假設(shè)我們要?jiǎng)h除的結(jié)點(diǎn)是110,其兄弟結(jié)點(diǎn)140為紅色而且有兩個(gè)孩子,原圖如下:

我們先交換兄弟結(jié)點(diǎn)140和父親結(jié)點(diǎn)120的顏色
1.被刪除的元素為左子樹(shù):對(duì)父親結(jié)點(diǎn)左旋
2.被刪除的元素為右子樹(shù):對(duì)父親結(jié)點(diǎn)右旋
上圖的情況是第一種情況,所以我們對(duì)父親結(jié)點(diǎn)140進(jìn)行左旋,按照上面操作之后(未完),結(jié)果如下:

我們發(fā)現(xiàn)完成上述操作之后樹(shù)還不是一個(gè)標(biāo)準(zhǔn)的紅黑樹(shù)(到葉子結(jié)點(diǎn)的一條路徑黑色結(jié)點(diǎn)只有3個(gè),而其他的路徑有4個(gè)),我們發(fā)現(xiàn)現(xiàn)在紅黑樹(shù)的情況又和情況5的很像,所以我們按照情況5的做法繼續(xù):
我們要需要?jiǎng)h除的結(jié)點(diǎn)還沒(méi)有被刪除(我特意留到最后刪除的,就是為了在這里表示父親結(jié)點(diǎn)是誰(shuí)的父親結(jié)點(diǎn)…),現(xiàn)在我們將父親結(jié)點(diǎn)120和兄弟結(jié)點(diǎn)130的顏色互換即可,結(jié)果如下:

我們現(xiàn)在對(duì)刪除結(jié)點(diǎn)沒(méi)有兒子結(jié)點(diǎn)的6種刪除情況進(jìn)行一下總結(jié):
刪除結(jié)點(diǎn)沒(méi)有兒子結(jié)點(diǎn):
1)刪除結(jié)點(diǎn)為紅色:
直接刪除
2)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)沒(méi)有兒子:
兄弟結(jié)點(diǎn)變紅,父親結(jié)點(diǎn)變黑,然后將父親結(jié)點(diǎn)當(dāng)作當(dāng)前結(jié)點(diǎn)按照這幾種情形處理,直到當(dāng)前結(jié)點(diǎn)為根結(jié)點(diǎn)
3)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有一個(gè)孩子不空,并且該孩子和兄弟結(jié)點(diǎn)在同一邊(同為左子樹(shù)或者同為右子樹(shù)):
1.不管是括號(hào)中那種情況,先交換兄弟結(jié)點(diǎn)和父親結(jié)點(diǎn)的顏色,并且把父親結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的子結(jié)點(diǎn)涂成黑色
2.1如果兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子都在右子樹(shù)的話:對(duì)父親結(jié)點(diǎn)進(jìn)行左旋
2.2如果兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子都在左子樹(shù)的話:對(duì)父親結(jié)點(diǎn)進(jìn)行右旋
4)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有一個(gè)孩子不空,并且該孩子和兄弟結(jié)點(diǎn)不在同一邊(右左或者左右的情況):
1.先將兄弟結(jié)點(diǎn)和兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)顏色互換
2.1如果兄弟結(jié)點(diǎn)是左子樹(shù),兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)是右子樹(shù):對(duì)兄弟結(jié)點(diǎn)進(jìn)行左旋
2.2如果兄弟結(jié)點(diǎn)是右子樹(shù),兄弟結(jié)點(diǎn)的兒子結(jié)點(diǎn)是左子樹(shù):對(duì)兄弟結(jié)點(diǎn)進(jìn)行右旋
3.將后續(xù)變換按照第3條處理
5)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有兩個(gè)孩子,兄弟結(jié)點(diǎn)為黑色而且兩個(gè)孩子結(jié)點(diǎn)也為黑色:
1.將父親結(jié)點(diǎn)和兄弟結(jié)點(diǎn)顏色互換
6)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)有兩個(gè)孩子,而且兄弟結(jié)點(diǎn)為紅色:
1.將兄弟結(jié)點(diǎn)和父親結(jié)點(diǎn)的顏色互換
2.1 被刪除的元素為左子樹(shù):對(duì)父親結(jié)點(diǎn)左旋
2.2 被刪除的元素為右子樹(shù):對(duì)父親結(jié)點(diǎn)右旋
3.將后續(xù)變換按照第5條進(jìn)行處理
以上6種情況討論的都是刪除結(jié)點(diǎn)沒(méi)有兒子的情況(空葉子結(jié)點(diǎn)不算兒子結(jié)點(diǎn))
現(xiàn)在我們來(lái)看看刪除結(jié)點(diǎn)僅有一個(gè)兒子結(jié)點(diǎn)的情況!
刪除結(jié)點(diǎn)僅有一個(gè)兒子結(jié)點(diǎn)的情況
1)刪除結(jié)點(diǎn)為黑色,兒子結(jié)點(diǎn)無(wú)論左右都可以
比如我們要?jiǎng)h除的結(jié)點(diǎn)是120結(jié)點(diǎn),刪除結(jié)點(diǎn)為黑色,唯一的兒子結(jié)點(diǎn)130為紅色(必須是紅色,不然違背紅黑樹(shù)第5條性質(zhì))原圖如下:

我們將需要?jiǎng)h除的結(jié)點(diǎn)120刪除,然后將子節(jié)點(diǎn)130涂黑放到被刪除結(jié)點(diǎn)120的位置,結(jié)果如下:

2)刪除結(jié)點(diǎn)為紅色:其兒子結(jié)點(diǎn)只能為黑,紅黑樹(shù)中不存在這種情況,要不然無(wú)法滿足紅黑樹(shù)第5條性質(zhì)
總結(jié)一下刪除結(jié)點(diǎn)只有一個(gè)兒子的情況:
1)刪除結(jié)點(diǎn)為黑色,兒子結(jié)點(diǎn)無(wú)論左右都可以(將兒子結(jié)點(diǎn)涂成黑色放到被刪除結(jié)點(diǎn)的位置)
下面我們來(lái)看看刪除結(jié)點(diǎn)有兩個(gè)兒子結(jié)點(diǎn)的情況
刪除結(jié)點(diǎn)有兩個(gè)兒子結(jié)點(diǎn)
找到刪除結(jié)點(diǎn)的右子樹(shù)中最左的結(jié)點(diǎn),兩兩值交換,然后刪除結(jié)點(diǎn)的情況就變成了上面兩種情況中的一種了
刪除結(jié)點(diǎn)只有一個(gè)兒子的情況
刪除結(jié)點(diǎn)沒(méi)有兒子的情況
比如下圖

假設(shè)要?jiǎng)h除的結(jié)點(diǎn)是120,先找到結(jié)點(diǎn)120右子樹(shù)中最左的結(jié)點(diǎn)125,交換兩者的值,圖如下:

現(xiàn)在120仍然是要?jiǎng)h除的結(jié)點(diǎn),我們發(fā)現(xiàn)刪除結(jié)點(diǎn)120沒(méi)有一個(gè)兒子,而且其兄弟結(jié)點(diǎn)也沒(méi)有兒子,那么其對(duì)應(yīng)的情況為:
2)刪除結(jié)點(diǎn)為黑色,其兄弟結(jié)點(diǎn)沒(méi)有兒子:兄弟結(jié)點(diǎn)變紅,父親結(jié)點(diǎn)變黑
經(jīng)過(guò)上面的變形,結(jié)果如下:

經(jīng)過(guò)變換,該樹(shù)變成了一顆標(biāo)準(zhǔn)的紅黑樹(shù)
所以當(dāng)刪除結(jié)點(diǎn)右兩個(gè)兒子結(jié)點(diǎn)的時(shí)候,我們只需要按照搜索二叉樹(shù)的刪除方法替換刪除值,這樣就可以將情況變成刪除結(jié)點(diǎn)沒(méi)有兒子結(jié)點(diǎn)或者1個(gè)兒子結(jié)點(diǎn)的情況處理了
紅黑樹(shù)動(dòng)態(tài)可視化網(wǎng)站
https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
紅黑樹(shù)參考代碼
package com.tree.rbtree;
/**
* Java 語(yǔ)言: 紅黑樹(shù)
*
* @author huanmin
*/
public class RBTree<T extends Comparable<T>> {
private RBTNode<T> mRoot; // 根結(jié)點(diǎn)
private static final boolean RED = false;
private static final boolean BLACK = true;
public class RBTNode<T extends Comparable<T>> {
boolean color; // 顏色
T key; // 關(guān)鍵字(鍵值)
RBTNode<T> left; // 左孩子
RBTNode<T> right; // 右孩子
RBTNode<T> parent; // 父結(jié)點(diǎn)
public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getKey() {
return key;
}
@Override
public String toString() {
return ""+key+(this.color==RED?"(R)":"B");
}
}
public RBTree() {
mRoot=null;
}
private RBTNode<T> parentOf(RBTNode<T> node) {
return node!=null ? node.parent : null;
}
private boolean colorOf(RBTNode<T> node) {
return node!=null ? node.color : BLACK;
}
private boolean isRed(RBTNode<T> node) {
return ((node!=null)&&(node.color==RED)) ? true : false;
}
private boolean isBlack(RBTNode<T> node) {
return !isRed(node);
}
private void setBlack(RBTNode<T> node) {
if (node!=null) {
node.color = BLACK;
}
}
private void setRed(RBTNode<T> node) {
if (node!=null) {
node.color = RED;
}
}
private void setParent(RBTNode<T> node, RBTNode<T> parent) {
if (node!=null) {
node.parent = parent;
}
}
private void setColor(RBTNode<T> node, boolean color) {
if (node!=null) {
node.color = color;
}
}
/*
* 前序遍歷"紅黑樹(shù)"
*/
private void preOrder(RBTNode<T> tree) {
if(tree != null) {
System.out.print(tree.key+" ");
preOrder(tree.left);
preOrder(tree.right);
}
}
public void preOrder() {
preOrder(mRoot);
}
/*
* 中序遍歷"紅黑樹(shù)"
*/
private void inOrder(RBTNode<T> tree) {
if(tree != null) {
inOrder(tree.left);
System.out.print(tree.key+" ");
inOrder(tree.right);
}
}
public void inOrder() {
inOrder(mRoot);
}
/*
* 后序遍歷"紅黑樹(shù)"
*/
private void postOrder(RBTNode<T> tree) {
if(tree != null)
{
postOrder(tree.left);
postOrder(tree.right);
System.out.print(tree.key+" ");
}
}
public void postOrder() {
postOrder(mRoot);
}
/*
* (遞歸實(shí)現(xiàn))查找"紅黑樹(shù)x"中鍵值為key的節(jié)點(diǎn)
*/
private RBTNode<T> search(RBTNode<T> x, T key) {
if (x==null) {
return x;
}
int cmp = key.compareTo(x.key);
if (cmp < 0) {
return search(x.left, key);
} else if (cmp > 0) {
return search(x.right, key);
} else {
return x;
}
}
public RBTNode<T> search(T key) {
return search(mRoot, key);
}
/*
* (非遞歸實(shí)現(xiàn))查找"紅黑樹(shù)x"中鍵值為key的節(jié)點(diǎn)
*/
private RBTNode<T> iterativeSearch(RBTNode<T> x, T key) {
while (x!=null) {
int cmp = key.compareTo(x.key);
if (cmp < 0) {
x = x.left;
} else if (cmp > 0) {
x = x.right;
} else {
return x;
}
}
return x;
}
public RBTNode<T> iterativeSearch(T key) {
return iterativeSearch(mRoot, key);
}
/*
* 查找最小結(jié)點(diǎn):返回tree為根結(jié)點(diǎn)的紅黑樹(shù)的最小結(jié)點(diǎn)。
*/
private RBTNode<T> minimum(RBTNode<T> tree) {
if (tree == null) {
return null;
}
while(tree.left != null) {
tree = tree.left;
}
return tree;
}
public T minimum() {
RBTNode<T> p = minimum(mRoot);
if (p != null) {
return p.key;
}
return null;
}
/*
* 查找最大結(jié)點(diǎn):返回tree為根結(jié)點(diǎn)的紅黑樹(shù)的最大結(jié)點(diǎn)。
*/
private RBTNode<T> maximum(RBTNode<T> tree) {
if (tree == null) {
return null;
}
while(tree.right != null) {
tree = tree.right;
}
return tree;
}
public T maximum() {
RBTNode<T> p = maximum(mRoot);
if (p != null) {
return p.key;
}
return null;
}
/*
* 找結(jié)點(diǎn)(x)的后繼結(jié)點(diǎn)。即,查找"紅黑樹(shù)中數(shù)據(jù)值大于該結(jié)點(diǎn)"的"最小結(jié)點(diǎn)"。
*/
public RBTNode<T> successor(RBTNode<T> x) {
// 如果x存在右孩子,則"x的后繼結(jié)點(diǎn)"為 "以其右孩子為根的子樹(shù)的最小結(jié)點(diǎn)"。
if (x.right != null) {
return minimum(x.right);
}
// 如果x沒(méi)有右孩子。則x有以下兩種可能:
// (01) x是"一個(gè)左孩子",則"x的后繼結(jié)點(diǎn)"為 "它的父結(jié)點(diǎn)"。
// (02) x是"一個(gè)右孩子",則查找"x的最低的父結(jié)點(diǎn),并且該父結(jié)點(diǎn)要具有左孩子",找到的這個(gè)"最低的父結(jié)點(diǎn)"就是"x的后繼結(jié)點(diǎn)"。
RBTNode<T> y = x.parent;
while ((y!=null) && (x==y.right)) {
x = y;
y = y.parent;
}
return y;
}
/*
* 找結(jié)點(diǎn)(x)的前驅(qū)結(jié)點(diǎn)。即,查找"紅黑樹(shù)中數(shù)據(jù)值小于該結(jié)點(diǎn)"的"最大結(jié)點(diǎn)"。
*/
public RBTNode<T> predecessor(RBTNode<T> x) {
// 如果x存在左孩子,則"x的前驅(qū)結(jié)點(diǎn)"為 "以其左孩子為根的子樹(shù)的最大結(jié)點(diǎn)"。
if (x.left != null) {
return maximum(x.left);
}
// 如果x沒(méi)有左孩子。則x有以下兩種可能:
// (01) x是"一個(gè)右孩子",則"x的前驅(qū)結(jié)點(diǎn)"為 "它的父結(jié)點(diǎn)"。
// (01) x是"一個(gè)左孩子",則查找"x的最低的父結(jié)點(diǎn),并且該父結(jié)點(diǎn)要具有右孩子",找到的這個(gè)"最低的父結(jié)點(diǎn)"就是"x的前驅(qū)結(jié)點(diǎn)"。
RBTNode<T> y = x.parent;
while ((y!=null) && (x==y.left)) {
x = y;
y = y.parent;
}
return y;
}
/*
* 對(duì)紅黑樹(shù)的節(jié)點(diǎn)(x)進(jìn)行左旋轉(zhuǎn)
*
* 左旋示意圖(對(duì)節(jié)點(diǎn)x進(jìn)行左旋):
* px px
* / /
* x y
* / \ --(左旋)-. / \ #
* lx y x ry
* / \ / \
* ly ry lx ly
*
*
*/
private void leftRotate(RBTNode<T> x) {
// 設(shè)置x的右孩子為y
RBTNode<T> y = x.right;
// 將 “y的左孩子” 設(shè)為 “x的右孩子”;
// 如果y的左孩子非空,將 “x” 設(shè)為 “y的左孩子的父親”
x.right = y.left;
if (y.left != null) {
y.left.parent = x;
}
// 將 “x的父親” 設(shè)為 “y的父親”
y.parent = x.parent;
if (x.parent == null) {
this.mRoot = y; // 如果 “x的父親” 是空節(jié)點(diǎn),則將y設(shè)為根節(jié)點(diǎn)
} else {
if (x.parent.left == x) {
x.parent.left = y; // 如果 x是它父節(jié)點(diǎn)的左孩子,則將y設(shè)為“x的父節(jié)點(diǎn)的左孩子”
} else {
x.parent.right = y; // 如果 x是它父節(jié)點(diǎn)的左孩子,則將y設(shè)為“x的父節(jié)點(diǎn)的左孩子”
}
}
// 將 “x” 設(shè)為 “y的左孩子”
y.left = x;
// 將 “x的父節(jié)點(diǎn)” 設(shè)為 “y”
x.parent = y;
}
/*
* 對(duì)紅黑樹(shù)的節(jié)點(diǎn)(y)進(jìn)行右旋轉(zhuǎn)
*
* 右旋示意圖(對(duì)節(jié)點(diǎn)y進(jìn)行左旋):
* py py
* / /
* y x
* / \ --(右旋)-. / \ #
* x ry lx y
* / \ / \ #
* lx rx rx ry
*
*/
private void rightRotate(RBTNode<T> y) {
// 設(shè)置x是當(dāng)前節(jié)點(diǎn)的左孩子。
RBTNode<T> x = y.left;
// 將 “x的右孩子” 設(shè)為 “y的左孩子”;
// 如果"x的右孩子"不為空的話,將 “y” 設(shè)為 “x的右孩子的父親”
y.left = x.right;
if (x.right != null) {
x.right.parent = y;
}
// 將 “y的父親” 設(shè)為 “x的父親”
x.parent = y.parent;
if (y.parent == null) {
this.mRoot = x; // 如果 “y的父親” 是空節(jié)點(diǎn),則將x設(shè)為根節(jié)點(diǎn)
} else {
if (y == y.parent.right) {
y.parent.right = x; // 如果 y是它父節(jié)點(diǎn)的右孩子,則將x設(shè)為“y的父節(jié)點(diǎn)的右孩子”
} else {
y.parent.left = x; // (y是它父節(jié)點(diǎn)的左孩子) 將x設(shè)為“x的父節(jié)點(diǎn)的左孩子”
}
}
// 將 “y” 設(shè)為 “x的右孩子”
x.right = y;
// 將 “y的父節(jié)點(diǎn)” 設(shè)為 “x”
y.parent = x;
}
/*
* 紅黑樹(shù)插入修正函數(shù)
*
* 在向紅黑樹(shù)中插入節(jié)點(diǎn)之后(失去平衡),再調(diào)用該函數(shù);
* 目的是將它重新塑造成一顆紅黑樹(shù)。
*
* 參數(shù)說(shuō)明:
* node 插入的結(jié)點(diǎn) // 對(duì)應(yīng)《算法導(dǎo)論》中的z
*/
private void insertFixUp(RBTNode<T> node) {
RBTNode<T> parent, gparent;
// 若“父節(jié)點(diǎn)存在,并且父節(jié)點(diǎn)的顏色是紅色”
while (((parent = parentOf(node))!=null) && isRed(parent)) {
gparent = parentOf(parent);
//若“父節(jié)點(diǎn)”是“祖父節(jié)點(diǎn)的左孩子”
if (parent == gparent.left) {
// Case 1條件:叔叔節(jié)點(diǎn)是紅色
RBTNode<T> uncle = gparent.right;
if ((uncle!=null) && isRed(uncle)) {
setBlack(uncle);
setBlack(parent);
setRed(gparent);
node = gparent;
continue;
}
// Case 2條件:叔叔是黑色,且當(dāng)前節(jié)點(diǎn)是右孩子
if (parent.right == node) {
RBTNode<T> tmp;
leftRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
// Case 3條件:叔叔是黑色,且當(dāng)前節(jié)點(diǎn)是左孩子。
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
} else { //若“z的父節(jié)點(diǎn)”是“z的祖父節(jié)點(diǎn)的右孩子”
// Case 1條件:叔叔節(jié)點(diǎn)是紅色
RBTNode<T> uncle = gparent.left;
if ((uncle!=null) && isRed(uncle)) {
setBlack(uncle);
setBlack(parent);
setRed(gparent);
node = gparent;
continue;
}
// Case 2條件:叔叔是黑色,且當(dāng)前節(jié)點(diǎn)是左孩子
if (parent.left == node) {
RBTNode<T> tmp;
rightRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
// Case 3條件:叔叔是黑色,且當(dāng)前節(jié)點(diǎn)是右孩子。
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
}
}
// 將根節(jié)點(diǎn)設(shè)為黑色
setBlack(this.mRoot);
}
/*
* 將結(jié)點(diǎn)插入到紅黑樹(shù)中
*
* 參數(shù)說(shuō)明:
* node 插入的結(jié)點(diǎn) // 對(duì)應(yīng)《算法導(dǎo)論》中的node
*/
private void insert(RBTNode<T> node) {
int cmp;
RBTNode<T> y = null;
RBTNode<T> x = this.mRoot;
// 1. 將紅黑樹(shù)當(dāng)作一顆二叉查找樹(shù),將節(jié)點(diǎn)添加到二叉查找樹(shù)中。
while (x != null) {
y = x;
cmp = node.key.compareTo(x.key);
if (cmp < 0) {
x = x.left;
} else {
x = x.right;
}
}
node.parent = y;
if (y!=null) {
cmp = node.key.compareTo(y.key);
if (cmp < 0) {
y.left = node;
} else {
y.right = node;
}
} else {
this.mRoot = node;
}
// 2. 設(shè)置節(jié)點(diǎn)的顏色為紅色
node.color = RED;
// 3. 將它重新修正為一顆二叉查找樹(shù)
insertFixUp(node);
}
/*
* 新建結(jié)點(diǎn)(key),并將其插入到紅黑樹(shù)中
*
* 參數(shù)說(shuō)明:
* key 插入結(jié)點(diǎn)的鍵值
*/
public void insert(T key) {
RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);
// 如果新建結(jié)點(diǎn)失敗,則返回。
if (node != null) {
insert(node);
}
}
/*
* 紅黑樹(shù)刪除修正函數(shù)
*
* 在從紅黑樹(shù)中刪除插入節(jié)點(diǎn)之后(紅黑樹(shù)失去平衡),再調(diào)用該函數(shù);
* 目的是將它重新塑造成一顆紅黑樹(shù)。
*
* 參數(shù)說(shuō)明:
* node 待修正的節(jié)點(diǎn)
*/
private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {
RBTNode<T> other;
while ((node==null || isBlack(node)) && (node != this.mRoot)) {
if (parent.left == node) {
other = parent.right;
if (isRed(other)) {
// Case 1: x的兄弟w是紅色的
setBlack(other);
setRed(parent);
leftRotate(parent);
other = parent.right;
}
if ((other.left==null || isBlack(other.left)) &&
(other.right==null || isBlack(other.right))) {
// Case 2: x的兄弟w是黑色,且w的倆個(gè)孩子也都是黑色的
setRed(other);
node = parent;
parent = parentOf(node);
} else {
if (other.right==null || isBlack(other.right)) {
// Case 3: x的兄弟w是黑色的,并且w的左孩子是紅色,右孩子為黑色。
setBlack(other.left);
setRed(other);
rightRotate(other);
other = parent.right;
}
// Case 4: x的兄弟w是黑色的;并且w的右孩子是紅色的,左孩子任意顏色。
setColor(other, colorOf(parent));
setBlack(parent);
setBlack(other.right);
leftRotate(parent);
node = this.mRoot;
break;
}
} else {
other = parent.left;
if (isRed(other)) {
// Case 1: x的兄弟w是紅色的
setBlack(other);
setRed(parent);
rightRotate(parent);
other = parent.left;
}
if ((other.left==null || isBlack(other.left)) &&
(other.right==null || isBlack(other.right))) {
// Case 2: x的兄弟w是黑色,且w的倆個(gè)孩子也都是黑色的
setRed(other);
node = parent;
parent = parentOf(node);
} else {
if (other.left==null || isBlack(other.left)) {
// Case 3: x的兄弟w是黑色的,并且w的左孩子是紅色,右孩子為黑色。
setBlack(other.right);
setRed(other);
leftRotate(other);
other = parent.left;
}
// Case 4: x的兄弟w是黑色的;并且w的右孩子是紅色的,左孩子任意顏色。
setColor(other, colorOf(parent));
setBlack(parent);
setBlack(other.left);
rightRotate(parent);
node = this.mRoot;
break;
}
}
}
if (node!=null) {
setBlack(node);
}
}
/*
* 刪除結(jié)點(diǎn)(node),并返回被刪除的結(jié)點(diǎn)
*
* 參數(shù)說(shuō)明:
* node 刪除的結(jié)點(diǎn)
*/
private void
remove(RBTNode<T> node) {
RBTNode<T> child, parent;
boolean color;
// 被刪除節(jié)點(diǎn)的"左右孩子都不為空"的情況。
if ( (node.left!=null) && (node.right!=null) ) {
// 被刪節(jié)點(diǎn)的后繼節(jié)點(diǎn)。(稱為"取代節(jié)點(diǎn)")
// 用它來(lái)取代"被刪節(jié)點(diǎn)"的位置,然后再將"被刪節(jié)點(diǎn)"去掉。
RBTNode<T> replace = node;
// 獲取后繼節(jié)點(diǎn)
replace = replace.right;
while (replace.left != null) {
replace = replace.left;
}
// "node節(jié)點(diǎn)"不是根節(jié)點(diǎn)(只有根節(jié)點(diǎn)不存在父節(jié)點(diǎn))
if (parentOf(node)!=null) {
if (parentOf(node).left == node) {
parentOf(node).left = replace;
} else {
parentOf(node).right = replace;
}
} else {
// "node節(jié)點(diǎn)"是根節(jié)點(diǎn),更新根節(jié)點(diǎn)。
this.mRoot = replace;
}
// child是"取代節(jié)點(diǎn)"的右孩子,也是需要"調(diào)整的節(jié)點(diǎn)"。
// "取代節(jié)點(diǎn)"肯定不存在左孩子!因?yàn)樗且粋€(gè)后繼節(jié)點(diǎn)。
child = replace.right;
parent = parentOf(replace);
// 保存"取代節(jié)點(diǎn)"的顏色
color = colorOf(replace);
// "被刪除節(jié)點(diǎn)"是"它的后繼節(jié)點(diǎn)的父節(jié)點(diǎn)"
if (parent == node) {
parent = replace;
} else {
// child不為空
if (child!=null) {
setParent(child, parent);
}
parent.left = child;
replace.right = node.right;
setParent(node.right, replace);
}
replace.parent = node.parent;
replace.color = node.color;
replace.left = node.left;
node.left.parent = replace;
if (color == BLACK) {
removeFixUp(child, parent);
}
node = null;
return ;
}
if (node.left !=null) {
child = node.left;
} else {
child = node.right;
}
parent = node.parent;
// 保存"取代節(jié)點(diǎn)"的顏色
color = node.color;
if (child!=null) {
child.parent = parent;
}
// "node節(jié)點(diǎn)"不是根節(jié)點(diǎn)
if (parent!=null) {
if (parent.left == node) {
parent.left = child;
} else {
parent.right = child;
}
} else {
this.mRoot = child;
}
if (color == BLACK) {
removeFixUp(child, parent);
}
node = null;
}
/*
* 刪除結(jié)點(diǎn)(z),并返回被刪除的結(jié)點(diǎn)
*
* 參數(shù)說(shuō)明:
* tree 紅黑樹(shù)的根結(jié)點(diǎn)
* z 刪除的結(jié)點(diǎn)
*/
public void remove(T key) {
RBTNode<T> node;
if ((node = search(mRoot, key)) != null) {
remove(node);
}
}
/*
* 銷(xiāo)毀紅黑樹(shù)
*/
private void destroy(RBTNode<T> tree) {
if (tree==null) {
return ;
}
if (tree.left != null) {
destroy(tree.left);
}
if (tree.right != null) {
destroy(tree.right);
}
tree=null;
}
public void clear() {
destroy(mRoot);
mRoot = null;
}
/*
* 打印"紅黑樹(shù)"
*
* key -- 節(jié)點(diǎn)的鍵值
* direction -- 0,表示該節(jié)點(diǎn)是根節(jié)點(diǎn);
* -1,表示該節(jié)點(diǎn)是它的父結(jié)點(diǎn)的左孩子;
* 1,表示該節(jié)點(diǎn)是它的父結(jié)點(diǎn)的右孩子。
*/
private void print(RBTNode<T> tree, T key, int direction) {
if(tree != null) {
if(direction==0) // tree是根節(jié)點(diǎn)
{
System.out.printf("%2d(B) is root\n", tree.key);
} else // tree是分支節(jié)點(diǎn)
{
System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B", key, direction==1?"right" : "left");
}
print(tree.left, tree.key, -1);
print(tree.right,tree.key, 1);
}
}
public void print() {
if (mRoot != null) {
print(mRoot, mRoot.key, 0);
}
}
}以上就是Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹(shù)的原理及實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Java紅黑樹(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java利用自定義注解、反射實(shí)現(xiàn)簡(jiǎn)單BaseDao實(shí)例
下面小編就為大家?guī)?lái)一篇Java利用自定義注解、反射實(shí)現(xiàn)簡(jiǎn)單BaseDao實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
JAVA抽象類(lèi)和抽象方法(abstract)實(shí)例分析
這篇文章主要介紹了JAVA抽象類(lèi)和抽象方法(abstract),結(jié)合實(shí)例形式分析了java抽象類(lèi)及抽象方法相關(guān)定義、使用技巧與操作注意事項(xiàng),需要的朋友可以參考下2019-11-11
Spring Boot簡(jiǎn)單實(shí)現(xiàn)快速搭建圖解
這篇文章主要介紹了Spring Boot簡(jiǎn)單實(shí)現(xiàn)快速搭建圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Java 位運(yùn)算符>>與>>>區(qū)別案例詳解
這篇文章主要介紹了Java 位運(yùn)算符>>與>>>區(qū)別案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08

