從紅黑樹的本質出發,徹底理解紅黑樹!

彤哥讀原始碼發表於2020-10-12

前言

早上好,我是彤哥。

上一節,我們一起從二叉樹、二叉查詢樹、平衡樹、AVL樹、2-3樹、2-3-4樹、B樹,一路講到紅黑樹,最後得出紅黑樹的本質:紅黑樹就是2-3-4樹,請看下圖:

14

我們知道2-3-4的插入、刪除、查詢元素的原理是相當簡單的,那麼,我們是不是可以利用2-3-4樹來記憶紅黑樹呢?

答案是肯定的,本節,我們就來看看如何利用2-3-4樹來快速掌握紅黑樹,再也不用死記硬背了~~

好了,讓我們進入今天的學習吧。

再憶2-3-4樹

我們給出一張圖簡單地回顧一下上一節關於2-3-4樹插入元素N的過程:

12

關注公主號彤哥讀原始碼,檢視上一節的內容。

左傾紅黑樹、右傾紅黑樹、AA樹

在正式講解紅黑樹之前呢,彤哥先來給大家普及幾個有意思的概念,分別是左傾紅黑樹、右傾紅黑樹、AA樹。

圖片太小?試試橫屏!

1

請看上圖,其實按照紅黑樹的概念,上面3顆樹都是紅黑樹,而且元素也是一模一樣,可以說是同一顆紅黑樹的不同變種。

細心的同學會發現①和②是同一顆2-3-4樹演化而來,③是這顆2-3-4樹縮小成2-3樹的樣子。

那麼,到底什麼是紅黑樹呢?

紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色或紅色或黑色。

首先,紅黑樹是一顆二叉查詢樹,另外,它還必須滿足以下五點要求:

  1. 節點是紅色或黑色;
  2. 根節點是黑色;
  3. 所有葉子節點是黑色;(葉子節點是NULL節點)
  4. 每個紅色節點的兩個子節點都是黑色;(從根節點到每個葉子節點的路徑上不能有兩個連續的紅節點)
  5. 從任何一個節點到每個葉子節點的所有路徑都包含相同數目的黑色節點;

大家不用記這個概念哈,因為確實很難記得住哈,下面彤哥會教大家更簡單的方法。

所以,你看上面三個圖是不是都是紅黑樹呢?

並不是啊,因為葉子節點有的是紅色的呀。

其實,它們都是紅黑樹,讓我把葉子節點補齊:

2

你再仔細看看,是不是滿足上面五條規則了?!

所以,你看,隨便畫一顆樹,它都可能滿足紅黑樹的定義,因此,為了方便記憶,我們將紅黑樹分成這麼幾種型別:左傾紅黑樹、右傾紅黑樹、AA樹。

左傾紅黑樹(LLRB,Left-Learning Red-Black Tree),一個節點如果有紅色子節點,那麼,它的紅色子節點是向左傾斜的。

怎麼理解呢?

我們還是把上面的null節點幹掉哈,葉子節點都是null節點,那是經典紅黑樹的講法,到彤哥這裡,完全不存在這種要求。

我們來看,一個節點要麼有一個子節點,要麼有兩個子節點,對吧。

如果這個節點有紅色的子節點呢,也是一個或者兩個,如果只有一個紅色子節點的話,那麼,這個子節點只能在左邊,如果是有兩個紅色子節點,那就不用管。

所以,整顆紅黑樹中,如果存在紅色節點,那麼只能是下面這兩種形態:

3

同理,右傾紅黑樹(RLRB,Right-Learning Red-Black Tree),也是一樣的道理,即紅色子節點向右傾斜,它的紅色子節點只能是下面這兩種形態:

4

好了,左傾和右傾紅黑樹都還算比較正常的形態,還有一種變態的紅黑樹,叫作AA樹(AA Tree)

當然,這裡的AA不是吃飯的時候大家各付各的哈,這裡的AA是其作者的名字的縮寫:Arne Andersson。

AA樹,是指紅黑樹中所有的紅色子節點必須只能是右節點,左子節點一律不允許是紅色子節點,所以,在AA樹中,紅色子節點只能是下面這一種形態:

5

也可以理解為嚴重右傾主義(我這麼說會不會被約去喝茶^^)。

其實AA樹可以看作2-3樹的右傾演化而來,而不是2-3-4樹,你可以畫個圖體驗一下。

好了,上面就是左傾紅黑樹、右傾紅黑樹、AA樹的概念,當然,也有可能存在一種紅黑樹,比如紅色子節點只能是左子節點,是不是叫BB樹,我們也不知道,還有一種可能是像下面這種紅黑樹:

6

上面這顆樹,它既有左邊的紅色子節點,也有右邊的紅色子節點,其實它也滿足紅黑樹的定義,這種就只是普通(經典)的紅黑樹了。

既然紅黑樹有這麼多完全不同的形態,我們要如何快速的記住它們呢?

很難,真的很難,所以,我們只需要記住一種形態就可以了,比如左傾紅黑樹,其它的形態都是一樣的道理,完全不用強形記憶。

因此,下面的內容,我將全部以左傾紅黑樹來講解,跟經典的紅黑樹講法會有點出入,且跟你以前看到的所有文章都不一樣,請不要糾結。

有趣的插入元素

首先,讓我們約定一件事:插入的節點必須為紅色,但如果是根節點,就把它塗成黑色。

有了這個約定之後,我們使用一步一圖的方式來慢慢拆解紅黑樹(左傾,下同)插入元素的過程。

  1. 插入第一個元素F

    第一個元素肯定是根節點,直接塗成黑色:

    7

  2. 插入第二個元素

    這裡分兩種情況:比F小,比F大。

    (1)假設插入的元素為D,那麼,它比F小,所以會成為F的左子節點:

    8

    此時,D為紅色左子節點,所以,不需要再平衡。

    (2)假設插入的元素為K,那麼,它比F大,所以會成為F的右子節點:

    9

    此時,K為紅色右子節點,不符合左傾紅黑樹的規則,所以,需要再平衡,那麼,要如何再平衡呢?

    讓我們迴歸紅黑樹的本質——2-3-4樹,上面包含F和K兩個元素的紅黑樹換成2-3-4樹就變成了:

    10

    再把這個2-3-4樹轉換成左傾紅黑樹就變成了:

    11

    讓我們畫一張對比圖來看看:

    12

    所以,你看,結合2-3-4樹來理解紅黑樹是不是就特別簡單了,對於2-3-4樹就是一個普通的3節點,而對於紅黑樹相當於插入一個右子節點,再做一次左旋變色即可。

  3. 插入第三個元素

    我們以上述的F K兩個元素的紅黑樹為例,在這個基礎上再增加一個元素,這裡可能有三種情況,我們一一來分析:

    (1)假設插入的元素為D,它比F小,所以會成為F的左子節點:

    13

    此時,顯然不符合紅黑樹的定義了,所以,需要再平衡,那麼如何平衡呢?來,上圖:

    14

    插入元素D,對於2-3-4樹就是形成一個4節點,而對於紅黑樹樹需要經過右旋再變色的過程。

    (2)假設插入的元素為G,它比F大,比K小,所以會成為F的右子節點:

    15

    顯然,它也不符合紅黑樹的定義,所以,也需要再平衡:

    16

    插入元素G,對於2-3-4樹,只是形成一個普通的4節點,而對於紅黑樹,需要先以F左旋,變成與情況(1)相同的狀態,再以G右旋,然後變色,最終再平衡成紅黑樹。

    (3)假設插入的元素為N,它比K大,所以會成為K的右子節點:

    17

    此時,正好符合紅黑樹的定義,不需要再平衡了,但是,我們同樣畫一張圖對比看下:

    18

    好了,通過上面的分析,連續插入三個元素,可以看到,對於2-3-4,都是形成一個4節點,而對於紅黑樹,最終都變成了下面這個樣子:

    19

    所以,我們再插入第四個元素看看。

  4. 插入第四個元素

    我們以F K N這顆紅黑樹為例,插入第四個元素,可能會出現四種情況,也就是分別可能會成為F和N的四個子節點的其中之一,簡單點,我們直接上圖:

    (1)假設為D,其為F的左子節點

    20

    (2)假設為G,其為F的右子節點

    21

    (3)假設為M,其為N的左子節點

    22

    (4)假設為Q,其為N的右子節點

    23

    好了,插入四個元素的各種情況到此結束,可以看到,插入第四個元素時,對於2-3-4樹,會形成一個5節點,然後再分裂,而對於紅黑樹,要經過一系列的左旋、右旋、變色,最終轉變成跟2-3-4樹對應的形態,是不是很好玩兒^^

  5. 插入第五個元素

    畫圖太累,交給你了~~

