红黑树,看不懂你找我
文章目錄
- 一:什么是紅黑樹
- 二:關(guān)于紅黑樹的一般操作
- 1.查找操作
- 2. 插入操作
- 2.1 新節(jié)點:我是紅色的,我爹是紅色的,我叔叔是黑色的
- 2.2 新節(jié)點:我是紅色的,我爹是紅色的,我叔叔也是紅色的
- 3.刪除操作
- 3.1替代點是紅色的
- 3.2替代點是黑色的
- 3.2.1 有一個兒子為且必須為紅色時
- 3.2.2 有兩個黑色兒子
- 3.2.2.1 替代點的兄弟節(jié)點是紅色
- 3.2.2.2 替代點的兄弟節(jié)點是黑色
- 3.2.2.2.1 當(dāng)兄弟節(jié)點的兩個兒子也都是黑色
- 3.2.2.2.2 當(dāng)兄弟節(jié)點的左兒子是紅色的,右兒子是黑色
- 3.2.2.2.3 當(dāng)兄弟節(jié)點的右兒子是紅色的,左兒子隨意
- 三:漸進(jìn)邊界的證明
一:什么是紅黑樹
紅黑樹是AVL樹的一種流行變種,是具有以下五個特點的二叉查找樹:
二:關(guān)于紅黑樹的一般操作
對紅黑樹操作,最困難的就是保持上述的紅黑樹性質(zhì)中的紅色標(biāo)記部分。
1.查找操作
紅黑樹是二叉查找樹,所以其查找操作與一般的二叉查找樹相同
- 令根節(jié)點為當(dāng)前節(jié)點
- 當(dāng)前節(jié)點為Nil,返回Nil
- 要找的值等于當(dāng)前節(jié)點,返回當(dāng)前節(jié)點
- 要找的值大于當(dāng)前節(jié)點, 令右兒子為當(dāng)前節(jié)點,重復(fù)步驟2
- 要找的值小于當(dāng)前節(jié)點,令左兒子為當(dāng)前節(jié)點,重復(fù)步驟2
操作的時間復(fù)雜度O(logN)
2. 插入操作
當(dāng)我們想向樹中插入一個新節(jié)點時,通常把這個節(jié)點作為葉節(jié)點插入。
Part1:
假設(shè)把這個點涂成黑色,那完了,肯定違反了條件5,因為本來任意的節(jié)點到某Nil指針的路徑中黑色節(jié)點數(shù)是相同的,現(xiàn)在加上黑色節(jié)點的這條路徑黑色節(jié)點數(shù)度肯定會+1,于是原來的條件被破壞了,因此,新加的節(jié)點必須被涂成紅色。
Part2:
如果新插入的節(jié)點的父節(jié)點是黑色的,那么直接插入新的紅節(jié)點就完事了
Part3:
如果新插入的節(jié)點的父節(jié)點是紅色,那么條件4就被破壞了,這種情況下,我們必須在保持條件5不被破壞的同時,利用改變節(jié)點顏色以及樹的旋轉(zhuǎn)來滿足所有條件。
2.1 新節(jié)點:我是紅色的,我爹是紅色的,我叔叔是黑色的
- 設(shè)置P為黑色
- 設(shè)置G為紅色
- 進(jìn)行右單旋轉(zhuǎn)
- 設(shè)置X為黑色
- 設(shè)置G為紅色
- 進(jìn)行右雙旋轉(zhuǎn)
還有對稱的兩種情況,操作也是對稱的
2.2 新節(jié)點:我是紅色的,我爹是紅色的,我叔叔也是紅色的
- 把G節(jié)點設(shè)置為紅色
- 把P節(jié)點設(shè)置為黑色
- 把S節(jié)點設(shè)置為黑色
- 把G節(jié)點設(shè)置為紅色
- 把P節(jié)點設(shè)置為黑色
- 把S節(jié)點設(shè)置為黑色
這時候,如果G的父節(jié)點也是紅的,那么G的顏色翻轉(zhuǎn)的操作就會影響性質(zhì)4,于是我們對G節(jié)點以及其父節(jié)點GP和父節(jié)點的兄弟節(jié)點GPS進(jìn)行2.1所示的旋轉(zhuǎn)操作。
注意如果G的父節(jié)點GP為紅色,G父節(jié)點的兄弟節(jié)點GPS肯定不能為紅色,只能為黑色,因為在紅黑樹中,不可能出現(xiàn)出現(xiàn)深度小于樹的總深度且雙兄弟都為紅的情況,這種情況在其他的插入或刪除過程中已經(jīng)消除了。
GP剛好為根結(jié)點時,那么根據(jù)性質(zhì)2,我們必須把GP重新設(shè)為黑色,那么樹的紅黑結(jié)構(gòu)變?yōu)?#xff1a;黑黑紅。換句話說,從根結(jié)點到葉子結(jié)點的路徑中,黑色結(jié)點增加了。這也是唯一一種會增加紅黑樹黑色結(jié)點層數(shù)的插入情景。
還有對稱的兩種情況,操作也是對稱的
偽代碼
RB-INSERT(T, z)//紅黑樹插入y = T.nilx = T.rootwhile x != T.nily = xif z.key<x.keyx = x.leftelse x = x.rightz.p = yif y == T.nilT.root = zelse if z.key < y.keyy.left = zelse y.right = zz.left = T.nilz.right = T.nilz.color = RED//對紅黑樹的性質(zhì)進(jìn)行恢復(fù)RB-INSERT-FIXUP(T, z)RB-INSERT-FIXUP(T, z)//插入紅色子節(jié)點,破壞了性質(zhì)4時while z.p.color = RED//父節(jié)點為左子樹if z.p == z.p.p.left//獲取叔叔結(jié)點yy = z.p.p.right//叔叔結(jié)點為紅色if y.color = REDz.p.color = BLACKy.color = BLACKz.p.p.color = REDz = z.p.p//叔叔結(jié)點為黑色else //新插入的結(jié)點為父節(jié)點的右孩子,先來個父子調(diào)換,先進(jìn)行一次左旋轉(zhuǎn)if z == z.p.rightz = z.pLEFT-ROTATE(T,z)//化成了叔叔結(jié)點黑色的一般情況z.p.color = BLACKz.p.p.color = REDRIGHT-ROTATE(T, z.p.p)//對稱情況else (same as then clause with "right" and "left" exchanged)T.root.color = BLACK難點來了,難點來了!!
其實不難
3.刪除操作
刪除操作一直一來是樹這種ADT的難點所在
一般二叉搜索樹的刪除操作:
- 該節(jié)點為葉節(jié)點的情況:可以直接刪除,將其父節(jié)點的兒子指針設(shè)為Nil
- 該節(jié)點只有一個非空子節(jié)點:可以直接刪除,將其父節(jié)點的兒子指針指向其唯一兒子
- 該節(jié)點有兩個非空子節(jié)點:用該節(jié)點左子樹的最大節(jié)點或者右子樹的最小節(jié)點替換該節(jié)點,然后刪除對應(yīng)的左子樹最大節(jié)點或者右子樹最小節(jié)點,最終可以回到前兩種情況。
好,到這里你肯定還是明白的對吧
現(xiàn)在考慮紅黑樹的刪除操作。
注意:
根據(jù)一般二叉搜索樹的刪除操作來看,我們要刪除的節(jié)點其實不一定是最終從圖中移出去的節(jié)點,稱最終從圖中移出去的節(jié)點為替代點,我們先從右子樹的最小節(jié)點(或左子樹的最大節(jié)點)找到我們的替代點,與刪除點的值交換,但是顏色都不變,然后再刪除替代點,然后對樹的性質(zhì)進(jìn)行恢復(fù)。而現(xiàn)在這些替代點都是一些樹的末節(jié)點(區(qū)別于葉節(jié)點,我們現(xiàn)在將Nil節(jié)點視為黑色葉節(jié)點),刪除操作便會簡化許多,并可以歸納為像插入那樣的幾個類別。當(dāng)然了替代點和我們要刪除的點也有可能是同一點,但處理方法是一樣的。
我們雖然刪除了替代點,但是我們對樹進(jìn)行恢復(fù)時,還是會將該替代點放在樹中參與恢復(fù),恢復(fù)完成后再移掉。
在本文中,示例都默認(rèn)是選擇右子樹的最小節(jié)點作為真正刪除點,也可以選擇左子樹的最大節(jié)點
3.1替代點是紅色的
直接從樹中去掉即可,不會影響紅黑樹的性質(zhì),算法直接結(jié)束
不會出現(xiàn)替代點為紅色且有兒子為非葉子節(jié)點的情況,所以這種情況是最簡單的
3.2替代點是黑色的
替代點是黑色,那么刪除之后樹的性質(zhì)就被破壞了,需要進(jìn)行修復(fù)
3.2.1 有一個兒子為且必須為紅色時
當(dāng)替代節(jié)點只有一個兒子且為紅色時
直接用它的紅色兒子節(jié)點取代它,并將顏色改為黑色,這樣所有經(jīng)過替代節(jié)點的路徑都將經(jīng)過他的兒子,黑色高度不變。
注意這跟上面圖不一樣,這張圖D節(jié)點是替代節(jié)點
不可能存在只有一個兒子且兒子為黑色的情況,那樣本身就是不平衡的
3.2.2 有兩個黑色兒子
當(dāng)替代點是黑色的且有兩個黑色兒子(可以為葉節(jié)點Nil)時,那他肯定有兄弟,那么又可以分為其他幾類
- 替代點是黑色,其兄弟節(jié)點是紅色
- 替代點是黑色,其兄弟節(jié)點是黑色
3.2.2.1 替代點的兄弟節(jié)點是紅色
- 將G節(jié)點也就是替代點的父節(jié)點設(shè)置為紅色,
- 兄弟節(jié)點S設(shè)置為黑色
- 然后進(jìn)行向左單旋轉(zhuǎn),
現(xiàn)在可以直接刪掉D了嗎?(以下都默認(rèn)標(biāo)識D為替代點) 不行,tan90°\hspace4ex 不行,tan90\degree不行,tan90°
這個操作不改變?nèi)魏温窂降暮谏叨?#xff0c;這時候刪去D肯定會變的不平衡,我們只是把情況變?yōu)榱诵值芄?jié)點為黑色的情況,也就是下面將要敘述的情況,我們接下來需要重新進(jìn)入算法,進(jìn)一步處理
3.2.2.2 替代點的兄弟節(jié)點是黑色
替代點D與其兄弟節(jié)點S都是黑色的,所以D的父節(jié)點顏色是不確定的,用綠色表示
又分為以下幾種情況
3.2.2.2.1 當(dāng)兄弟節(jié)點的兩個兒子也都是黑色
PS:C1、C2可為Nil
- S節(jié)點顏色變?yōu)榧t
- G節(jié)點顏色變黑
- 把G作為新的替代點進(jìn)行下一輪操作
假設(shè)G一開始是黑色的,這個操作過后,所有一開始經(jīng)過S節(jié)點的路徑黑色高度-1,那么在刪除D節(jié)點后,所有經(jīng)過D節(jié)點的路徑黑色高度也會-1,這棵子樹大家黑色高度都減一,結(jié)局竟該死的甜美,但是經(jīng)過G的比不經(jīng)過G的黑色高度減一了,所以再在G上重新平衡處理
假設(shè)G一開始是紅色的,這個操作后,所有一開始經(jīng)過S節(jié)點的路徑黑色高度不變,經(jīng)過D的路徑黑色高度+1,那么在刪除D后,所有經(jīng)過D節(jié)點的路徑黑色高度又變回來了,結(jié)局竟還是該死的甜美,經(jīng)過G為根的子樹已經(jīng)平衡了,再在G上重新平衡處理
注意當(dāng)G是樹的根時,就表示我們已經(jīng)做完了,我們從所有路徑去除了一個黑色節(jié)點,所有性質(zhì)在上溯過程中都保持著。
3.2.2.2.2 當(dāng)兄弟節(jié)點的左兒子是紅色的,右兒子是黑色
- C1設(shè)置為黑色
- S設(shè)置為紅色
- 對S進(jìn)行向右單旋轉(zhuǎn)
這樣的操作不改變?nèi)魏温窂降暮谏叨?#xff0c;本來的平衡狀態(tài)不變,所以刪除D之后就破壞了原先的條件,還要進(jìn)行自平衡變換,此時成了下面一種情況繼續(xù)處理
3.2.2.2.3 當(dāng)兄弟節(jié)點的右兒子是紅色的,左兒子隨意
- 交換G和S的顏色
- 設(shè)置兄弟節(jié)點的右兒子C2為黑色
- 進(jìn)行向左單旋轉(zhuǎn)
該操作使所有變化前經(jīng)過S節(jié)點的路徑黑色高度都不變,經(jīng)過D的路徑黑色高度+1,在刪除D后,所有經(jīng)過G節(jié)點的路徑都不變,達(dá)到平衡
說白了刪除就是不斷遞歸變換直到滿足替代點的刪除條件。
例子:刪除30根節(jié)點
練習(xí)一下吧,這里有個在線紅黑樹工具,戳
三:漸進(jìn)邊界的證明
包含n個內(nèi)部節(jié)點的紅黑樹的高度是O(logn)O(logn)O(logn)
定義:
h(v)表示以節(jié)點v為根的子樹的高度。{\displaystyle h(v)}表示以節(jié)點{\displaystyle v}為根的子樹的高度。h(v)表示以節(jié)點v為根的子樹的高度。
bh(v)表示從v到子樹中任何葉子的黑色節(jié)點的數(shù)目{\displaystyle bh(v)}表示從{\displaystyle v}到子樹中任何葉子的黑色節(jié)點的數(shù)目bh(v)表示從v到子樹中任何葉子的黑色節(jié)點的數(shù)目
(如果v是黑色則不計數(shù)它,也叫做黑色高度)。(如果{\displaystyle v}是黑色則不計數(shù)它,也叫做黑色高度)。(如果v是黑色則不計數(shù)它,也叫做黑色高度)。
引理:以節(jié)點v為根的子樹有至少2bh(v)?1個內(nèi)部節(jié)點。引理:以節(jié)點{\displaystyle v}為根的子樹有至少2^{bh(v)}-1個內(nèi)部節(jié)點。引理:以節(jié)點v為根的子樹有至少2bh(v)?1個內(nèi)部節(jié)點。
引理的證明(通過歸納高度):引理的證明(通過歸納高度):引理的證明(通過歸納高度):
歸納假設(shè):歸納假設(shè):歸納假設(shè):
如果v的高度是零則它必定是NIL,因此bh(v)=0bh(v)=0。所以:2bh(v)?1=20?1=0如果v的高度是零則它必定是NIL,因此{(lán)\displaystyle bh(v)=0}{\displaystyle bh(v)=0}。所以:2^{{bh(v)}}-1=2^{{0}}-1=0如果v的高度是零則它必定是NIL,因此bh(v)=0bh(v)=0。所以:2bh(v)?1=20?1=0
如果h(v)=k,有2bh(v)?1個內(nèi)部節(jié)點成立如果h(v)=k,有2^{bh(v)}-1個內(nèi)部節(jié)點成立如果h(v)=k,有2bh(v)?1個內(nèi)部節(jié)點成立
于是h(v′)=k+1于是h(v')=k+1于是h(v′)=k+1
因為v′有h(v′)>0所以它是個內(nèi)部節(jié)點。同樣的它有的兩個兒子,其黑色高度要么是bh(v′)要么是bh(v′)?1(依據(jù)v′是紅色還是黑色)。通過歸納假設(shè)每個兒子都有至少2bh(v′)?1?1個內(nèi)部接點,所以v′有:因為v'有h(v')>0 \\所以它是個內(nèi)部節(jié)點。同樣的它有的兩個兒子,其黑色高度要么是bh(v')要么是bh(v')-1(依據(jù)v'是紅色還是黑色)。通過歸納假設(shè)每個兒子都有至少2^{{bh(v')-1}}-1個內(nèi)部接點,所以v'有:因為v′有h(v′)>0所以它是個內(nèi)部節(jié)點。同樣的它有的兩個兒子,其黑色高度要么是bh(v′)要么是bh(v′)?1(依據(jù)v′是紅色還是黑色)。通過歸納假設(shè)每個兒子都有至少2bh(v′)?1?1個內(nèi)部接點,所以v′有:
2bh(v′)?1?1+2bh(v′)?1?1+1=2bh(v′)?1個內(nèi)部節(jié)點。2^{bh(v')-1}-1+2^{bh(v')-1}-1+1=2^{bh(v')}-1個內(nèi)部節(jié)點。2bh(v′)?1?1+2bh(v′)?1?1+1=2bh(v′)?1個內(nèi)部節(jié)點。
使用這個引理我們現(xiàn)在可以展示出樹的高度是對數(shù)性的。因為在從根到葉子的任何路徑上至少有一半的節(jié)點是黑色(根據(jù)紅黑樹性質(zhì)4),根的黑色高度至少是h(root)2通過引理我們得到:使用這個引理我們現(xiàn)在可以展示出樹的高度是對數(shù)性的。\\ 因為在從根到葉子的任何路徑上至少有一半的節(jié)點是黑色(根據(jù)紅黑樹性質(zhì)4),\\根的黑色高度至少是 {\frac {h(root)}{2}}通過引理我們得到:使用這個引理我們現(xiàn)在可以展示出樹的高度是對數(shù)性的。因為在從根到葉子的任何路徑上至少有一半的節(jié)點是黑色(根據(jù)紅黑樹性質(zhì)4),根的黑色高度至少是2h(root)?通過引理我們得到:
n?2h(root)2?1?log?(n+1)?h(root)2?h(root)?2log?(n+1)n?2h(root)2?1?log?(n+1)?h(root)2?h(root)?2log?(n+1){\displaystyle n\geqslant 2^{\frac {h(root)}{2}}-1\leftrightarrow \;\log {(n+1)}\geqslant {\frac {h(root)}{2}}\leftrightarrow \;h(root)\leqslant 2\log {(n+1)}}n\geqslant 2^{{{\frac {h(root)}{2}}}}-1\leftrightarrow \;\log {(n+1)}\geqslant {\frac {h(root)}{2}}\leftrightarrow \;h(root)\leqslant 2\log {(n+1)}n?22h(root)??1?log(n+1)?2h(root)??h(root)?2log(n+1)n?22h(root)??1?log(n+1)?2h(root)??h(root)?2log(n+1)
因此根的高度是O(log?n)因此根的高度是{\displaystyle {\text{O}}(\log n)}因此根的高度是O(logn)
總結(jié)
以上是生活随笔為你收集整理的红黑树,看不懂你找我的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你了解欧拉回路吗?(附Java实现代码)
- 下一篇: 从JVM看类的加载过程与对象实例化过程