一.為什么要有紅黑樹這種數據結構?
學過二叉查找樹的同學都知道,普通的二叉查找樹在極端情況下可退化成鏈表,此時的增刪查O(n)效率都會比較低下。為了避免這種情況,就出現了一些自平衡的查找樹,比如 AVL。
ALV樹是一種嚴格按照定義來實現的平衡二叉查找樹,所以它查找的效率非常穩定,為O(log n),由於其嚴格按照左右子樹高度差不大於1的規則,插入和刪除操作中需要大量且復雜的操作來保持ALV樹的平衡(左旋和右旋),因此ALV樹適用於大量查詢,少量插入和刪除的場景中。
現在假設有這樣一種場景:大量查詢,插入和刪除,使用ALV樹就不太合適了,因為ALV樹大量的插入和刪除會非常耗時間,那么我們是否可以降低ALV樹對平衡性的要求從而達到快速的插入和刪除呢?
答案肯定是有的,紅黑樹這種數據結構就應運而生了(因為ALV樹是高度平衡的,所以查找起來肯定比紅黑樹快,但是紅黑樹在插入和刪除方面的性能就遠遠不是ALV樹所能比的了)。
二.紅黑樹的性質
紅黑樹通過如下的性質定義實現自平衡:
1.節點是紅色或黑色。
2.根是黑色。
3.所有葉子都是黑色(葉子是NIL節點)。
4.每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
5.從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點(簡稱黑高)。
有了上面的幾個性質作為限制,即可避免二叉查找樹退化成單鏈表的情況。但是,僅僅避免這種情況還不夠,這里還要考慮某個節點到其每個葉子節點路徑長度的問題。如果某些路徑長度過長,那么,在對這些路徑上的及誒單進行增刪查操作時,效率也會大大降低。這個時候性質4和性質5用途就凸顯了,有了這兩個性質作為約束,即可保證任意節點到其每個葉子節點路徑最長不會超過最短路徑的2倍。
當某條路徑最短時,這條路徑必然都是由黑色節點構成。當某條路徑長度最長時,這條路徑必然是由紅色和黑色節點相間構成(性質4限定了不能出現兩個連續的紅色節點)。而性質5又限定了從任一節點到其每個葉子節點的所有路徑必須包含相同數量的黑色節點。此時,在路徑最長的情況下,路徑上紅色節點數量 = 黑色節點數量。該路徑長度為兩倍黑色節點數量,也就是最短路徑長度的2倍。舉例說明一下,請看下圖:
三.紅黑樹的基本操作之旋轉
旋轉操作分為左旋和右旋,左旋是將某個節點旋轉為其右孩子的左孩子,而右旋是節點旋轉為其左孩子的右孩子。這話聽起來有點繞,所以還是請看下圖:
上圖包含了左旋和右旋的示意圖,這里以右旋為例進行說明,右旋節點 M 的步驟如下:
- 將節點 M 的左孩子引用指向節點 E 的右孩子
- 將節點 E 的右孩子引用指向節點 M,完成旋轉
四.紅黑樹的基本操作之添加元素
紅黑樹的插入過程和二叉查找樹插入過程基本類似,不同的地方在於,紅黑樹插入新節點后,需要進行調整,以滿足紅黑樹的性質。性質1規定紅黑樹節點的顏色要么是紅色要么是黑色,那么在插入新節點時,這個節點應該是紅色還是黑色呢?答案是紅色,原因也不難理解。如果插入的節點是黑色,那么這個節點所在路徑比其他路徑多出一個黑色節點,這個調整起來會比較麻煩(參考紅黑樹的刪除操作,就知道為啥多一個或少一個黑色節點時,調整起來這么麻煩了)。如果插入的節點是紅色,此時所有路徑上的黑色節點數量不變,僅可能會出現兩個連續的紅色節點的情況。這種情況下,通過變色和旋轉進行調整即可,比之前的簡單多了。
現在我們來分析一下新增的節點(紅色)插入之后可能面臨的幾種情況,以及他們的處理措施:
1.插入的節點為根節點
新插入的紅色節點變成黑色節點,滿足根節點為黑色節點的要求
2.父親節點為黑色節點
這個時候不需要進行任何調整操作,此時的樹仍然是一顆標准的紅黑樹
3.父親節點為紅色節點的情況下,叔叔節點為紅色節點(不用考慮左右)
解決方案:將叔叔和父親節點改為黑色,爺爺節點改為紅色,然后又將爺爺節點當作插入節點看待,一直進行上面的操作,直到當前節點為根節點,然后將根節點變成黑色
4.父親節點為紅色,叔叔節點為黑色
1)父親節點為爺爺節點的左孩子,新插入節點為父節點的左孩子(左左)
解決方案:將父親節點和爺爺節點顏色互換(父節點變為黑色,爺爺節點變為紅色),然后對爺爺節點進行一次右旋
注:上圖叔叔是空葉子節點,所以也是黑色
2)父親節點為爺爺節點的右孩子,新插入節點為父節點的右孩子(右右)
解決方案:將父親節點和爺爺節點顏色互換(父節點變為黑色,爺爺節點變為紅色),然后對爺爺節點進行一次左旋
3)父親節點為爺爺節點的左孩子,新插入節點為父節點的右孩子(左右)
解決方案:對父親節點進行一次左旋,然后就變成了情況1,按照情況1再進行處理
4)父親節點為爺爺節點的右孩子,新插入節點為父節點的左孩子(右左)
解決方案:對父親節點進行一次右旋,然后就變成了情況2,按照情況2再進行處理
五.紅黑樹的基本操作之刪除元素
相較於插入操作,紅黑樹的刪除操作則要更為復雜一些。刪除操作首先要確定待刪除節點有幾個孩子,如果有兩個孩子,不能直接刪除該節點。而是要先找到該節點的前驅(該節點左子樹中最大的節點)或者后繼(該節點右子樹中最小的節點),然后將前驅或者后繼的值復制到要刪除的節點中,最后再將前驅或后繼刪除。由於前驅和后繼至多只有一個孩子節點,這樣我們就把原來要刪除的節點有兩個孩子的問題轉化為只有一個孩子節點的問題,問題被簡化了一些。
刪除一個節點有以下四種情況:
1.刪除的節點沒有孩子
2.刪除的節點只有左子樹
3.刪除的節點只有右子樹
*4.刪除的節點擁有左子樹和右子樹
其實只有上面前三種情況,對於第四種情況,可以找到待刪除節點的直接后繼節點,用這個節點的值替代待刪除節點,接着情況轉變為刪除這個直接后繼節點,情況也變為前三種之一。
1.刪除的節點只有左子樹或只有右子樹
或者
只有上面兩種情況會存在於紅黑樹中,直接用DL/DR的元素值代替D的元素,再把DL/DR直接刪去就好。
2.刪除的節點沒有孩子
1)待刪除節點是紅色的,直接刪去這個節點。
2)父節點P是紅色節點
解決方案:把P節點染成黑色,兄弟節點染成紅色,刪除節點D。
3)兄弟節點S是紅色節點
或者
解決方案:把P染成紅色,S染成黑色,然后以P為軸做相應的旋轉操作(D為P的左子樹則左旋,否則右旋),變成了情況2(父節點為紅色),按照情況2進行操作。
4)節點D的遠親侄子為紅色節點的情況(父節點P可紅可黑)
解決方案:交換P和S的顏色,然后把遠親侄子節點SR/SL設置為黑色,再已P為軸做相應的旋轉操作(D為P的左子樹則左旋,否則右旋),刪除節點D。
5)節點D的近親侄子為紅色節點的情況(父節點P可紅可黑)
解決方案:把S染成紅色,把近親侄子節點SR/SL染成黑色,然后以節點S為軸做相應的旋轉操作(D為P的左子樹則右旋,否則左旋),變成了情況4,按照情況4進行操作。
6)節點D,P,S均為黑色節點
解決方案:把D刪去,然后把節點S染成紅色。
①從節點P往上依然是全黑的情況
②從節點P往上是其他情況
參考博客1:https://segmentfault.com/a/1190000012728513
參考博客2:https://www.cnblogs.com/yinbiao/p/10732600.html
參考博客3:https://www.cnblogs.com/GNLin0820/p/9668378.html
參考博客4:https://blog.csdn.net/tanrui519521/article/details/80980135