刪除元素

不管是2-3-4樹還是左傾紅黑樹刪除元素的過程都要比插入元素複雜得多,我們先來看2-3-4樹刪除元素的過程。

2-3-4樹刪除元素

為了方便講解,我構造了一顆下圖所示的2-3-4樹:

24

對於2-3-4樹,刪除3節點或4節點的葉子節點是最簡單的,比如C DP Q R這兩個葉子節點,刪除這兩個節點中的任意一個元素直接刪除即可,4節點刪除一個元素後變成3節點,3節點刪除一個元素之後變成2節點,並不影響原來樹的平衡性,比如,刪除C之後的結果如下:

25

但是,刪除2節點就不一樣了,比如,上圖刪除ABFGHJLN這幾個節點,直接刪除之後樹就不平衡了,所以,需要想一些辦法來保證刪除L之後樹依然是平衡的,怎麼辦呢?

答案是——偷!

沒錯,就是偷,從別的地方偷元素過來,把這個空缺補上,就像我們上班划水一樣,總要找一些東西把工時補上對不對。

那麼,怎麼個偷法呢?

總體來說,分成兩大類,子節點從父節點偷,父節點從子節點偷,偷著偷著可能還要合併或者遷移元素。

我們來分別看一下刪除ABFGHJLN這幾個節點的過程是如何偷的,以下多圖,請慎重!

(1)刪除A

26

刪除A元素時,先從父節點偷個B過來,此時,B位置空缺了,原來B的位置再從其右子節點偷個C過來,搞定。

(2)刪除B

27

刪除B就很簡單了,直接從右子節點偷個C過來就搞定了。

(3)刪除F

28

刪除F的過程就比較複雜了,總之,始終圍繞著一個原則:子節點偷不到就偷父節點的,偷過來的元素之後記得可能會合並或者遷移元素。

合併的規則是要始終保證整顆樹的有序性,比如,上面從父節點偷了個I過來,它本身就比H大,所以,H必須放在I的左子節點,而左子節點原來已經有G了,所以,只能把它們倆合併了。

同理,遷移J元素的過程也是一樣的,J肯定是要放在K的左邊,遷移到I的右子節點正好。

(4)刪除G

其實跟刪除F時從偷I開始是一樣的,就不贅述了。

(5)刪除H

與刪除F的過程一模一樣,不再贅述。

(6)刪除J

29

刪除J時,從父節點先偷個K過來,此時父節點變成了3節點,所以,直接把M左邊的兩個元素合併即可。

(7)刪除L

30

刪除L的過程與刪除J的過程有點像,也是從父節點偷K過來,然後再把M左邊的兩個元素合併。

(8)刪除N

31

刪除N時,從父節點偷個O過來,父節點再從其右子節點偷個P過來,偷個屁,偷個屁呀~~

好了,到此為止,2-3-4樹刪除元素的過程全解析完畢了,我這個示例中幾乎包含了所有的場景,請多畫圖仔細體會,雖然畫得想吐血了。

左傾紅黑樹刪除元素

注:紅黑樹的刪除稍微有點小複雜,如果強型跟2-3-4掛鉤會變得更復雜,所以,下面的內容不完全跟2-3-4樹掛鉤。

首先,我想問一個問題:一顆二叉查詢樹刪除元素之後如何還能保證它還是二叉查詢樹呢?

32

