一篇文章讓你徹底弄懂紅黑樹的原理

首先,在閱讀文章之前,我希望讀者對(duì)二叉樹有一定的了解懊蒸,因?yàn)榧t黑樹的本質(zhì)就是一顆二叉樹拇泛。所以本篇博客中不在將二叉樹的增刪查的基本操作了滨巴,需要了解的同學(xué)可以到我之前寫的一篇關(guān)于二叉樹基本操作的博客:https://www.cnblogs.com/rainple/p/9970760.html

有隨機(jī)數(shù)節(jié)點(diǎn)組成的二叉樹的平均高度為logn俺叭,所以正常情況下二叉樹查找的時(shí)間復(fù)雜度為O(logn)恭取。但是,根據(jù)二叉樹的特性熄守,在最壞的情況下蜈垮,比如存儲(chǔ)的是一個(gè)有序的數(shù)據(jù)的話耗跛,那么所以的數(shù)據(jù)都會(huì)形成一條鏈,此時(shí)二叉樹的深度為n攒发,時(shí)間復(fù)雜度為O(n)调塌。紅黑樹就是為了解決這個(gè)問(wèn)題的,它能夠保證在任何情況下樹的深度都保持在logn左右惠猿,紅黑樹通過(guò)一下約束來(lái)完成這個(gè)特性:

1羔砾、每個(gè)節(jié)點(diǎn)不是紅色就是黑色。

  2偶妖、根節(jié)點(diǎn)為黑色姜凄。

  3、每個(gè)葉子節(jié)點(diǎn)都是黑色的趾访。

  4檀葛、每個(gè)紅色節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色。

  5腹缩、任意節(jié)點(diǎn)屿聋,到其任意葉節(jié)點(diǎn)的所有路徑都包含相同的黑色節(jié)點(diǎn)。

結(jié)構(gòu)如下圖:

  紅黑樹的基本操作包括刪除和添加藏鹊。在刪除或者添加一個(gè)節(jié)點(diǎn)的時(shí)候就有可能打破原有的紅黑樹維持的平衡润讥,那么就需要通過(guò)著色和旋轉(zhuǎn)的方式來(lái)使紅黑樹重新達(dá)到平衡。著色是非常簡(jiǎn)單的盘寡,直接將節(jié)點(diǎn)的顏色改變就可以了楚殿,多以要理解紅黑樹,就必須需要懂得如何進(jìn)行旋轉(zhuǎn)竿痰,旋轉(zhuǎn)又分為左旋和右轉(zhuǎn)脆粥,兩個(gè)操作相反的,所以理解了一個(gè)旋轉(zhuǎn)的操作就很容易理解另一個(gè)旋轉(zhuǎn)了影涉。

  左旋:

  如圖所示变隔,紅色節(jié)點(diǎn)為旋轉(zhuǎn)支點(diǎn),支點(diǎn)往左子樹移動(dòng)即為左旋蟹倾。左旋之后我們可以看到原支點(diǎn)的位置被原支點(diǎn)的右子節(jié)點(diǎn)代替匣缘,新支點(diǎn)的左子節(jié)點(diǎn)變?yōu)榱嗽瓉?lái)為父節(jié)點(diǎn)的原支點(diǎn),新支點(diǎn)的左子節(jié)點(diǎn)變?yōu)樵c(diǎn)的右子節(jié)點(diǎn)鲜棠,因此左旋操作總共右3個(gè)節(jié)點(diǎn)肌厨,以為旋轉(zhuǎn)前的結(jié)構(gòu)舉例,分別為紅色節(jié)點(diǎn)(原支點(diǎn))豁陆,黃色節(jié)點(diǎn)(新支點(diǎn))和L節(jié)點(diǎn)柑爸。Java代碼實(shí)現(xiàn)如下:

~~~

/**

? ? * 左旋

? ? * @param e 支點(diǎn)

? ? */privatevoidleftRotate(Entry e){

? ? ? ? //支點(diǎn)的右子節(jié)點(diǎn)Entry right = e.right;

? ? ? ? //支點(diǎn)右子節(jié)點(diǎn)的左子節(jié)點(diǎn)Entry rightOfLeft = right.left;

? ? ? ? //新舊支點(diǎn)的替換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;

? ? ? ? }

? ? ? ? //將原支點(diǎn)變?yōu)樾轮c(diǎn)的左節(jié)點(diǎn)right.left = e;

? ? ? ? e.parent = right;

? ? ? ? //將新支點(diǎn)的左節(jié)點(diǎn)變?yōu)榫椭c(diǎn)的右節(jié)點(diǎn)e.right = rightOfLeft;

? ? ? ? if(rightOfLeft !=null)

? ? ? ? ? ? rightOfLeft.parent = e;

? ? }

