從2-3樹理解紅黑樹

提及紅黑樹就頭痛,在大學時就沒搞懂,看的暈暈乎乎,理解不了。直到前幾天在極客時間的《數據結構與算法之美》專欄中的《26 | 紅黑樹(下):掌握這些技巧,你也能夠實現一個紅黑樹 》,再次看到講解紅黑樹插入刪除如何保持平衡,很惋惜,仍是沒看明白。但在留言區看到小夥伴推薦的紅黑樹是2-3樹的變形,以2-3樹的角度去理解紅黑樹就容易多了。因而,就跑去看了2-3樹相關的文章,發現理解起來是要簡單些。算法

2-3樹定義

通常咱們接觸最多的是二叉樹,也就是一個父節點最多有兩個子節點。2-3樹的意思就是說,一個父節點能夠有兩個子節點,也能夠有三個子節點,而且其也知足相似二叉搜索樹的定義(父節點的值大於左子樹,但小於右子樹),全部葉子節點都在同一層。bash

2節點:父節點存儲一個值,最多有左右兩個子樹。假設父節點爲p,子節點爲l(左節點)、r(有節點),且知足:數據結構

l < p < r
複製代碼

如圖所示: spa

1545379775127.png

3節點:父節點存儲兩個值,最多有左中右三個子樹。假設父節點分別爲p1,p2,子節點分別爲l(左節點)、m(中間節點)、r(右節點),且知足:3d

l < p1
p1 < m < p2
r > p2
複製代碼

如圖所示: code

QQ20181221-2.png

2-3樹的查找

BST的查找相似:cdn

從根節點開始比較,若相等,則結束。若是小於根節點,則說明它應該在左邊,選定左節點進行比較;若是大於根節點,則說明在右邊,選定右節點進行比較,如不相等,則繼續循環。如到最後訪問到空節點,則說明沒找到。blog

只不過對於3節點的狀況,就須要判斷左中右子樹,原理同樣。get

下面以這顆樹舉例說明,要查找的值存在和不存在的狀況。it

QQ20181221-3.png

值存在

查找5。

  1. 對比根節點10。因爲5<10,查找左子樹。
  2. 因爲4<5<7,因此須要找中間的子樹。
  3. 節點值=5,查找結束。

值不存在

查找24。

  1. 對比根節點。因爲24>10,查找右子樹。
  2. 24>20,繼續查找右子樹。
  3. 24>22,查找右子樹。
  4. 節點爲空,查找結束,未找到。

節點分裂與合併

在將插入以前,先介紹一下節點分裂與合併。

2-3樹只能存在2節點和3節點,因爲插入的時候會引入4節點,因此咱們須要將其分裂。

節點分裂

好比單個4節點,只需將中間節點往上提,左邊值做爲其左子樹,右邊值做爲其右子樹便可。

QQ20190108-5.png

節點合併

好比有父節點的4節點,節點分裂後,需與父節點進行合併。若合併後父節點仍是4節點,則繼續分裂,直至知足定義爲止。下圖中6與3合併後,知足條件,無需再進行操做。

QQ20190108-6.png

2-3樹的插入

插入一個節點後,也要知足2-3樹的定義。咱們須要找到一個適合的位置來插入新的值,可是和二叉樹不一樣的是,它不會生成新的葉子節點來存儲,而是找到合適的葉子節點來進行合併。

可是注意,插入的原則是儘可能保持樹的高度,也就是儘可能不要增長樹的高度。由於樹的高度越小,查找效率會更高。

下面分幾種狀況來講明不一樣的處理狀況。其關鍵字是往上分裂,從下往上生長。

空樹

生成新節點,則其爲根節點。

QQ20181221-4.png

待插入節點爲2節點

若是不能直接放到空的子節點,則放到父節點中,此時成爲3節點,仍然知足定義。好比咱們在這棵樹中插入12。

QQ20190108-8.png

  1. 首先找到待插入節點9。

    QQ20190108-9.png

  2. 節點9爲2節點,可直接插入。