如果是葉子節點,刪了也就刪了,不影響,但如果是非葉子節點呢?比如,刪除M這個元素。

其實,有兩種方法:一種是找到M的前置節點並拿到M的位置,一種是找到M的後繼節點並拿到M的位置。

什麼是前置節點?什麼是後繼節點呢?好像二叉樹裡面只聽說過父節點、子節點?

我們知道二叉查詢樹本質上是有序的,這個有序性指的是元素的自然順序(還有一種有序性是插入順序)。

所以,你把這顆二叉樹中的所有元素排個序(或者中序遍歷一下),在M前面的那個節點就是前置節點,在M後面的那個節點就是後繼節點。

還有一種更形象的方法,M這個節點左子樹中最大的元素就是M的前置節點,M節點右子樹中最小的元素就是M的後繼節點。

所以,刪除M後,把L或者N移到M的位置就可以了,此時,就能保證二叉查詢樹依然是二叉查詢樹。

33

34

不過,大家好像都喜歡移後繼節點,即右子樹中最小的節點你如果看原始碼的話,會看到一個單詞叫作successor,就是後繼節點的意思。

好了,關於二叉查詢樹刪除元素我們就講這麼多,還是回到紅黑樹刪除元素的過程。

為了方便講解,我構造了下面這麼一顆紅黑樹:

35

我們先來看一種最簡單的情況,如果刪除的是紅色的葉子節點,比如,上圖中的C、P、R這三個元素,如果它的父節點只有它這麼一個子節點,直接刪之,啥也不用管,比如C,如果它的父節點有兩個子節點,那麼會分成兩種情況,一種是刪除的右子節點,則直接刪,比如R,另一種是刪除的左子節點,那就做一次簡單的左旋即可,比如P。

我們這裡講的是左傾紅黑樹,如果是經典的紅黑樹,則刪除紅色葉子節點不需要旋轉。

OK,我們再來看第二種情況,如果刪除的是黑色的葉子節點呢?

我們知道,黑色節點刪除之後,肯定不符合紅黑樹定義了,所以,肯定要進行再平衡的過程。

如果按照經典紅黑樹的說法,要看它的兄弟節點的顏色,有可能還要看它兄弟節點的子節點的顏色,情況大概有三四種,根本不可能記得住,我這裡介紹一種更牛逼的方法,保證你看一遍就能記住。

我們以刪除F節點為例,我先給出圖示,下面再描述詳細步驟:

36

這種方法非常簡單,F是黑色節點沒錯,那就想辦法把它變成紅色節點,怎麼變呢?

那就得從它的上層節點動手,上層節點的紅色其實是可以向下傳遞的,傳遞之後,整顆樹其實還是紅黑樹,並不會打破原來紅黑樹的平衡,直到F變成紅色的葉子節點,再一舉把它刪除,就很簡單了。

這種方法相比於經典紅黑樹的方法,理解起來就容易得多了。

我們再舉個刪除L的例子,直接上圖:

37

好了,上面說的都是刪除葉子節點,那麼,如果刪除的是非葉子節點呢,比如刪除E。

38

根據二叉查詢樹的特性,那麼,我們會找到E的後繼節點F,然後,把它移到E的位置,但是,此時,不符合紅黑樹的定義了,所以,你可以發現,其實,刪除E相當於間接地刪除F原來所在的節點位置,因此,又轉化成了上面的刪除葉子節點。

過程很簡單,最後的結果與刪除F的結果基本相同,只是原來E所在位置的元素變成了F,我就不畫圖了。

你可以想想刪除M的過程~

總算講完了,能看到這裡的同學不容易,可能已經超出了一頓早餐的時間,我很抱歉!

後記

本節,我們從紅黑樹的本質,即2-3-4樹出發,徹底掌握了一種不用死記硬背的方法來理解紅黑樹,你Get到了嗎?歡迎留言評論。

有些同學看到這裡,可能又說了:Talk is cheap, show me the code!

好,下一節,我就show you the code,敬請期待!

關注公主號“彤哥讀原始碼”,解鎖更多原始碼、基礎、架構知識。

相關文章