~~~

  因?yàn)樵诩t黑樹中每個(gè)節(jié)點(diǎn)都有一個(gè)指針指向自己的父節(jié)點(diǎn),父節(jié)點(diǎn)也有指針指向子節(jié)點(diǎn)盒音,因?yàn)樵诟膭?dòng)一個(gè)節(jié)點(diǎn)的時(shí)候都需要分別改動(dòng)當(dāng)前節(jié)點(diǎn)和父節(jié)點(diǎn)的指向表鳍,結(jié)合左旋的示意圖馅而,用Java代碼實(shí)現(xiàn)起來(lái)就不會(huì)很困難了。


  右旋

    右旋操作和左旋相反的进胯,兩者互反用爪。依然是紅色作為旋轉(zhuǎn)支點(diǎn),右旋后黃色節(jié)點(diǎn)代替了紅色節(jié)點(diǎn)原來(lái)的位置胁镐,黃色節(jié)點(diǎn)的右節(jié)點(diǎn)旋轉(zhuǎn)后變?yōu)榧t色節(jié)點(diǎn)的左節(jié)點(diǎn)偎血。Java 代碼實(shí)現(xiàn)如下:

/**

? ? * 右旋

? ? * @param e 旋轉(zhuǎn)支點(diǎn)

? ? */privatevoidrightRotate(Entry e){

? ? ? ? //原支點(diǎn)的左節(jié)點(diǎn)Entry left = e.left;

? ? ? ? //原支點(diǎn)的左節(jié)點(diǎn)的右節(jié)點(diǎn)Entry leftOfRight = left.right;

? ? ? ? //新舊支點(diǎn)的替換left.parent = e.parent;

? ? ? ? if(e.parent ==null){//支點(diǎn)的父節(jié)點(diǎn)為根節(jié)點(diǎn)的情況root = left;

? ? ? ? }else{//非跟節(jié)點(diǎn)if(e == e.parent.left)

? ? ? ? ? ? ? ? e.parent.left = left;

? ? ? ? ? ? else? ? ? ? ? ? ? ? e.parent.right = left;

? ? ? ? }

? ? ? ? //將原支點(diǎn)變?yōu)樾轮c(diǎn)的右節(jié)點(diǎn)left.right = e;

? ? ? ? e.parent = left;

? ? ? ? //將新支點(diǎn)未旋轉(zhuǎn)前的右節(jié)點(diǎn)變?yōu)檗D(zhuǎn)換后的原支點(diǎn)的左節(jié)點(diǎn)e.left = leftOfRight;

? ? ? ? if(leftOfRight !=null)

? ? ? ? ? ? leftOfRight.parent = e;

? ? }


  添加節(jié)點(diǎn)

  首先,在進(jìn)入主題之前我們?cè)賮?lái)回顧一下紅黑樹的5個(gè)特點(diǎn):

 1盯漂、每個(gè)節(jié)點(diǎn)不是紅色就是黑色颇玷。

  2、根節(jié)點(diǎn)為黑色就缆。

  3帖渠、每個(gè)葉子節(jié)點(diǎn)都是黑色的。

  4竭宰、每個(gè)紅色節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色空郊。

  5、任意節(jié)點(diǎn)切揭,到其任意葉節(jié)點(diǎn)的所有路徑都包含相同的黑色節(jié)點(diǎn)狞甚。

?  紅黑樹插入節(jié)點(diǎn)與二叉樹是一致的,所以每次添加節(jié)點(diǎn)肯定是添加到葉子節(jié)點(diǎn)上廓旬,具體步驟如下:

?  第一步:將新節(jié)點(diǎn)插入到紅黑樹中哼审。

  第二步:將新節(jié)點(diǎn)設(shè)置為紅色。這里為什么需要設(shè)置成紅色呢孕豹?主要是為了滿足特性5涩盾,這樣在插入節(jié)點(diǎn)后就少解決了一個(gè)沖突,也就少一點(diǎn)麻煩励背。插入完成后春霍,我們來(lái)看一下還有那些特性是有可能發(fā)生沖突的,特性1每個(gè)節(jié)點(diǎn)不是紅色就是黑色的椅野,這明顯沒(méi)有沖突终畅,特性2根節(jié)點(diǎn)為黑色,當(dāng)插入節(jié)點(diǎn)為根節(jié)點(diǎn)的時(shí)候就會(huì)有沖突了竟闪,這種就很簡(jiǎn)單了,直接將根節(jié)點(diǎn)著色為黑色即可杖狼。特性3每個(gè)葉子節(jié)點(diǎn)都是黑色炼蛤,這個(gè)明顯沒(méi)有沖突。特性4每個(gè)紅色節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色的蝶涩,這個(gè)特性就有可能會(huì)沖突理朋,因?yàn)樵诓迦胄鹿?jié)點(diǎn)的時(shí)候我們無(wú)法確定新節(jié)點(diǎn)的父節(jié)點(diǎn)的顏色是黑色的還是紅色絮识,如果新節(jié)點(diǎn)的父節(jié)為黑色,那么就不會(huì)有沖突嗽上,否則就會(huì)違背了特性4次舌。特性5任意節(jié)點(diǎn),到其任意子節(jié)點(diǎn)的所有路徑都包含相同的黑色節(jié)點(diǎn)兽愤,因?yàn)槲覀儾迦氲男鹿?jié)點(diǎn)被著色為紅色彼念,所以并不會(huì)影響到每個(gè)路徑的黑色節(jié)點(diǎn)的數(shù)量,因此也不會(huì)有沖突浅萧。綜上所訴逐沙,那么在插入新節(jié)點(diǎn)的時(shí)候,只有特性4有可能發(fā)生沖突洼畅。

  第三步:平衡紅黑樹吩案,使之成為新的紅黑樹。

  根據(jù)第二部得到的結(jié)論帝簇,我們可以知道只有情況是需要解決沖突的徘郭,那就是新節(jié)點(diǎn)的父節(jié)點(diǎn)為紅色的時(shí)候違背了特性4。接下來(lái)我們將要討論這個(gè)問(wèn)題丧肴,因?yàn)樵谛虏迦胍粋€(gè)節(jié)點(diǎn)之前是一顆已經(jīng)平衡了的紅黑樹残揉,因此根據(jù)特新4,新節(jié)點(diǎn)的祖父節(jié)點(diǎn)必定為黑色闪湾。根據(jù)這種情況冲甘,我們又可以分為以下四種情況:

  情況1:新節(jié)點(diǎn)為左節(jié)點(diǎn),叔叔節(jié)點(diǎn)為紅色途样;

  情況2:新節(jié)點(diǎn)為左節(jié)點(diǎn)江醇,叔叔節(jié)點(diǎn)為黑色;

  情況3:新節(jié)點(diǎn)為右節(jié)點(diǎn)何暇,叔叔節(jié)點(diǎn)為紅色陶夜;

  情況4:新節(jié)點(diǎn)為右節(jié)點(diǎn),叔叔節(jié)點(diǎn)為黑色裆站;

  情況1和情況3的情況是一樣的条辟,所以我們可以將這兩種情況看作是一種情況,這個(gè)情況我們稍后再討論宏胯,然后看一下情況2和情況4羽嫡,通過(guò)左旋就可以轉(zhuǎn)換成情況2。

  綜上所述肩袍,我們可以歸結(jié)為3中情況:

  情況1:叔叔節(jié)點(diǎn)是紅色節(jié)點(diǎn)杭棵;

  情況2:叔叔節(jié)點(diǎn)是黑色節(jié)點(diǎn),新節(jié)點(diǎn)為右節(jié)點(diǎn)氛赐;

  情況3:叔叔節(jié)點(diǎn)是黑色節(jié)點(diǎn)魂爪,新節(jié)點(diǎn)為左節(jié)點(diǎn)先舷;

  上面我也有提到,當(dāng)插入新節(jié)點(diǎn)時(shí)肯定是屬于第一種情況的滓侍,然后2蒋川、3由1轉(zhuǎn)換而來(lái),在此之前我希望你之前已經(jīng)了解過(guò)遞歸的原理和思想撩笆,把局部看作整體的思想捺球,因?yàn)檫@將有助于下面討論的理解。下面我們將要繼續(xù)分析這三種情況浇衬,情況1這種情況處理起來(lái)比比較簡(jiǎn)單懒构,只需要將祖父節(jié)點(diǎn)變?yōu)榧t色節(jié)點(diǎn),父節(jié)點(diǎn)和叔叔節(jié)點(diǎn)變?yōu)楹谏纯稍爬蓿@僅僅只是當(dāng)整個(gè)紅黑樹只有這幾個(gè)節(jié)點(diǎn)的時(shí)候是可以了胆剧,但事實(shí)并非如此,這僅僅只是達(dá)到了局部平衡醉冤。