QQ20190108-10.png

待插入節點爲3節點

這種狀況下,稍微會複雜一些,由於涉及到分裂,且跟待插入節點的父節點有關。假定待插入節點爲p待插入節點的父節點pp

將節點強插到p中,此時p中會有三個值,咱們暫且稱之爲4節點。4節點是不知足2-3樹的定義的,所以須要將4節點中的某個節點往上抽離,與pp進行合併。這時須要考慮pp的類型了。

  1. pp爲二節點

    將分裂的節點放到pp中,則pp成爲3節點,知足定義。好比咱們在這棵樹中插入13。

QQ20190108-11.png

  1. 找到待插入葉子節點[9,12]

QQ20190108-12.png

  1. 插入13,變成4節點

QQ20190108-13.png

  1. 進行分裂,合併到父節點,插入完成
    QQ20190108-14.png
  1. pp爲三節點

    將分裂的節點放到pp中,則pp成爲了4節點,不知足定義,那麼4-節點須要提出一個值,並向上合併,這時須要從新設置新舊節點的關係。往上合併的過程就是繼續套用這幾種狀況。好的狀況是往上的過程當中遇到了2節點,且平衡,則結束;壞的狀況是一直到根節點,而且根節點是3樹,那麼只好繼續往上分裂出新的根節點,而後處理新節點與其餘節點的關係,此時樹高增長了1。

好比在這顆樹中插入18。

QQ20190108-15.png

  1. 插入18,找到葉子節點插入,成爲4節點

QQ20190108-16.png

2)向上分裂,將18插入父節點,變成4節點,需繼續分裂

QQ20190108-17.png

3)根節點成爲3節點,插入結束

QQ20190108-18.png

以上的插入,樹的高度都沒有變化。下面說一種樹的高度會+1的狀況。

好比在這棵樹中插入32。

QQ20190108-19.png

  1. 找到待插入的節點直接插入32

QQ20190108-20.png

  1. 分裂節點,此時父節點變成4節點,還需分裂

QQ20190108-21.png

  1. 此時根節點爲4節點,需分裂

QQ20190108-22.png

  1. 生成新的根節點,樹的高度加1

QQ20190108-23.png

2-3樹的刪除

刪除的狀況會複雜一些,下面分幾種狀況來講。

待刪除的值在葉子節點

葉子節點爲3節點

直接刪除便可。以下圖12可直接刪除。

QQ20190108-24.png

刪除後,3節點成2節點。

QQ20190108-25.png

葉子節點爲2節點

這裏須要區分臨近兄弟節點的類型。先將節點刪除。

  1. 兄弟節點爲3節點 此時被刪除後,節點會爲空。經過向兄弟節點借一個最近的鍵值,而後再調整該節點與父節點的值。

好比在這顆樹中刪除7。

QQ20190108-26.png

  1. 向兄弟節點借最近節點,此時大小關係不知足

QQ20190108-27.png

  1. 調整大小,8,9交換,知足定義

QQ20190108-28.png

  1. 兄弟節點爲2節點,這時須要判斷父節點類型

    a. 父節點爲3節點 此時兄弟節點不夠借,父節點降元,從3節點變成2節點,與兄弟節點合併。

好比從這棵樹中刪除36。

QQ20190108-29.png

30與18合併,3節點變成2節點,刪除完成

QQ20190108-30.png

b. 父節點爲2節點 將父節點和兄弟節點合併,造成新的節點,這是把新節點當作當前節點,不斷套用上述幾種狀況進行調整,直至平衡。這種狀況下,若根節點是2節點,樹的高度會減1。

例1 從這顆樹中刪除12

QQ20190108-31.png

  1. 兄弟節點和父節點都爲2節點,進行合併。此時新節點爲[8,9]。

QQ20190108-32.png

  1. 當前節點([8,9])的父節點和兄弟節點都爲2節點,還需進行合併(即節點2,5合併)合併完成以下圖。
    QQ20190108-34.png

例2 從這棵樹中刪除30

