很早以前就想寫一篇關於紅黑樹的文章,可是因爲擔憂本身理解的不透徹,就一直不敢下筆。因而在從新看了不少篇文章和資料以後,決定不折不扣的把紅黑樹搞清楚。也但願讓你在面試中游刃有餘。OK,廢話很少說,開始今天的文章。java
整篇文章的思路是這樣的,紅黑樹其實就是一種數據結構,設計它的目的就是爲了高效地進行增刪改查,因此咱們文章的順序也會按照這個思路來進行。咱們先從二叉查找樹逐漸引入到紅黑樹,而後再詳細的講解。你若是看過其餘文章想必也必定清楚,紅黑樹比較麻煩,但願你有點耐心,認真理解每一張圖再往下分析。linux
1、二叉查找樹
在正式開始瞭解紅黑樹以前呢,咱們先來看一下二叉查找樹的概念,從淺入深,但願你不要着急,下面就是是一顆二叉查找樹:面試
從這張圖咱們會發現以下的規律:算法
(1)左子樹上全部結點的值均小於或等於它的根結點的值。微信
(2)右子樹上全部結點的值均大於或等於它的根結點的值。數據結構
若是咱們想要查找一個數字11,過程是怎麼樣的呢?app
上面的過程已經很清晰了,在查找的時候,先與根節點比較,比根節點大則從右子樹查找,比根節點小則從左子樹查找,而後重複上面的過程,一直到找到咱們須要的元素爲止。spa
這個過程是查找操做,對於添加和刪除呢?其實原理也是同樣的,咱們第一步就是找到咱們須要插入的位置,而後把元素插入便可。這樣看二叉查找樹挺好的呀?彆着急咱們繼續往下看這種狀況。.net
若是咱們在剛剛開始的時候仍是以9爲根節點,而後依次插入1三、1五、1七、19。咱們看會發生什麼狀況:設計
好好地一棵樹變成了這個鬼樣子,成了「一邊倒」了。這時候再去查找19呢?
這效率也過低下了吧,一顆二叉查找樹的優點徹底喪失了。怎麼辦呢?既然上面的二叉查找樹在插入的時候變成了「一條腿」,也就是喪失了平衡,那咱們乾脆作出一點改進,使用平衡二叉樹吧。
2、平衡二叉樹
下面就是一顆平衡二叉樹。
上面這顆二叉樹就是平衡二叉樹,也叫做AVL樹。仔細分析你會發現以下特色:
(1)從任何一個節點出發,左右子樹深度之差的絕對值不超過1。
(2)左右子樹仍然爲平衡二叉樹。
如今咱們再往裏插入一個元素4,這時候會發生什麼呢?
從圖中咱們能夠看到,插入了4以後破壞了平衡,怎麼辦呢?既然破壞了平衡,那就想辦法糾正過來。
咱們發現通過調整以後,咱們二叉樹就從新回到了平衡。對於其餘插入的狀況,你們能夠本身私下試一遍,最終你會發現一個結論,那就是平衡二叉樹在插入時最多隻須要兩次旋轉就會從新恢復平衡。
從上面這個過程咱們會發現,平衡二叉樹真的很不錯,在查找時既有着二叉查找樹的優越性,在插入時還能經過調整繼續保持着。那麼爲何還要使用到紅黑樹呢?我以爲能夠從如下兩個方面來考慮:
(1)刪除:對於平衡二叉樹來講,在最壞狀況下,須要維護從被刪節點到根節點這條路徑上全部節點的平衡性,旋轉的量級是O(logN)。可是紅黑樹就不同了,最多隻需3次旋轉就會從新平衡,旋轉的量級是O(1)。
(2)保持平衡:平衡二叉樹高度平衡,這也就意味着在大量插入和刪除節點的場景下,平衡二叉樹爲了保持平衡須要調整的頻率會更高。
注意:在大量查找的狀況下,平衡二叉樹的效率更高,也是首要選擇。
在大量增刪的狀況下,紅黑樹是首選。
鑑於以上緣由,所以咱們才使用到了紅黑樹這種更好的結構。上面提了這麼屢次紅黑樹,相信你已經火燒眉毛的想要認識一下了。下面就正式拉開紅黑樹的序幕。
3、紅黑樹
紅黑樹聽名字就知道,裏面涉及到兩種顏色:紅色和黑色。咱們直接來看一下:
上面這張圖就是紅黑樹,你會發現他有以下特徵(下面的特徵最好看一個特徵從新看一遍紅黑樹):
(1)每一個節點只有兩種顏色:紅色和黑色。
(2)根節點是黑色的。
(3)每一個葉子節點(NIL)都是黑色的空節點。
(4)從根節點到葉子節點,不會出現兩個連續的紅色節點。
(5)從任何一個節點出發,到葉子節點,這條路徑上都有相同數目的黑色節點。
這五條就是紅黑樹的特徵,你每看一個特徵最好從新看一遍圖,這樣能夠加深理解。這五條特徵看起來真的很複雜,不過正是因爲這些複雜的特徵才保證了紅黑樹的良好特性。如何保證的呢?咱們從增刪改查四個角度來一個一個分析一下:
一、查詢節點
查詢節點是最簡單的一個,他的查找過程和二叉查找樹同樣,查找元素比當前節點大,就從右子樹繼續查找比較,查找元素比當前節點小,就從左子樹繼續查找比較。查找過程就再也不贅述了。
二、插入節點
插入節點是最麻煩的一個,它分爲三種狀況。咱們一種一種看,這樣比較有條理性。
第一種狀況:新節點沒有父節點
沒有父節點只有一種狀況,就是插入的節點是整棵樹第一個節點,也就是根節點,爲此咱們只須要把插入節點塗成黑色就OK了。這也就保證了性質2:根節點是黑色的。
第二種狀況:新節點的父節點是黑色
爲此咱們舉一個例子,好比說上面的紅黑樹中,咱們插入節點14。來看一下會發生什麼狀況?
這種狀況咱們發現新插入節點14的父節點就是黑色的。如今爲了保證紅黑樹的性質,咱們對照每一個特性來檢查一遍。只要有一條不知足,咱們都須要調整。咱們從新對照以後會發現每一條都符合。此時不須要調整。
第三種狀況:新節點的父親節點爲紅色
咱們仍是舉個例子,好比咱們在最開始的紅黑樹基礎之上插入節點21,此時會發生什麼狀況呢?
此時仍是老規矩,對照着紅黑樹的5個特徵一個一個來看,只要是違反了一條就須要作出調整。咱們來看一下:
(1)每一個節點只有兩種顏色:紅色和黑色。這一條知足。
(2)根節點是黑色的。這一條也知足。
(3)每一個葉子節點(NIL)都是黑色的空節點。這一條知足。
(4)從根節點到葉子節點,不會出現兩個連續的紅色節點。這一條發現不知足。
就是上面這一條規則沒有知足,因此咱們此時須要調整?問題來了如何調整呢?由於直接看父節點沒辦法實現,因此還須要觀察另外的節點,也就是新節點的叔叔節點。根據叔叔節點的顏色來調整。調整的方式有兩種:變色和旋轉。
(1)叔叔節點是紅色:
此時插入的節點是21,可是叔叔節點是27,更好是紅色。咱們直接來看調整的步驟:
第一步:把新節點21的父節點變成黑色。
此時從新看一下是否知足紅黑樹的五條特徵了沒,一條一條發現,第五條沒有知足,也就是從任何一個節點出發,到葉子節點,這條路徑上沒有相同數目的黑色節點。好比從25出發。這時候怎麼辦呢?那就繼續調整。
第二步:把22的父節點25變成紅色
這時候仍是老規矩,不要嫌棄麻煩,由於只有經歷了一步又一步的麻煩以後,你才能牢記那5條規則特徵。咱們對照以後會發現節點25和節點27是兩個連續的紅色節點,這時候又破壞了規則4。怎麼辦呢?那就繼續調整就OK了。
難道這時候還要繼續往上調整嗎?若是你這樣作就錯了,由於不斷地往上調整最後就會把根節點變成了紅色,會走進死衚衕。咱們往下走。
第三步:把節點27變成黑色
來吧,繼續從新審查那5條規則特徵。很明顯節點17和節點25是兩個連續的紅色,又破壞了。是否是心太累了,調整了這麼久,仍是沒有保證那5條規則,感受是否是尚未平衡二叉樹好。若是你如今有這種感受,我只能說,但願你繼續堅持下去,勝利就在眼前。
第四步:把節點17和節點18都變成黑色節點
來來來,如今你再對照一下那5條規則,是否是徹底保證了。寫到這真的是太累了,和你讀這篇文章的感受同樣同樣的,不過這種狀況也只是插入狀況中的一種。繼續往下看:
(1)叔叔節點是黑色:
這種狀況下又分了兩種狀況:
第一種狀況:新插入節點爲父節點的左孩子
第二種狀況:新插入節點爲父節點的右孩子
按照第一遍的思路,咱們對這兩種狀況執行一樣的操做,最終也能保證紅黑樹的5條特徵。
到了這一步,插入操做的全部狀況就講解完畢。另外關於左旋和右旋的知識我在這裏再也不說明了,由於你看到了紅黑樹這個程度,相信也必定看過平衡二叉樹。左旋右旋哪幾種狀況,都會有介紹到。
三、刪除節點
紅黑樹的刪除說實話更加的複雜,若是你看過算法導論的話應該能明白一點,咱們在這裏也進行一個大概的講解。
刪除大體分了三種狀況,
(1)第一種狀況:要刪除的節點有零個子節點
這種狀況下最簡單,也就是刪除的是根節點或者是葉子節點(這裏的葉子節點都是指非NULL的葉子節點),根節點直接刪除便可。若是葉子節點是紅色的,也能夠直接刪除,若是葉子節點是黑色的,那麼就須要進行調整,調整的步驟和插入時調整的步驟同樣。
(2)第二種狀況:要刪除的節點有一個子節點
這時候。把子節點的值替換掉要刪除的節點的值。
如今咱們的5把11替換掉以後,又回到了第一種狀況。若是節點5是紅色的,能夠直接刪除,若是節點5是黑色的,那麼就須要進行調整,此時的節點5就是葉子節點。調整的步驟和插入時調整的步驟同樣。
(3)第三種狀況:要刪除的節點有兩個子節點
如今刪除的節點有兩個子節點,一樣的咱們能夠執行第二種狀況的操做,
若節點13以前是葉子節點,那就和第一種狀況同樣了,若是節點13是紅色的,能夠直接刪除,若是節點13是黑色的,那麼就須要進行調整,此時的節點13就是葉子節點。調整的步驟和插入時調整的步驟同樣。
若節點13以前還有子節點,那就和第二種狀況同樣了。那就繼續替換和判斷。
以上呢就是刪除的狀況,最後一種狀況是修改,這種狀況是查找和插入的結合體,也就是先找到要修改的元素,修改完值以後,繼續進行調整便可。
如今還有最後一個問題了,都說紅黑樹很重要,爲何重要呢?咱們來看一下使用場景。
4、使用場景
紅黑樹的應用真的是太多了,好比說java中的HashMap和TreeMap。還有就是linux也常用到。這種數據結構在面試的時候是一個常問問題,但願你們可以明白和理解。如何用java手撕紅黑樹,在後續文章中我會添加。若有問題還請批評指正。
本文分享自微信公衆號 - 愚公要移山(fdd_sxu_nwpu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。