上圖秩霍,我們看到已經(jīng)達(dá)到了局部的平衡,但是蚁阳,我們還會(huì)有其他的情況铃绒,那就是祖父節(jié)點(diǎn)有可能也會(huì)有父節(jié)點(diǎn)。那么又會(huì)有兩種情況螺捐,1是祖父節(jié)點(diǎn)的父節(jié)點(diǎn)可能是黑色的颠悬,2是可能是紅色的,如果黑色那么整個(gè)紅黑樹就達(dá)到平衡了定血。不知道大家根覺(jué)到了沒(méi)有赔癌,這兩種情況是不是跟新插入一個(gè)節(jié)點(diǎn)的情況是一致的,是不是又回到了插入新節(jié)點(diǎn)的問(wèn)題了澜沟?于是我將局部收到影響的部分畫出來(lái)灾票,如圖:

  圖a就是將情況1從新著色后的部分受影響的節(jié)點(diǎn),當(dāng)然只是其中的一種情況茫虽,此時(shí)我們將已經(jīng)平衡的部分去掉就變成的圖b的情況刊苍,這種情況是不是很熟悉呢?我們的祖父節(jié)點(diǎn)當(dāng)成新節(jié)點(diǎn)濒析,是不是相當(dāng)于上面討論的情況1呢正什?不過(guò)與上面討論的情況不同的是,這里3中可能情況都可能出現(xiàn)号杏,因?yàn)槭迨骞?jié)點(diǎn)有可能為紅色或黑色埠忘。所以這時(shí)候才有可能出現(xiàn)真正的三種情況:

  情況1:叔叔節(jié)點(diǎn)是紅色節(jié)點(diǎn);

  情況2:叔叔節(jié)點(diǎn)是黑色節(jié)點(diǎn)馒索,新節(jié)點(diǎn)為右節(jié)點(diǎn)莹妒;

  情況3:叔叔節(jié)點(diǎn)是黑色節(jié)點(diǎn),新節(jié)點(diǎn)為左節(jié)點(diǎn)绰上;

  如果為情況1的話,我們一層一層的往上平衡就可以了,當(dāng)祖父節(jié)點(diǎn)為根節(jié)點(diǎn)的時(shí)候想括,我們直接將根節(jié)點(diǎn)著色為黑色即可忠寻,因?yàn)樽娓腹?jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色的,所以變?yōu)楹谏笕匀皇瞧胶獾陌俳摇=酉聛?lái)我們來(lái)討論下情況2和3爽哎。

  很明顯的,這兩種情況的右節(jié)點(diǎn)多出了一個(gè)黑色節(jié)點(diǎn)器一,這種情況是在情況1向上著色的時(shí)候造成的课锌,即祖父節(jié)點(diǎn)由黑色節(jié)點(diǎn)變?yōu)榱思t色節(jié)點(diǎn)。情況2以父節(jié)點(diǎn)為支點(diǎn)左旋祈秕,然后將父節(jié)點(diǎn)和新節(jié)點(diǎn)互換可以得到情況3:

  情況3進(jìn)行的操作是渺贤,首先將父節(jié)點(diǎn)著色為黑色,祖父節(jié)點(diǎn)著色為紅色请毛,然后以祖父為支點(diǎn)進(jìn)行右旋

  情況3旋轉(zhuǎn)結(jié)束后整棵紅黑也已經(jīng)重新恢復(fù)平衡了志鞍。單從部分其實(shí)并看不出已經(jīng)平衡了,我們可以將三個(gè)情況連起來(lái)就可以看到了方仿,如下圖:

  上圖中都是以n節(jié)點(diǎn)為參考點(diǎn)的固棚,其余無(wú)關(guān)的節(jié)點(diǎn)就不標(biāo)出來(lái)了。n節(jié)點(diǎn)即為插入節(jié)點(diǎn)仙蚜,但是除了第一次操作n節(jié)點(diǎn)為真正的新節(jié)點(diǎn)此洲,此后的操作所指的n節(jié)點(diǎn)只是有助于我們的理解把他當(dāng)成新節(jié)點(diǎn)。當(dāng)然鳍征,這只是其中的一種情況黍翎,其他其他的情況可以通過(guò)不斷向上旋轉(zhuǎn)或著色,最終也會(huì)達(dá)到這種情況或者頂部是p節(jié)點(diǎn)為根節(jié)點(diǎn)的時(shí)候艳丛,第二種情況直接將根節(jié)點(diǎn)著色為黑色即可匣掸。

  總結(jié):

  回顧一下紅黑樹的5個(gè)特性:

  1、節(jié)點(diǎn)不是紅色就是黑色氮双。

  2碰酝、根節(jié)點(diǎn)為黑色。

  3戴差、葉子節(jié)點(diǎn)為黑色送爸。

  4、每個(gè)紅色節(jié)點(diǎn)其子節(jié)點(diǎn)必須是黑色節(jié)點(diǎn)。

  5袭厂、任意節(jié)點(diǎn)到到其任意的子節(jié)點(diǎn)的所有路徑的黑色節(jié)點(diǎn)的數(shù)量相等墨吓。

  在插入新節(jié)點(diǎn)的時(shí)候很顯然,不會(huì)違背1和3纹磺,如果插入的是根節(jié)點(diǎn)直接將根節(jié)點(diǎn)著色為黑色即可帖烘,這種情況可以忽略不計(jì),所以插入節(jié)點(diǎn)時(shí)可能會(huì)違背了4和5橄杨,又因?yàn)椴迦氲氖羌t色節(jié)點(diǎn)因此5也不會(huì)違背秘症,最后在插入新節(jié)點(diǎn)的時(shí)候我們只需要關(guān)注特性4就可以了。當(dāng)父節(jié)點(diǎn)為紅色的時(shí)候跟4有沖突式矫,所以我們接下來(lái)討論的就是這種情況乡摹。我們知道,在插入新節(jié)點(diǎn)之前整顆紅黑樹是平衡的采转,因此可以得出一個(gè)結(jié)論就是祖父節(jié)點(diǎn)肯定肯定是黑色的聪廉。我們現(xiàn)在只關(guān)注相關(guān)的節(jié)點(diǎn)即可,目前氏义,我們知道了祖父的節(jié)點(diǎn)為黑色锄列,父節(jié)點(diǎn)為紅色,但是叔叔節(jié)點(diǎn)的顏色不知道惯悠,新節(jié)點(diǎn)的位置也不能確定邻邮,所以有2x2中情況,當(dāng)叔叔節(jié)點(diǎn)為紅色的時(shí)候克婶,兩種情況的處理方式是一致的筒严,所以最后我們可以總結(jié)為3中情況:

  1、叔叔節(jié)點(diǎn)為紅色

  2情萤、新節(jié)點(diǎn)為右節(jié)點(diǎn)鸭蛙,叔叔節(jié)點(diǎn)為黑色

  3、新節(jié)點(diǎn)為左節(jié)點(diǎn)筋岛,叔叔節(jié)點(diǎn)為黑色