QQ20190108-35.png

  1. 節點30的父節點和兄弟節點爲2節點,進行合併,此時[22,25]爲新節點。

    QQ20190108-36.png

  2. 把[22,25]看做當前節點,因爲其兄弟節點爲2節點,父節點爲3節點,套用2.a中的狀況,父節點降元,調整完成。

    QQ20190108-37.png

待刪除的值在父節點

將該節點與其前驅後繼節點交換,而後刪除交換後的葉子節點,此時轉換成上一種狀況的處理。

使用中序遍歷的順序,前驅就是指其前一個節點,後繼是指其後面的一個節點。最直接的定位以下:

  • 前驅節點:該節點左子樹最右節點
  • 後繼節點:該節點右子樹最左節點

好比下圖,5的前驅是3,後繼是7。

QQ20190108-38.png

那爲何要用前驅/後繼節點交換呢?

由於,用前驅/後繼節點交換後,才能保持大小順序。後繼節點是右子樹中最小的節點,與父節點交換後,排除待刪除的葉節點,仍保持左子樹<新父節點<右子樹的關係。同理,前驅節點是左子樹中最大的節點,交換後,仍能保持。

這裏咱們使用後繼節點來進行替換。

從這顆樹中刪除節點5。

QQ20190108-39.png

因爲5的後繼節點是7,先將值進行交換。這時候目的就是刪除葉子節點5,因而能夠轉換成其父節點和兄弟節點都爲2節點的狀況進行調整。

QQ20190108-40.png

紅黑樹定義

紅黑樹也是一種二叉平衡樹,它知足以下幾個特性(根據算法中的定義):

  1. 根節點是黑色的。
  2. 紅連接均爲左連接。
  3. 從任一節點到其可達葉子節點,通過的黑色節點數量同樣(黑平衡)。
  4. 沒有任何一個節點同時與兩個紅連接相連。

這個定義可能跟咱們日常看到的不太同樣,因爲是以2-3樹來理解紅黑樹,定義紅鏈在左邊,這樣才能跟2-3樹徹底對應上。

紅黑樹與AVL

AVL是一種極度平衡的二叉樹,那爲何不用AVL呢?由於AVL插入刪除要保持平衡,相比紅黑樹要慢一些,須要左旋右旋等等。但實際上它的旋轉也只是幾個場景的套用,哪些場景須要怎麼旋轉,理解就好了。

而紅黑樹是近似平衡的(黑平衡),也就是說它不像AVL那樣絕對的平衡,因此添加/刪除節點後的平衡操做沒那麼多。

因此對於插入和刪除操做較多的場景,用紅黑樹效率會高一些。

將2-3樹轉換成紅黑樹

主要思想:3節點分裂成2節點。

將3節點的第一個元素,做爲第二個元素的左節點,並用紅色的線鏈接,此時紅色線鏈接的節點就至關於紅色。

QQ20190108-41.png

將2-3樹按照以上思想轉換後,就獲得了一顆紅黑樹。用這種方式理解是否是簡單多了呢?

同時也有幾個問題值得咱們思考:

  1. 爲何紅鏈規定在左邊呢?

    我以爲是前人的一個約定,爲了保持統一,簡化處理,都放在左邊。那都放右邊是否是也能夠呢?

  2. 沒有任何一個節點同時與兩個紅連接相連

    由於一個紅鏈表示一個3節點,若是有2個紅鏈相連,則表示爲4節點,不符合2-3樹定義。

  3. 根節點爲黑色

    只有3節點的左鏈才爲紅色。根節點沒有父節點,不可能爲紅色。

  4. 根節點到葉子節點通過的黑色節點數目相同

    由於2-3樹是完美平衡的。紅黑樹中通過的黑節點數=其層數。

紅黑樹的應用

  1. 散列表的衝突處理

    map的實現,底層通常會採用紅黑樹,在節點多的時候效率高。 在節點少的時候,可用鏈表方式。

QQ20190108-42.png

  1. 動態插入、刪除和查詢較多的場景
相關文章
相關標籤/搜索