前言
紅黑樹直接看有點懵,塗上顏色,顏色轉換,狀態調整,說實話,一上來這麼弄,我都不想看,有些東西你會知道是這麼回事,但是你不清楚爲什麼這麼做?爲什麼要塗上顏色?我自己也不太喜歡死記硬背,感覺很傷腦子,而且過一段時間就忘記了,這不是我的風格。
2-3 樹
2-3 樹 同2-3-4樹 是差不多的概念,這也是一種平衡樹,但有不一樣的地方:
- 一般平衡樹一個節點只能存一個key,這種樹的節點可以有兩個key,有兩個key的節點稱爲3節點,有一個key的節點稱爲2節點,這裏不是說 2個節點,就叫2節點。下圖左邊是2節點,右邊是3節點。
- 2節點只有一個左子樹、一個右子樹,3節點有三個子樹,分別是左子樹、中間子樹、一個右子樹。
- 平衡樹都有 節點旋轉操作,因爲要保證左子樹、右子樹 高度不能相差1,2-3樹也有,但是它的旋轉會複雜一些
同樣 2-3樹的插入、刪除也會更復雜,大致的思路是:
- 如果是2節點,就變成3結點
- 如果是3結點,3結點成4節點,4節點重新分配節點
刪除的話就複雜很多,當然被篇文章就不仔細討論這些了,他的優點包括平衡二叉樹的所有優點:
- 查找時間複雜度:,最壞的情況下降低了樹的高度,比二叉平衡樹可以容納更多的key
當然缺點也非常致命:
- 查找和插入操作的實現需要大量的代碼,而且它們所產生的額外開銷可能會使算法比標準的二叉查找樹更慢
所以2-3樹在實際中很少使用。由於其需要大量的節點變換(從2-節點到3-節點到4-節點甚至到5-節點…),這些變換在實際代碼中是很複雜的。所以現在幾乎沒有2-3樹的具體實現。
但是由於2-3樹的變化十分直觀,因此前人在2-3樹的理論基礎上發明了紅黑樹。
2-3-4樹與紅黑樹
2-3-4樹 與 2-3樹唯一的差別就在於多了一個4結點,容納3個key,其他差別不太大,紅黑樹其實更類似2-3-4樹:
- 一個黑結點 如果有 兩個紅結點,就類似一個4結點
- 一個黑結點如果有一個紅結點,就類似一個3結點
2-3樹 和 2-3-4樹 雖然很方便,但是上一節中也提到代碼會非常複雜,於是改善了一下,提出了紅黑樹,紅黑樹用紅鏈接表示2-3樹中另類的3-結點,統一了樹中的結點類型,使代碼實現簡單化,又不破壞其高效性,用顏色來做一個標識。
有一些博客裏講解了紅黑樹的幾個特徵性質,當然我們也提一下,雖然這些特徵比較麻煩:
- 1、根結點是黑色
- 2、沒有相鄰的紅結點,意思就是父結點是紅結點,子結點就不能是紅結點,紅結點的子結點必須是黑結點
- 3、從結點(包括根)到其任何後代NULL節點(葉子結點下方掛的兩個空節點,並且認爲他們是黑色的)的每條路徑都具有相同數量的黑色節點
這是約束一顆紅黑樹的條件,也就是符合這些條件就是紅黑樹,其實還有一點:新插入的結點都是紅色節點。紅色節點插入樹中之後,根據約束條件來更改顏色、調整樹結構。
紅黑樹插入結點
插入相對還是比較簡單的。同樣結點數目構成的紅黑樹有很多種,這些不同的情況都可以通過染色、平衡來實現,那麼問題來了?你依據什麼樣的準則來染色、調整?因爲很多種染色、平衡操作都可以使得不平衡的紅黑樹變得平衡,那麼你設計的調整準則是什麼?
看到一篇文章講解插入操作,講解的還是比較好的,插入主要關注三種情況,這三種情況會因爲一開始插入,或者是因爲調整之後出現的情況,並且並不是調整一次就結束,會反覆調整,我們先看下僞代碼:
RB-INSERT-FIXUP(T,z)
1 while color[p[z]] = RED
2 if p[z] = left[p[p[z]]]
3 y ← right[p[p[z]]]
4 if color[y] = RED
5 color[p[z]] ← BLACK ▹ Case 1
6 color[y] ← BLACK ▹ Case 1
7 color[p[p[z]]] ← RED ▹ Case 1
8 z ← p[p[z]] ▹ Case 1
9 else if z = right[p[z]]
10 z ← p[z] ▹ Case 2
11 LEFT-ROTATE(T, z) ▹ Case 2
12 else
13 color[p[z]] ← BLACK ▹ Case 3
14 color[p[p[z]]] ← RED ▹ Case 3
15 RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3
16 else (same as then clause with "right" and "left" exchanged)
17 color[root[T]] ← BLACK
代碼可能有點難懂,參數說明:z是插入節點的指針,T是樹。總結一下代碼流程:
while ( z節點父節點 是紅色 ):
if z節點的父節點 == z節點 祖父節點的左節點:
y = z節點 祖父節點右節點
if y 顏色 == red
第一種情況調整 ▹ 注意
z = z節點的祖父節點
else if z是右孩子 # 這也說明 y 顏色是黑色
z = z節點的父節點
第二種情況調整 ▹ 注意
else # z是左孩子 ,y 顏色是黑色
第三種情況調整 ▹ 注意
else # 隱藏含義: z節點的父節點 == z節點 祖父節點的右節點
# 剩下的操作跟上面一樣,只不過 左右互換一下
最後將 根節點 顏色 改爲 黑色
注意幾點:
- 調整一共有三種情況
- 這是一個while 循環,三種情況 反覆調整,一直都 z節點 的父節點不是紅色
- 最後一步:將根節點置位黑色
那麼調整的三種情況是怎麼樣的呢?圖解如下:
講解:
- 紅色節點是插入節點:這句話是說 紅色圈起來的節點,節點的圓圈是紅色筆畫的,
- 調整後,被我用紅色字體標出來了,這就是下一個循環中用的節點。
- 第一種情況:變色
- 第二種情況:左旋轉
- 第三種情況:父節點變黑,祖父節點變紅,變色後 右旋轉
看的時候注意幾個問題:
-
第一種情況下:爲什麼根節點是紅色?這不就不符合紅黑樹定義了嘛?
因爲有最後的一個步驟將根節點置位黑色,所以其實也不算是一種失誤 -
第二種情況、第三種情況 下 原有結構也是有問題的?
因爲我們看到了的只是一部分結構,單純的這種樹結構是不會出現的,因爲根本不符合紅黑樹的定義,建議看下教你透徹瞭解紅黑樹。裏面有完整的樹結構,第二種、第三種其中一部分結構,並且也有可能是調整之後出現的情況 -
第二種情況、第三種情況的聯繫
如果你仔細看,你會發現第二種情況跟第三種情況是連續的,第二種情況之後就開始執行第三種情況, -
第三種情況之後好像出現了不平衡的情況
第三種情況是先變色,再旋轉,但其實第三種情況的初始狀態就不會存在,也就是不存在單純的第三種情況的初始狀態,它只是紅黑樹的一部分。完整的樹下它是平衡的。 -
出現連續的兩個紅色節點的情況,比如第三種情況,有點像2-3-4樹
紅-紅-黑 就像 一個 4結點,只不過在2-3-4樹中是一個結點,在紅黑樹中要變色、調整,使得平衡。 -
第三種情況之後,似乎就結束調整了
沒錯,我們看圖中第三種情況調整之後,的節點位置,發現它不滿足while循環的條件,也就會跳出循環,所以可以認爲這是最後的一個步驟。 -
爲什麼是這三種情況?
很奇怪的一點就是爲什麼是這三種情況?這三種情況似乎是可以連續起來的,2、3、1這樣的順序,不過也只是猜測,各種情況分析之後其實也就只有這幾種情況需要調整了,具體的還要繼續研究一下。
插入的操作大致是這樣,一些不需要調整的情況如下:
- 插入的是根結點,因爲原樹是空樹,此情況只會違反性質2,所以直接把此結點塗爲黑色。
- 如果插入的結點的父結點是黑色,由於此不會違反性質2和性質4,紅黑樹沒有被破壞,所以此時也是什麼也不做。
三種情況的另一種換言之,可以加強大家認識:
- 情況1:如果當前結點的父結點是紅色且祖父結點的另一個子結點(叔叔結點)是紅色
- 情況2:當前結點的父結點是紅色,叔叔結點是黑色,當前結點是其父結點的右子
- 情況3:當前結點的父結點是紅色,叔叔結點是黑色,當前結點是其父結點的左子
左右反過來的情況是一樣的,處理過程也是類似的,這裏不贅述。
TreeSet treeSet = new TreeSet();
treeSet.add(4);
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(5);
treeSet.add(6);
treeSet.add(7);
以上代碼是我用來測試的代碼,通過debug來查看了每個節點的顏色,得到了最終樹的形狀:
我比較奇怪,爲啥 這個不是其他結構,因爲4也可以作爲根節點,得到一個更加平衡的二叉樹,於是我猜想是不是跟節點插入順序有關,於是我重新弄了一個代碼:
TreeSet treeSet = new TreeSet();
treeSet.add(4);
treeSet.add(1);
treeSet.add(5);
treeSet.add(2);
treeSet.add(6);
treeSet.add(3);
treeSet.add(7);
我debug看了一下,還是結構發生了變化,
插入的順序跟最終的結果關係很大,有可能本來就很平衡,不需要調整。
紅黑樹 刪除結點
刪除結點 一共有6種情況,真是複雜,慢慢來分析,這裏會先講解刪除樹節點的大致操作,然後來了解一下二叉樹刪除節點之後的調整過程,定一個基調,用來分析紅黑樹的刪除:
- 1、葉子節點刪除,直接刪除
- 2、只有一個子節點的節點,刪除該節點,子節點替代上去
- 3、有兩個子節點的節點刪除,選擇左子節點的最大節點來替代,默認這麼做,也可以選擇右邊最小子節點。
不過紅黑樹是先刪除再調整樹結構,調整樹結構時會以刪除節點的位置出發來做調整操作。
所以如果是紅黑樹的話,應該怎麼做呢? - 當前結點是紅節點
- 刪除後,替代節點如果是黑色節點,且是根節點,則直接刪除
這兩種情況比較好弄,但是不太好理解,第一種比較好理解,第二種不太好理解,因爲極有可能會造成左子樹黑色結點少了一個,這樣,根節點到葉子節點的路徑上黑色節點數目不太一致,這一點先保留意見,之後來測試一下場景。
還有另外四種情況,代碼先上:
1 while x ≠ root[T] and color[x] = BLACK
2 do if x = left[p[x]]
3 then w ← right[p[x]]
4 if color[w] = RED
5 then color[w] ← BLACK ▹ Case 1
6 color[p[x]] ← RED ▹ Case 1
7 LEFT-ROTATE(T, p[x]) ▹ Case 1
8 w ← right[p[x]] ▹ Case 1
9 if color[left[w]] = BLACK and color[right[w]] = BLACK
10 then color[w] ← RED ▹ Case 2
11 x ← p[x] ▹ Case 2
12 else if color[right[w]] = BLACK
13 then color[left[w]] ← BLACK ▹ Case 3
14 color[w] ← RED ▹ Case 3
15 RIGHT-ROTATE(T, w) ▹ Case 3
16 w ← right[p[x]] ▹ Case 3
17 color[w] ← color[p[x]] ▹ Case 4
18 color[p[x]] ← BLACK ▹ Case 4
19 color[right[w]] ← BLACK ▹ Case 4
20 LEFT-ROTATE(T, p[x]) ▹ Case 4
21 x ← root[T] ▹ Case 4
22 else (same as then clause with "right" and "left" exchanged)
23 color[x] ← BLACK
4種情況主要有:
- Case 1:當前結點是黑色,且兄弟結點爲紅色(此時父結點和兄弟結點的子結點分爲黑)
下圖中A節點就是當前節點,把父結點染成紅色,把兄弟結點染成黑色,之後重新進入算法。此變換前、後 原紅黑樹性質5不變,而把問題轉化爲兄弟結點爲黑色的情況。 - Case 2:當前結點是黑色,且兄弟是黑色且兄弟結點的兩個子結點全爲黑色
這裏要注意,調整後會將B節點作爲當前節點,然後進行插入調整。所以下面的圖可以看出其實是不平衡的,刪除調整之後還需要插入調整。
- Case 3:當前結點顏色是黑,兄弟結點是黑色,兄弟的左子是紅色,右子是黑色
把兄弟結點染紅,兄弟左子結點染黑,之後再在兄弟結點爲支點解右旋,之後重新進入算法,這主要是把當前情況轉化爲情況4,然後通過插入調整來使得滿足性質:根節點到所有葉子節點路徑中黑色節點數目一致。這裏有個疑問?插入調整的當前節點是哪個?
- Case 4:當前結點顏色是黑,它的兄弟結點是黑色,但是兄弟結點的右子是紅色,兄弟結點左子的顏色任意
把兄弟結點染成當前結點父結點的顏色,把當前結點父結點染成黑色,兄弟結點右子染成黑色,之後以當前結點的父結點爲支點進行左旋,然後進行插入調整,
其實通過上述的情況來看,這裏的刪除調整不是一下子就結束了,也要進行插入調整,兩個調整之後還要進行多次調整,最終可以得到一顆符合條件的平衡樹。
來看一個代碼實現:
TreeSet treeSet = new TreeSet();
treeSet.add(4);
treeSet.add(1);
treeSet.add(5);
treeSet.add(2);
treeSet.add(6);
treeSet.add(3);
treeSet.add(7);
treeSet.remove(2);
通過debug發現刪除前後的差別是,刪除前:
刪除後:
紅黑樹 業務總結
目前樹結構的有:
- 二叉查找樹(BST)
英文Binary Sort Tree,查找、插入、刪除的時間複雜度爲,時間複雜度就變味了 - AVL 樹
平衡二叉樹全稱平衡二叉搜索樹,也叫AVL樹。是一種自平衡的樹。它要求左子樹、右子樹的高度相差不超過1,如果超過1了就會執行調整算法來調整數結構。
紅黑樹也是一種AVL樹,紅黑樹是用非嚴格的平衡來換取增刪節點時候旋轉次數的降低,任何不平衡都會在三次旋轉之內解決,而AVL是嚴格平衡樹,因此在增加或者刪除節點的時候,根據不同情況,旋轉的次數比紅黑樹要多。
實際應用中,根據業務特點來選擇:
- 若搜索的次數遠遠大於插入和刪除,那麼選擇AVL
- 如果搜索,插入刪除次數幾乎差不多,應該選擇RB
參考博客
漫畫算法:什麼是紅黑樹?(通俗易懂)
2-3樹到紅黑樹
紅黑樹從頭至尾插入和刪除結點的全程演示圖
RedBlack.pdf