類型描述步驟示意圖

情況1叔叔節(jié)點(diǎn)為紅色1娶视、父節(jié)點(diǎn)設(shè)為黑色

2、叔叔節(jié)點(diǎn)設(shè)為黑色

3睁宰、祖父節(jié)點(diǎn)設(shè)為紅色

4肪获、把祖父節(jié)點(diǎn)設(shè)置為新節(jié)點(diǎn)(當(dāng)前節(jié)點(diǎn))


情況2新節(jié)點(diǎn)為右節(jié)點(diǎn),叔叔節(jié)點(diǎn)為黑色1柒傻、以父節(jié)點(diǎn)為支點(diǎn)左旋

2孝赫、父節(jié)點(diǎn)和新節(jié)點(diǎn)互換位置

3、把父節(jié)點(diǎn)設(shè)為當(dāng)前節(jié)點(diǎn)


情況3新節(jié)點(diǎn)為左節(jié)點(diǎn)红符,叔叔節(jié)點(diǎn)為黑色1青柄、父節(jié)點(diǎn)設(shè)為黑色

2伐债、祖父節(jié)點(diǎn)設(shè)為紅色

3、以祖父節(jié)點(diǎn)為支點(diǎn)右旋


整合

  樹a就是表中的情況1致开,通過(guò)著色后直接轉(zhuǎn)換成了情況3峰锁,情況3進(jìn)行著色旋轉(zhuǎn)后達(dá)到了平衡,當(dāng)樹b中的叔叔節(jié)點(diǎn)為紅色的時(shí)候與樹a一致喇喉,循環(huán)調(diào)用樹a的處理方式祖今,直至達(dá)到樹b的情況或者樹a中的祖父節(jié)點(diǎn)到達(dá)了根節(jié)點(diǎn),這時(shí)候?qū)⒆娓腹?jié)點(diǎn)設(shè)為黑色即可拣技。

  這種情況就是由情況1轉(zhuǎn)情況2再轉(zhuǎn)情況3,由情況3重新著色旋轉(zhuǎn)后達(dá)到平衡耍目。

  需要注意的是:不是每次插入節(jié)點(diǎn)都會(huì)出現(xiàn)3中情況膏斤,有可能只出現(xiàn)了2和3,或者只出現(xiàn)了3一種情況邪驮。

  上面是討論左子樹的問(wèn)題莫辨,因?yàn)榧t黑色具有堆成性,因此在處理右子樹的時(shí)候與處理左子樹相反即可毅访。Java代碼示例如下:

/**

? ? * 插入新節(jié)點(diǎn)后平衡紅黑樹

? ? * @param e 新節(jié)點(diǎn)

? ? */privatevoidfixAfterInsertion(Entry e) {

? ? ? ? //將新插入節(jié)點(diǎn)設(shè)置為紅色? ? ? ? setRed(e);

? ? ? ? Entry p,g,u;//父節(jié)點(diǎn)和祖父節(jié)點(diǎn)和叔叔節(jié)點(diǎn)Entry current = e;//新節(jié)點(diǎn)/**

? ? ? ? * 這里通過(guò)循環(huán)不斷向上平衡

? ? ? ? */while((p = parentOf(current)) !=null&& isRed(p)){

? ? ? ? ? ? g = parentOf(p);//祖父節(jié)點(diǎn)if(p == g.left){

? ? ? ? ? ? ? ? u = g.right;

? ? ? ? ? ? ? ? //情況1:叔叔節(jié)點(diǎn)為紅色if(u !=null&& isRed(u)){

? ? ? ? ? ? ? ? ? ? setBlack(p);//父節(jié)點(diǎn)設(shè)為黑色setBlack(u);//叔叔節(jié)點(diǎn)設(shè)為黑色setRed(g);//祖父節(jié)點(diǎn)設(shè)為紅色current = g;//把祖父節(jié)點(diǎn)設(shè)為當(dāng)前節(jié)點(diǎn)

? ? ? ? ? ? ? ? ? ? //繼續(xù)向上平衡continue;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //情況2:當(dāng)前節(jié)點(diǎn)為右節(jié)點(diǎn)沮榜,叔叔節(jié)點(diǎn)為黑色if(current == p.right){

? ? ? ? ? ? ? ? ? ? leftRotate(p);//父節(jié)點(diǎn)為支點(diǎn)左旋Entry tmp = p;

? ? ? ? ? ? ? ? ? ? p = current;//父節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)互換current = tmp;//父節(jié)點(diǎn)設(shè)為當(dāng)前節(jié)點(diǎn)? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //情況3:當(dāng)前節(jié)點(diǎn)為左節(jié)點(diǎn),叔叔節(jié)點(diǎn)為黑色setBlack(p);//父節(jié)點(diǎn)設(shè)為黑色setRed(g);//祖父節(jié)點(diǎn)設(shè)為紅色rightRotate(g);//祖父節(jié)點(diǎn)為支點(diǎn)右旋}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 tmp = p;

? ? ? ? ? ? ? ? ? ? p = current;

? ? ? ? ? ? ? ? ? ? current = tmp;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? setBlack(p);

? ? ? ? ? ? ? ? setRed(g);

? ? ? ? ? ? ? ? leftRotate(g);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? //最后將根節(jié)點(diǎn)設(shè)置為紅色? ? ? ? setBlack(root);

? ? }


  刪除節(jié)點(diǎn)

  在二叉樹分析一文中已經(jīng)說(shuō)過(guò)喻粹,刪除一個(gè)節(jié)點(diǎn)的時(shí)候有3中情況:

  1蟆融、刪除節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)

  2、刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn)

  3守呜、刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn)

  首先型酥,我們逐個(gè)來(lái)分析每種情況刪除節(jié)點(diǎn)后對(duì)整顆紅黑樹的平衡性的影響。在刪除節(jié)點(diǎn)時(shí)候紅黑樹的特性1查乒,2弥喉,3肯定不會(huì)違背,所以只需要考慮特性4玛迄,5即可由境。

  對(duì)于情況1,肯定不會(huì)違背特性4蓖议,如果刪除節(jié)點(diǎn)為紅色虏杰,那么對(duì)整顆紅黑樹的平衡性都不會(huì)影響,如果是黑色則違背了特性5拒担,我們先將這種情況記錄下來(lái)嘹屯,稍后再進(jìn)一步討論。

  對(duì)于情況2从撼,有可能刪除的是左子樹或右子樹州弟,暫且不討論钧栖。如果刪除的節(jié)點(diǎn)為紅色,不影響平衡性婆翔,如果刪除的是黑色拯杠,那么肯定會(huì)和特性5有沖突,當(dāng)刪除節(jié)點(diǎn)的父節(jié)點(diǎn)為紅色啃奴,子節(jié)點(diǎn)為紅色是也和特性4有沖突潭陪。

  對(duì)于情況3,其實(shí)最后刪除的是它的替代節(jié)點(diǎn)最蕾,根據(jù)替代節(jié)點(diǎn)的特點(diǎn)依溯,最終其實(shí)是回到了1這種情況或者情況2。

  總結(jié)上面的3種情況可得到一個(gè)結(jié)論瘟则,只有刪除節(jié)點(diǎn)為黑色時(shí)才會(huì)破壞紅黑樹原來(lái)的平衡黎炉,因在刪除節(jié)點(diǎn)之前紅黑樹是出于平衡狀態(tài)的,刪除之后很明顯的其兄弟節(jié)點(diǎn)分支必然比刪除節(jié)點(diǎn)的分支多了一個(gè)黑色的節(jié)點(diǎn)醋拧,因此我們只需要改變兄弟節(jié)點(diǎn)的顏色即可慷嗜,我們只討論左節(jié)點(diǎn),右節(jié)點(diǎn)對(duì)稱丹壕。


  一庆械、刪除節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是紅色

  將兄弟節(jié)點(diǎn)設(shè)為黑色,父節(jié)點(diǎn)設(shè)為紅色菌赖,以父節(jié)點(diǎn)為支點(diǎn)左旋轉(zhuǎn)缭乘,然后將父節(jié)點(diǎn)的右節(jié)點(diǎn)放到兄弟節(jié)點(diǎn)上:

  二、兄弟節(jié)點(diǎn)是黑色的盏袄,兄弟的兩個(gè)子節(jié)點(diǎn)也都是黑色的

  兄弟節(jié)點(diǎn)設(shè)為紅色忿峻,把父節(jié)點(diǎn)設(shè)置為新的刪除節(jié)點(diǎn):

  三、兄弟節(jié)點(diǎn)是黑色的辕羽,且兄弟節(jié)點(diǎn)的左子節(jié)點(diǎn)是紅色逛尚,右子節(jié)點(diǎn)是黑色

  將兄弟節(jié)點(diǎn)的左子節(jié)點(diǎn)設(shè)為黑色,兄弟節(jié)點(diǎn)設(shè)為紅色刁愿,以兄弟節(jié)點(diǎn)為支點(diǎn)右旋绰寞,把父節(jié)點(diǎn)的右節(jié)點(diǎn)設(shè)置為兄弟節(jié)點(diǎn)


四、兄弟節(jié)點(diǎn)是黑色的铣口,且兄弟節(jié)點(diǎn)的右子節(jié)點(diǎn)是紅色滤钱,左子節(jié)點(diǎn)任意顏色

  把兄弟節(jié)點(diǎn)的設(shè)為父節(jié)點(diǎn)的顏色,父節(jié)點(diǎn)設(shè)為黑色脑题,父節(jié)點(diǎn)的右節(jié)點(diǎn)設(shè)為黑色件缸,父節(jié)點(diǎn)為支點(diǎn)左旋

  刪除的Java代碼示例:

public V remove(Object key){

? ? ? ? if(key ==null)returnnull;

? ? ? ? Entry delEntry;

? ? ? ? delEntry = getEntry(key);

? ? ? ? if(delEntry ==null)returnnull;

? ? ? ? size--;

? ? ? ? Entry 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;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }elseif(delEntry.right ==null){//只有左節(jié)點(diǎn)Entry 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;

? ? ? ? ? ? }

? ? ? ? }elseif(delEntry.left ==null){//只有右節(jié)點(diǎn)Entry 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{//有兩個(gè)節(jié)點(diǎn),找到后繼節(jié)點(diǎn),將值賦給刪除節(jié)點(diǎn)叔遂,然后將后繼節(jié)點(diǎn)刪除掉即可Entry successor = successor(delEntry);//獲取到后繼節(jié)點(diǎn)boolean color = successor.color;

? ? ? ? ? ? V old = delEntry.value;

? ? ? ? ? ? delEntry.value = successor.value;

? ? ? ? ? ? delEntry.key = successor.key;

? ? ? ? ? ? if(delEntry.right == successor){//后繼節(jié)點(diǎn)為右子節(jié)點(diǎn)他炊,if(successor.right !=null) {//右子節(jié)點(diǎn)有右子節(jié)點(diǎn)delEntry.right = successor.right;

? ? ? ? ? ? ? ? ? ? successor.right.parent = delEntry;

? ? ? ? ? ? ? ? }else{//右子節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)delEntry.right =null;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }else {

? ? ? ? ? ? ? ? successor.parent.left =null;

? ? ? ? ? ? }

? ? ? ? ? ? if(color == BLACK)

? ? ? ? ? ? ? ? //fixUpAfterRemove(child,parent);return old;

? ? ? ? }

? ? ? ? V old = delEntry.value;

? ? ? ? if(delEntry.color == BLACK)//刪除為黑色時(shí)争剿,需要重新平衡樹if(delEntry.right !=null)//刪除節(jié)點(diǎn)的子節(jié)點(diǎn)只有右節(jié)點(diǎn)? ? ? ? ? ? ? ? fixUpAfterRemove(delEntry.right,delEntry.parent);

? ? ? ? ? ? elseif(delEntry.left !=null)//刪除節(jié)點(diǎn)只有左節(jié)點(diǎn)? ? ? ? ? ? ? ? fixUpAfterRemove(delEntry.left,delEntry.parent);

? ? ? ? ? ? else? ? ? ? ? ? ? ? fixUpAfterRemove(null,delEntry.parent);

? ? ? ? delEntry.parent =null;

? ? ? ? delEntry.left =null;

? ? ? ? delEntry.right =null;

? ? ? ? return old;

? ? }

