1. 程式人生 > >瞭解紅黑樹的起源,理解紅黑樹的本質

瞭解紅黑樹的起源,理解紅黑樹的本質

# 前言 > 本文收錄於專輯:[http://dwz.win/HjK](http://dwz.win/HjK),點選解鎖更多資料結構與演算法的知識。 你好,我是彤哥。 前面兩節,我們一起學習了關於跳錶的理論知識,並手寫了兩種完全不同的實現,我們放一張圖來簡單地回顧一下: ![15](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221029345-1242929879.jpg) 實現跳錶的關鍵之處是在有序連結串列的基礎上加上各層索引,通過這些索引可以做到O(log n)的時間複雜度快速地插入、刪除、查詢元素。 說起跳錶,我們就不得不提另一種非常經典的資料結構——紅黑樹,紅黑樹相對於跳錶來說,雖然時間複雜度都是O(log n),但是紅黑樹的使用場景相對更廣泛一些,在早期的Linux核心中就一直存在紅黑樹的實現,也運用在了更高效的多路複用器Epoll中。 所以,紅黑樹是每一個程式設計師不得不會的知識點,甚至有些變態的面試官,還會讓你手寫紅黑樹的一部分實現,比如左旋、右旋、插入平衡的過程、刪除平衡的過程,這些內容非常複雜,靠死記硬背往往很難徹底掌握。 彤哥也是一直在尋找一種紅黑樹的記憶法,總算讓我找到了那麼一種還算不錯的方式,從紅黑樹的起源出發,理解紅黑樹的本質,再從本質出發,徹底掌握不用死記硬背的方法,最後再把它手寫出來。 從本節開始,我也將把這種方法傳遞給你,因此,紅黑樹的部分,我會分成三個小節來講解: - 從紅黑樹的起源,到紅黑樹的本質 - 從紅黑樹的本質,找到不用死記硬背的方法 - 不靠死記硬背,手寫紅黑樹 好了,下面我們就進入第一小節。 # 紅黑樹的起源 ## 二叉樹 說起樹,我們不得不說最有名的樹,那就是二叉樹,什麼是二叉樹呢? **二叉樹(binary tree)**,是指樹中的每個節點最多隻有兩個子節點的樹。 ![1](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221029940-1224265774.jpg) 當然,二叉樹本身似乎沒什麼用,我們平時說的二叉樹基本上都是指二叉查詢樹,或者叫有序二叉樹、二叉搜尋樹、二叉排序樹。 ## 二叉查詢樹 **二叉查詢樹(BST,binary search tree)**,就是在二叉樹的基礎上增加有序性,這個有序性一般是指自然順序,有了有序性,我們就可以使用二叉樹來快速的查詢、刪除、插入元素了。 ![2](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221030432-711590938.jpg) 比如,上面這顆二叉查詢樹,查詢元素的平均時間複雜度為O(log n)。 但是,二叉查詢樹有個非常嚴重的問題,試想,還是這三個元素,如果按照A、B、C的順序插入元素會怎樣? ![3](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221030937-1391530984.jpg) 這是啥?單鏈表?沒錯,當按照元素的自然順序插入元素的時候,二叉查詢樹就退化成單鏈表了,單鏈表的插入、刪除、查詢元素的時間複雜度是多少?O(n)。 所以,在極限情況下,二叉查詢樹的時間複雜度是非常差的。 既然,插入元素後有可能導致二叉查詢樹的效能變差,那麼,我們是否可以增加一些手段,讓插入元素後的二叉查詢樹依然效能良好呢? 答案是肯定的,這種手段就叫做`平衡`,這種可以自平衡的樹就叫做平衡樹。 ## 平衡樹 **平衡樹(self-balancing or height-balanced binary search tree)**,是指插入、刪除元素後可以自平衡的二叉查詢樹,使得它的時間複雜度可以一直漸近於O(log n)。 比如,上面那顆樹,按A、B、C插入元素後,做一次旋轉操作,就可以再次變成查詢時間複雜度為O(log n)的樹。 ![4](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221031475-132666712.jpg) 但是,平衡樹一直只是一個概念,直到1962年才由兩個蘇聯人發明了第一種平衡樹——AVL樹。 > 嚴格來說,平衡樹是指可以自平衡的二叉查詢樹,三個關鍵詞:自平衡、二叉、查詢(有序)。 ## AVL樹 AVL樹(由發明者**A**delson-**V**elsky 和 **L**andis 的首字母縮寫命名),是指任意節點的兩個子樹的高度差不超過1的平衡樹。 ![5](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221032043-1804791188.jpg) 比如,上面這顆樹,就是一顆AVL樹,不信你可以數數看,是不是每個節點的兩個子樹的高度差都不超過1。 是不是很難發現它真的是一顆AVL樹,沒錯,這是AVL樹的第一個缺點,不夠直觀,特別是節點個數多的時候。 第二個缺點,就是插入、刪除元素的時候自平衡的過程非常複雜,比如,上面這顆樹插入一個節點`T`: ![6](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221032623-461173927.jpg) 我們從T往上找,它的父節點U,U的兩顆子樹的高度差為1,滿足AVL樹的規則,再往上,S的兩顆子樹的高度差為1,也滿足規則,再往上,V的兩顆子樹的高度差為2,不滿足規則,此時,需要一個自平衡的過程,該如何自平衡呢? 我下面給出圖示,你可以試著理解一下: ![7](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221033218-651065667.jpg) 紅色節點表示旋轉的軸。 經過兩次旋轉,讓這顆樹再次變成了AVL樹,而且這只是其中一種插入場景,真實的情況還要根據插入的位置的不同做不同的旋轉,你可以多插入幾個節點自己嘗試平衡一下。 同樣地,AVL樹的程式碼也不是那麼好實現的,反正,到目前為止,彤哥是沒搞懂AVL樹的各種規則。 基於這些缺點,所以,後來又發展出來了各種各樣的神奇的平衡樹。 ## 2-3樹 **2-3樹**,是指每個具有子節點的節點(內部節點,internal node)要麼有兩個子節點和一個數據元素,要麼有三個子節點和兩個資料元素的自平衡的樹,它的所有葉子節點都具有相同的高度。 簡單點講,2-3樹的非葉子節點都具有兩個分叉或者三個分叉,所以,稱作2叉-3叉樹更容易理解。 另外一種說法,具有兩個子節點和一個數據元素的節點又稱作2節點,具有三個子節點和兩個資料元素的節點又稱作3節點,所以,整顆樹叫做2-3樹。 ![8](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221033869-414059503.jpg) 2-3樹,插入元素後自平衡的過程相對於AVL樹就要簡單得多了,比如,上面這顆樹,再插入一個元素K,它會先找到`I J`這個節點,插入元素K,形成臨時節點`I J K`,不符合2-3樹的規則,所以分裂,`J`往上移,`F H`這個節點變成了`F H J`了,也不符合2-3樹的規則,繼續上移`H`,根節點變為`D H`,同時,上移的過程中,子節點也要相應的分裂,過程大致如下: ![9](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221034443-1207439204.jpg) > 畫圖辛苦了,關注一波:彤哥讀原始碼。 可以看到,上面自平衡的過程中,出現了一種節點,它具有四個子節點和三個資料元素,這個節點可以稱作4節點,如果把4節點當作是可以允許存在的,那麼,就出現了另一種樹:2-3-4樹。 ## 2-3-4樹 **2-3-4樹**,它的每個非葉子節點,要麼是2節點,要麼是3節點,要麼是4節點,且可以自平衡,所以稱作2-3-4樹。 2節點、3節點、4節點的定義在上面已經提及,我們再重申一下: 2節點:包含兩個子節點和一個數據元素; 3節點:包含三個子節點和兩個資料元素; 4節點:包含四個子節點和三個資料元素; ![10](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221035073-1204612399.jpg) 當然,2-3-4樹插入元素的過程也很好理解,比如,上面這顆樹,插入元素M,找到`K L`這個節點,插入即可,形成4節點,滿足規則,不需要自平衡: ![11](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221035628-1851432460.jpg) 再插入元素N呢?過程與2-3樹一樣,向上分裂即可,此時,中間節點有兩個,取任意一個上移都是可以的,我們這裡以左中節點上移為例,大致過程如下: ![12](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221036174-795716654.jpg) 是不是挺簡單的,至少比AVL樹那種左旋右旋簡單得多。 同樣地,在2-3-4樹自平衡的過程中出現了臨時的5節點,所以,如果允許5節點的存在呢? 嗯,2-3-4-5樹由此誕生! 同樣地,還有2-3-4-5-6樹、2-3-4-5-6-7樹……子子孫孫,無窮盡也~ 所以,有人就把這一類樹歸納為一個新的名字:B樹。 ## B樹 **B樹**,表示的是一類樹,它允許一個節點可以有多於兩個子節點,同時,也是自平衡的,葉子節點的高度都是相同的。 所以,為了更好地區分一顆B樹到底屬於哪一類樹,我們給它一個新的屬性:度(Degree)。 具有度為3的B樹,表示一個節點最多有三個子節點,也就是2-3樹的定義。 具有度為4的B樹,表示一個節點最多有四個子節點,也就是2-3-4樹的定義。 ![13](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221036809-216076303.jpg) B樹,一個節點可以儲存多個元素,有利於快取磁碟資料,整體的時間複雜度趨向於O(log n),原理也比較簡單,所以,經常用於資料庫的索引,包括早期的mysql也是使用B樹來作為索引的。 但是,B樹有個大缺陷,比如,我要按範圍查詢元素,以上面的2-3-4樹為例,查詢大於B且小於K的所有元素,該怎麼實現呢? 很難,幾乎無解,所以,後面又出現替代B樹的方案:B+樹。 當然了,B+樹不是本節的重點,本節的重點是紅黑樹。 納尼,紅黑樹在哪裡?寫了3000多字了,還沒見到紅黑樹的影子,我尬了~ 來了來了,有意思的紅黑樹來了~~ ## 紅黑樹 先上一張圖,請仔細體會: ![14](https://img2020.cnblogs.com/blog/1648938/202009/1648938-20200917221037398-1979647049.jpg) 看明白了沒有?紅黑樹是啥?紅黑樹就是2-3-4樹!!! OK,本節到此結束。 # 後記 本節,我們一起從二叉樹出發,一路經過二叉查詢樹、平衡樹、AVL樹、2-3樹、2-3-4樹、B樹,最後終於得出了紅黑樹的本質,紅黑樹的本質就是一顆2-3-4樹,換了個面板而已。 那麼,為什麼要再造一個紅黑樹呢?直接用2-3-4樹它不香麼? 我們下一節解答,同時,下一節,我們將從紅黑樹的本質出發,徹底理解紅黑樹插入、刪除、查詢、左旋、右旋的全過程,再也不用死記硬背了,還不來關注我^^ > 關注公主號“彤哥讀原始碼”,解鎖更多原始碼、基礎、架構