一、前言
這幾天想學一學紅黑樹這種數據結構,於是上網找了很多篇博客,初看嚇了一跳,紅黑樹竟然如此復雜。連續看了幾篇博客后,算是對紅黑樹有了一些了解,但是它的原理卻並不是特別理解。網上的博客,千篇一律的都是在敘述紅黑樹的操作,如何插入節點、刪除節點,旋轉、變色等,只關注如何正確構建一棵紅黑樹,但是卻很少提及為什么這么做。這篇博客我就來記錄一些我所知道的紅黑樹中比較重要的東西,以及談一談我的理解。
我不會描述紅黑樹的具體實現,因為閱讀紅黑樹具體實現的過程中,我發現這真的不是很重要,沒有太大的意義(這絕對不是自我安慰⊙﹏⊙∥),真正重要的是紅黑樹的思想。如果想要了解紅黑樹的具體實現,建議閱讀這篇文章:https://mp.weixin.qq.com/s/hGHJonK999TAVJakPDNAkg
二、正文
2.1 二叉搜索樹和平衡二叉樹
在談紅黑樹之前,先來說一說二叉搜索樹以及平衡二叉樹,因為平衡二叉樹是為了彌補二叉搜索樹而發明出來的,而紅黑樹又是為了彌補平衡二叉樹。
(1)二叉搜索樹
二叉搜索樹比較簡單,它是一棵二叉樹,而且滿足這樣一個性質:對於樹上的每一個節點,它左子樹上的節點的值都比它小,而右子樹上的節點的值都比它大。如下,就是一棵二叉搜索樹:
當我們需要往一棵二叉搜索樹中插入節點時,只需要從根節點開始,依次比較,若比根節點小,則向左走,繼續和左子節點比較,反之繼續和右子節點比較,以此類推。這樣的操作其實就和二分差不多,在節點值分布均勻的情況下,也就是樹的結構如上所示時,查找的時間復雜度為O(logN)
,因為二叉搜索樹的查詢完全復雜度依賴於樹的深度。所以,它有一個很大的缺陷,就是可能出現如下情況:
我們每次插入的節點都比最小值要小,於是最終所有的節點就連成了一條鏈,此時深度為N,查找的時間復雜度就是O(N)了。為了解決這個問題,於是又有了平衡二叉樹。關於二叉搜索樹,可以參考這個博客:https://www.jianshu.com/p/ff4b93b088eb。
(2)平衡二叉樹
平衡二叉樹也是一棵二叉搜索樹,但是它還有一個性質,就是這棵樹上的每一個節點,其左子樹和右子樹的高度差都不超過一。比如圖一中的二叉樹,它就時一棵平衡二叉樹,而下面這張圖中的樹就不是平衡二叉樹,因為節點9
和節點5
都不平衡,它們的左右子樹的高度差都是2
。
當發生不平衡的情況時,可以通過左旋和右旋的方式,在不改變二叉搜索樹的性質的前提下,將樹調整為平衡狀態,這就是平衡二叉樹。對於左旋和右旋的具體實現,我這里就不描述了,想要了解可以參考這篇博客:https://www.jianshu.com/p/655d83f9ba7b。
2.2 有了二叉搜索樹和平衡二叉樹,為什么還要紅黑樹
這個問題是一個面試題,下面我就來簡單敘述一下。首先,二叉搜索樹在極端情況下會變成一個鏈表,從而導致查詢的時間復雜度大大提高,變成線性復雜度,而平衡二叉樹正是為了解決這個問題才被發明出來。但是,平衡二叉樹也有一定的局限。對於平衡二叉樹來說,每個節點的子樹的高度差不能超過1
,一旦出現違反的情況,就需要進行左旋或者右旋操作,從而調整二叉樹的高度。但是,在插入和刪除節點的過程中,平衡度超過1
是經常發生的事情,這也就導致了調整操作頻繁地發生,從而提高時間復雜度。也就是說,這種追求絕對平衡的行為,並不是一個好的做法。
紅黑樹的出現正是為了解決平衡二叉樹的問題,它是平衡二叉樹的變形,或者說升級。紅黑樹並不追求絕對的平衡,它實現的是近似的平衡,這樣破壞規則導致需要調整的情況就會大大減少,而且每次也只需要少量的調整即可恢復。也就是說,他在平衡和效率之間做了一個折中。但是統計發現,這種折中對查找的效率並沒有多大的降低,這也就導致了紅黑樹在大多數情況下要優於平衡二叉樹。而紅黑樹的這種折中,依靠的就是它五條性質,或者說五條約束來實現。
2.3 紅黑樹的五大性質
這里先放出紅黑樹的五大性質:
- 樹中的所有節點,要么是紅色,要么是黑色;
- 樹的根節點是黑色;
- 樹中的葉子節點是黑色;(注意:這里的葉子節點不是指沒有子節點的節點,而是指的空節點,比如某個節點沒有左子節點,那這個不存在的左子節點其實就是這里說的葉子節點)
- 紅色節點的子節點一定是黑色,也就是說沒有兩個連在一起的紅色節點;
- 每個節點到任意一個葉子節點,經過的黑色節點數量是相等的;
這五條性質單獨分開來看都很好理解,但是放在一起就有點迷惑了,為什么是這五條性質,它們之間有什么關系?關系肯定是有的,發明紅黑樹的專家,經過大量的數學推導,理論驗證,才組合出這五條性質,它們相輔相成,共同維護了紅黑樹的結構。就像機器人三大准則一樣,相輔相成,完全地約束了機器人的行為。這里就來簡單描述幾條紅黑樹的定理,它們都是由上述結論推導而來。
2.4 紅黑樹的定理
(1)紅黑樹從根節點到葉子節點的最長路徑,不會大於最短路徑的兩倍
這條定理是怎么來的呢?它是通過上面的定理推導而來。我們看上述性質5
可知,從根節點到任意一個葉子節點,經過的黑色節點數目是相同的。而性質4
又規定,不能有兩個紅色節點連在一起。於是我們這樣考慮,假設從根節點到每個葉子節點,都是經過3
個黑色節點,那么怎樣才能讓路徑盡可能長?答案當然是盡量在黑色節點中插入紅色節點,用紅色節點充數。但是由於性質4
,所以紅色節點不能多放。於是,理論上的最長路徑,一定是一個黑節點,連着一個紅節點,再連着一個黑節點,以此類推。我們之前假設每條路徑都是3
個黑節點,再加上性質2
說根節點一定是黑色,所以從根節點到子節點最多只能有6
個節點,三黑三紅交叉連接。那理論上的最短路徑又是如何?當然是不包含任何一個紅色節點,也就是只有3
個黑節點。也就是說,理論上,最長路徑,最多是最短路徑的兩倍。這個結論就限制了紅黑樹的最大平衡差,也就保證了不會出現圖二這種情況。
(2)一棵含有n個節點的紅黑樹的高度至多為2log(n+1)
這個結論是經過一系列數學推導得出的,比較復雜,我也沒有仔細研究過,想要了解的可以看看這篇博客:https://www.cnblogs.com/skywang12345/p/3245399.html#a2。
從這個結論可以看出什么?紅黑樹也是一個二叉搜索樹,而二叉搜索樹的查找次數取決於樹的深度。此結論說明紅黑樹的高度至多為2log(n+1)
,也就是說紅黑樹的查找效率也是O(logn)
級別的,和平衡二叉樹近似,再加上紅黑樹的調整平衡的次數相對較少,所以總體來說,紅黑樹要優於平衡二叉樹。
三、總結
最后我忍不住吐槽一下,我看了大量紅黑樹的講解博客,都是千篇一律的在描述如何操作構建一棵紅黑樹,如何操作能夠不違反紅黑樹的這五條性質,一旦講完操作就戛然而止。在學習紅黑樹時,我硬着頭皮把紅黑樹的操作看完,才發現這些操作根本沒有太大的意義,它們只不過是列舉操作紅黑樹的各種情況,告訴你什么時候應該怎么做而已,而你只能背下這些操作,但是完全不能理解這些操作有什么意義,只知道它們唯一的作用就是維持這五大性質。仿佛將紅黑樹的操作實現強行記下,寫出代碼,就是學會了紅黑樹,理解了紅黑樹。可是實際上,這五條性質只是結論,只是他人研究的最終產物罷了,而紅黑樹僅僅是在使用這些結論。如果只是學會了紅黑樹的實現方式,僅僅只是在使用這些結論,和調API
有什么區別。但是,網上的博客都是千篇一律,偏偏標題取得是一個比一個厲害,什么《學會紅黑樹這一篇就夠了》,《十分鍾徹底理解紅黑樹》,《看完這篇博客,再也不怕面試管問紅黑樹了》......這些標題的博客數不勝數,但是內容卻都是在介紹操作,極少敘述思想。一想到我花了大量時間,最后發現自己看的都是些意義不大的實現細節,就感覺對不起自己。
我個人認為,學習算法和數據結構,真正重要的是理解它的思想,而不是如何做。說句實話,如果不是做算法相關的工作,工作中真的需要我們自己寫紅黑樹嗎?可以說基本上不可能用到。當然,不是說研究具體實現是一種錯誤的行為,研究算法和數據結構的實現能夠鍛煉自己的編碼能力,是有好處的。但是大部分人是為了學習數據結構本身,如果只學會了寫代碼,不知道原理,那就有點本末倒置了,而且很快就會忘記。