? ? privateEntry getEntry(Object key) {

? ? ? ? if(key ==null)returnnull;

? ? ? ? Entry delEntry =null;

? ? ? ? Entry current = root;

? ? ? ? int ret;

? ? ? ? if(comparator ==null){

? ? ? ? ? ? Comparable k = (Comparable) key;

? ? ? ? ? ? while(current !=null){

? ? ? ? ? ? ? ? ret = k.compareTo(current.key);

? ? ? ? ? ? ? ? if(ret <0)

? ? ? ? ? ? ? ? ? ? current = current.left;

? ? ? ? ? ? ? ? elseif(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;

? ? ? ? ? ? ? ? elseif(ret >0)

? ? ? ? ? ? ? ? ? ? current = current.right;

? ? ? ? ? ? ? ? else{

? ? ? ? ? ? ? ? ? ? delEntry = current;

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return delEntry;

? ? }

? ? //node表示待修正的節(jié)點(diǎn),即后繼節(jié)點(diǎn)的子節(jié)點(diǎn)(因?yàn)楹罄^節(jié)點(diǎn)被挪到刪除節(jié)點(diǎn)的位置去了)privatevoidfixUpAfterRemove(Entry node,Entry parent) {

? ? ? ? Entry other;

? ? ? ? while((node ==null|| isBlack(node)) && (node != root)) {

? ? ? ? ? ? if(parent.left == node) {//node是左子節(jié)點(diǎn)痊末,下面else與這里的剛好相反other = parent.right;//node的兄弟節(jié)點(diǎn)if(isRed(other)) {//case1: node的兄弟節(jié)點(diǎn)other是紅色的? ? ? ? ? ? ? ? ? ? setBlack(other);

? ? ? ? ? ? ? ? ? ? setRed(parent);

? ? ? ? ? ? ? ? ? ? leftRotate(parent);

? ? ? ? ? ? ? ? ? ? other = parent.right;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //case2: node的兄弟節(jié)點(diǎn)other是黑色的蚕苇,且other的兩個(gè)子節(jié)點(diǎn)也都是黑色的if((other.left ==null|| isBlack(other.left)) &&? ? ? ? ? ? ? ? ? ? ? ? (other.right ==null|| isBlack(other.right))) {

? ? ? ? ? ? ? ? ? ? setRed(other);

? ? ? ? ? ? ? ? ? ? node = parent;

? ? ? ? ? ? ? ? ? ? parent = parentOf(node);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? //case3: node的兄弟節(jié)點(diǎn)other是黑色的,且other的左子節(jié)點(diǎn)是紅色凿叠,右子節(jié)點(diǎn)是黑色if(other.right ==null|| isBlack(other.right)) {

? ? ? ? ? ? ? ? ? ? ? ? setBlack(other.left);

? ? ? ? ? ? ? ? ? ? ? ? setRed(other);

? ? ? ? ? ? ? ? ? ? ? ? rightRotate(other);

? ? ? ? ? ? ? ? ? ? ? ? other = parent.right;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? //case4: node的兄弟節(jié)點(diǎn)other是黑色的涩笤,且other的右子節(jié)點(diǎn)是紅色,左子節(jié)點(diǎn)任意顏色? ? ? ? ? ? ? ? ? ? setColor(other, colorOf(parent));

? ? ? ? ? ? ? ? ? ? setBlack(parent);

? ? ? ? ? ? ? ? ? ? setBlack(other.right);

? ? ? ? ? ? ? ? ? ? leftRotate(parent);

? ? ? ? ? ? ? ? ? ? node =this.root;

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } else{//與上面的對(duì)稱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的倆個(gè)子節(jié)點(diǎn)都是黑色的? ? ? ? ? ? ? ? ? ? setRed(other);

? ? ? ? ? ? ? ? ? ? node = parent;

? ? ? ? ? ? ? ? ? ? parent = parentOf(node);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? if(other.left==null|| isBlack(other.left)) {

? ? ? ? ? ? ? ? ? ? ? ? // Case 3: node的兄弟other是黑色的蹬碧,并且other的左子節(jié)點(diǎn)是紅色,右子節(jié)點(diǎn)為黑色履恩。? ? ? ? ? ? ? ? ? ? ? ? setBlack(other.right);

? ? ? ? ? ? ? ? ? ? ? ? setRed(other);

? ? ? ? ? ? ? ? ? ? ? ? leftRotate(other);

? ? ? ? ? ? ? ? ? ? ? ? other = parent.left;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? // Case 4: node的兄弟other是黑色的锰茉;并且other的左子節(jié)點(diǎn)是紅色的,右子節(jié)點(diǎn)任意顏色? ? ? ? ? ? ? ? ? ? setColor(other, colorOf(parent));

? ? ? ? ? ? ? ? ? ? setBlack(parent);

? ? ? ? ? ? ? ? ? ? setBlack(other.left);

? ? ? ? ? ? ? ? ? ? rightRotate(parent);

? ? ? ? ? ? ? ? ? ? node =this.root;

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? if(node!=null)

? ? ? ? ? ? setBlack(node);

? ? }

? ? privateEntry successor(Entry delEntry) {

? ? ? ? Entry r = delEntry.right;//assert r != null;while(r.left !=null){

? ? ? ? ? ? r = r.left;

? ? ? ? }

? ? ? ? return r;

? ? }

  到此切心,紅黑樹的添加刪除操作已經(jīng)全部講完了,如果文中有什么錯(cuò)誤或不懂得地方片吊,隨時(shí)歡迎大家指出討論绽昏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市俏脊,隨后出現(xiàn)的幾起案子全谤,更是在濱河造成了極大的恐慌,老刑警劉巖爷贫,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件认然,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡漫萄,警方通過(guò)查閱死者的電腦和手機(jī)卷员,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)腾务,“玉大人毕骡,你說(shuō)我怎么就攤上這事⊙沂荩” “怎么了未巫?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)启昧。 經(jīng)常有香客問(wèn)我叙凡,道長(zhǎng),這世上最難降的妖魔是什么密末? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任握爷,我火速辦了婚禮跛璧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饼拍。我一直安慰自己赡模,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布师抄。 她就那樣靜靜地躺著漓柑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叨吮。 梳的紋絲不亂的頭發(fā)上辆布,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音茶鉴,去河邊找鬼锋玲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涵叮,可吹牛的內(nèi)容都是我干的惭蹂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼割粮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盾碗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起舀瓢,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廷雅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后京髓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體航缀,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年堰怨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芥玉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诚些,死狀恐怖飞傀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诬烹,我是刑警寧澤砸烦,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站绞吁,受9級(jí)特大地震影響幢痘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜家破,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一颜说、第九天 我趴在偏房一處隱蔽的房頂上張望购岗。 院中可真熱鬧,春花似錦门粪、人聲如沸喊积。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乾吻。三九已至,卻和暖如春拟蜻,著一層夾襖步出監(jiān)牢的瞬間绎签,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工酝锅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诡必,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓搔扁,卻偏偏與公主長(zhǎng)得像爸舒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稿蹲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容