面試官 :小桂子是吧,看你簡歷上寫着精通 java 編程,想必對 java 已經掌握的很好了吧?
小桂子 :系呀系呀,一直都用 java 寫 bug 呢~
面試官 :那你說說 jdk1.7 以前 HashMap 的底層實現原理唄,另外爲何在高併發場景下可能形成較高的 CPU 佔用?
小桂子 :這個。。。好像是紅黑樹?
面試官 :哦?你說的是 jdk1.8 以後的設計,既然你提到了,那就聊聊紅黑樹這個數據結構吧,這裏是白紙和筆,手寫一棵吧!
小桂子 :哎呀,哎呀哎呀,老師,忽然肚子好疼,我要去一下廁所,一下子就回來~~~html
面試到處是套路呀。。。不知道你是否有和小桂子同樣尷尬的面試經歷呢,若是有的話歡迎到評論區留言,說出你的故事~java
接下來咱們進入正題,開始探究面試官爲難小桂子的紅黑樹。說到紅黑樹,大部分人應該對他既熟悉又陌生,熟悉是由於咱們天天 coding 都會直接或者間接的用到它,可是設計和實現上的複雜性又讓不少人對其原理望而卻步。紅黑樹的定義比較簡單,無非是在插入和刪除的過程當中自平衡規則多了一些,不過再多也只是個位數而已,只要靜下心來跟隨本文,相信你會有所收穫,let's moving...git
接下去的篇幅小編假設你已經對二叉樹、平衡二叉樹的結構、做用,以及弊端有必定的瞭解。github
紅黑樹(如上圖,引用自維基百科)是一種 自平衡 的二叉樹,所謂的自平衡是指在插入和刪除的過程當中,紅黑樹會採起必定的策略對樹的組織形式進行調整,以儘量的減小樹的高度,從而節省查找的時間。紅黑樹的特性以下:面試
- 結點是紅色或黑色
- 根結點始終是黑色
- 葉子結點(NIL 結點)都是黑色
- 紅色結點的兩個直接孩子結點都是黑色(即從葉子到根的全部路徑上不存在兩個連續的紅色結點)
- 從任一結點到每一個葉子的全部簡單路徑都包含相同數目的黑色結點
以上性質保證了紅黑樹在知足平衡二叉樹特徵的前提下,還能夠作到 從根到葉子的最長路徑最多不會超過最短路徑的兩倍 ,這主要是考慮兩個極端的狀況,由性質 4 和 5 咱們能夠知道在一棵紅黑樹上從根到葉子的最短路徑所有由黑色結點構成,而最長結點則由紅黑結點交錯構成(始終按照一紅一黑的順序組織),又由於最短路徑和最長路徑的黑色結點數目是一致的,因此最長路徑上的結點數是最短路徑的兩倍。算法
對於一棵紅黑樹的操做最基本的無外乎增刪改查,其中查和改都不會改變樹的結構,因此與普通平衡二叉樹操做無異。剩下的就是增刪操做,插入和刪除都會破壞樹的結構,不過藉助必定的平衡策略可以讓樹從新知足定義。平衡策略能夠簡單歸納爲三種: 左旋轉 、 右旋轉 ,以及 變色 。在插入或刪除結點以後,只要咱們沿着結點到根的路徑上執行這三種操做,就能夠最終讓樹從新知足定義。編程
對於當前結點而言,若是右子結點爲紅色,左子結點爲黑色,則執行左旋轉,以下圖:數據結構
對於當前結點而言,若是左子、左孫子結點均爲紅色,則執行右旋轉,以下圖:併發
對於當前結點而言,若是左、右子結點均爲紅色,則執行變色,以下圖:高併發
紅黑樹做爲平衡二叉樹的一種,一樣須要藉助於查找操做定位插入點,不過紅黑樹約定 新插入的結點一概爲紅色 ,這主要也是爲了簡化樹的自平衡過程。對於一棵空樹而言,插入結點爲紅色會增長一次變色操做,可是對於其他的狀況,若是插入的結點是一個黑色結點,那麼必然會破壞性質 5,而插入一個紅色結點有可能會破壞性質 4,可是此時咱們能夠經過簡單的策略對樹進行調整以從新知足定義。
咱們約定 X 爲插入的結點,P 爲 X 的父結點,G 爲 X 的祖父結點,U 爲 X 的叔叔結點。
下面聽從上述策略分場景對插入過程進行探討:
1.新插入結點 X 是根結點
此時新插入結點爲紅色,違背性質 2,只需將其變爲黑色便可。
2.新插入結點 X 的父結點 P 是黑色
此時須要依據新插入結點 X 值相對於父結點 P 的大小分爲兩種狀況,若是小於則將 X 簡單插入到 P 的左子位置便可(下圖左),若是 X 的值大於 P,則須要將 X 插入到 P 的右子結點位置,而後執行一次左旋轉便可(下圖右)。
3.父結點 P 爲紅色,同時存在叔叔結點 U 也爲紅色
由於 P 爲紅色,按照性質 4 則 G 一定爲黑色,若是 X 的值小於 P,則須要在 P 的左子位置插入(以下圖),插入後不知足性質 4,此時只須要執行一次變色操做,將 P、G、U 的顏色反轉一下便可,由於 G 變爲紅色,因此路徑長度減 1,可是由於 P 和 U 都變爲了黑色,因此路徑長度又加 1,最終長度不變,但此時 G 變爲了紅色,因此須要繼續向上遞歸。
若是 X 的值大於 P,則須要在 P 的右子位置插入(以下圖),插入後不知足性質 4,此時須要先執行左旋轉變爲上面這種狀況,繼續變色便可。
4.父結點 P 爲紅色,同時叔叔結點 U 爲黑色或不存在
由於 P 爲紅色,按照性質 4 則 G 一定爲黑色,若是 X 的值小於 P,則須要在 P 的左子位置插入(以下圖),插入後不知足性質 4,此時須要先執行一次右旋轉,旋轉以後仍然違背性質 4,同時左子樹的高度減 1,這個時候須要再執行一次變色操做便可知足定義。
若是 X 的值大於 P,則須要在 P 的右子位置插入(以下圖),插入後不知足性質 4,此時咱們須要執行一次左旋轉,而後就轉換成了上面這種狀況,繼續右旋轉、變色便可。
紅黑樹做爲平衡二叉樹的一種,一樣須要藉助於查找操做定位刪除點,在執行刪除以前咱們須要判斷待刪除結點有幾個孩子結點,若是是 2 個的話咱們須要從結點的左子樹中尋找值最大的結點,或者從右子樹中尋找值最小的結點,並用結點值替換掉待刪除結點(只要目標結點值從樹上消失便可,不要糾結具體刪除的是哪一個結點)。這兩個結點有一個共性,即最多隻有一個孩子結點(由於已是本身所處範圍內的最大和最小了嘛,一山不容二虎(鼠)),此時就將需求轉變成刪除只有一個孩子結點的結點,相對要簡單了許多。
咱們約定 X 爲待刪除的結點,P 爲 X 的父結點,S 爲 X 的孩子結點,B 爲 X 的兄弟結點,BL 爲 B 的左孩子結點,BR 爲 B 的右孩子結點。
對於第三種狀況咱們首先將 X 替換成 S,並重命名其爲 N,N 沿用 X 對於長輩和晚輩的稱呼,須要清楚這裏實際刪除的是 X 結點,而且刪除以後經過 N 的路徑長度減 1。
1.N 是新的根
這種狀況比較簡單,不須要再作任何調整。
2.N 的父結點、兄弟結點 B,以及 B 的孩子結點均爲黑色
以下圖,此時只須要將 B 變爲紅色便可,這樣全部經過 B 的路徑減 1,與全部經過 N 的路徑正好一致,可是此時經過 P 的路徑都減小了 1 個長度,因此須要向上遞歸對結點 P 繼續斷定。
3.N 的兄弟結點 B 爲紅色,其他結點均爲黑色
以下圖,此時須要執行一次左旋轉,而後將 P 和 B 的顏色互換。調整先後各個結點的路徑沒有變化,可是由於以前通過 N 的路徑長度少了一個單位,因此此時仍然不知足定義,須要按照後面的場景繼續調整。
4.N 的父結點 P 爲紅色,兄弟結點 B,以及 B 的孩子結點均爲黑色
以下圖,此時咱們只須要簡單互換 P 和 B 的顏色,這種狀況下對於不經過 N 的結點路徑沒有影響,可是卻讓經過 N 的結點路徑加 1,正好彌補以前刪除操做所帶來的損失。
5.N 的兄弟結點 B 爲黑色,B 的左孩子爲紅色,B 的右孩子爲黑色
以下圖,此時咱們須要先執行一次右旋轉操做,而後互換 B 與 BL 的顏色,操做以後經過全部結點的路徑長度並無發生變化,卻讓 N 有了一個新的黑色兄弟結點,而且該兄弟結點的右孩子爲紅色,從而能夠按照接下去介紹的一種場景繼續調整。
注:白色結點表示該結點既能夠是黑色也能夠是紅色,後續圖示亦是如此。
6.N 的兄弟結點 B 爲黑色,B 的右孩子爲紅色
以下圖,此時咱們須要先執行一次左旋轉,並互換 P 和 B 的顏色,同時將 B 的右孩子結點變爲黑色。變動以後,除 N 外其他結點的路徑長度未發生變化,可是通過 N 的路徑上卻增長了一個黑色結點,這恰好彌補以前刪除操做所帶來的損失。
紅黑樹的主要難點在於插入和刪除過程當中的自平衡調整,其中插入過程的調整相對簡單,刪除的過程須要處理的狀況要多一些,但不論是插入仍是刪除,都建議讀者將全部的圖放置在一塊兒進行觀察,可以發現其中承前啓後的奧妙,本文鑑於篇幅就再也不貼出長圖。
另外也建議讀者按照上述過程本身在白紙上手動去構造一棵紅黑樹,並逐一將結點刪除,以此來加深理解,也能夠舊金山大學提供的交互網站輔助學習(點此前往),相關實現位於 algorithm-design 項目的 org.zhenchao.classic.search
包下面,地址:
https://github.com/plotor/algorithm-design
《算法》紅寶書的做者之一 「羅伯特·塞奇威克」 是紅黑樹的提出者,紅黑樹是在 2-3 樹的基礎上改進而成,相對於紅黑樹而言 2-3 樹的自平衡策略要容易理解不少,在此也推薦你們在學習時參閱相關章節。