天天看點

紅黑樹

紅黑樹

引子

前幾天看java TreeMap源碼,發現TreeMap是有序的map集合,是以很感興趣他是怎麼實作的,原來是用了紅黑樹來實作。

相關知識介紹

二叉排序樹:中序周遊即得有序序列(大的放右邊,小的放左邊)

Avl樹/平衡二叉樹:左右子樹高度差不超過一的二叉排序樹

紅黑樹:不太嚴格的平衡二叉樹(做了限制)

紅黑樹介紹

紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種資料結構,典型的用途是實作關聯數組。

它是在1972年由Rudolf Bayer發明的,當時被稱為平衡二叉B樹(symmetric binary B-trees)。後來,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改為如今的“紅黑樹”。

紅黑樹和AVL樹類似,都是在進行插入和删除操作時通過特定操作保持二叉查找樹的平衡,進而獲得較高的查找性能。

它雖然是複雜的,但它的最壞情況運作時間也是非常良好的,并且在實踐中是高效的: 它可以在O(log n)時間内做查找,插入和删除,這裡的n 是樹中元素的數目。

紅黑樹限制

紅黑樹是每個節點都帶有顔色屬性的二叉查找樹,顔色或紅色或黑色。在二叉查找樹強制一般要求以外,對于任何有效的紅黑樹我們增加了如下的額外要求:

性質1. 節點是紅色或黑色。

性質2. 根節點是黑色。

性質3 每個葉節點(NIL節點,空節點)是黑色的。

性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)

性質5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。(從根到葉子的最長的可能路徑不多于最短的可能路徑的兩倍長)

紅黑樹

性質三意思是NIL節點是黑色的,比如203節點其實有兩個NIL黑色子節點

紅黑樹左旋右旋

左旋

了解為,對X左旋,X的右孩子當爸爸,X變成原來右孩子的左節點

算法導論:

LEFT-ROTATE(T, x)       01  y ← right[x]            // 前提:這裡假設x的右孩子為y。下面開始正式操作     02  right[x] ← left[y]      // 将 “y的左孩子” 設為 “x的右孩子”,即 将β設為x的右孩子     03  p[left[y]] ← x          // 将 “x” 設為 “y的左孩子的父親”,即 将β的父親設為x     04  p[y] ← p[x]             // 将 “x的父親” 設為 “y的父親”     05  if p[x] = nil[T]            06  then root[T] ← y                 // 情況1:如果 “x的父親” 是空節點,則将y設為根節點     07  else if x = left[p[x]]       08            then left[p[x]] ← y    // 情況2:如果 x是它父節點的左孩子,則将y設為“x的父節點的左孩子”     09            else right[p[x]] ← y   // 情況3:(x是它父節點的右孩子) 将y設為“x的父節點的右孩子”     10  left[y] ← x             // 将 “x” 設為 “y的左孩子”     11  p[x] ← y                // 将 “x的父節點” 設為 “y”      

右旋

紅黑樹

了解為:對Y右旋,Y的左孩子當爸爸,Y變成原來左孩子的右節點

RIGHT-ROTATE(T, y)       01  x ← left[y]             // 前提:這裡假設y的左孩子為x。下面開始正式操作     02  left[y] ← right[x]      // 将 “x的右孩子” 設為 “y的左孩子”,即 将β設為y的左孩子     03  p[right[x]] ← y         // 将 “y” 設為 “x的右孩子的父親”,即 将β的父親設為y     04  p[x] ← p[y]             // 将 “y的父親” 設為 “x的父親”     05  if p[y] = nil[T]            06  then root[T] ← x                 // 情況1:如果 “y的父親” 是空節點,則将x設為根節點     07  else if y = right[p[y]]       08            then right[p[y]] ← x   // 情況2:如果 y是它父節點的右孩子,則将x設為“y的父節點的左孩子”     09            else left[p[y]] ← x    // 情況3:(y是它父節點的左孩子) 将x設為“y的父節點的左孩子”     10  right[x] ← y            // 将 “y” 設為 “x的右孩子”     11  p[y] ← x                // 将 “y的父節點” 設為 “x”      
紅黑樹

紅黑樹插入

三個步驟:

1:将紅黑樹當成普通的二叉排序樹,插入新節點

2:将新節點渲染為紅色

為什麼不渲染成黑色,假設渲染成黑色,那麼就違背了性質5,渲染成紅色可能違背性質4

3:insert fixup()

RB-INSERT(T, z)       01  y ← nil[T]                        // 建立節點“y”,将y設為空節點。     02  x ← root[T]                       // 設“紅黑樹T”的根節點為“x”     03  while x ≠ nil[T]                  // 找出要插入的節點“z”在二叉樹T中的位置“y”     04      do y ← x                           05         if key[z] < key[x]       06            then x ← left[x]       07            else x ← right[x]       08  p[z] ← y                          // 設定 “z的父親” 為 “y”     09  if y = nil[T]                          10     then root[T] ← z               // 情況1:若y是空節點,則将z設為根     11     else if key[z] < key[y]             12             then left[y] ← z       // 情況2:若“z所包含的值” < “y所包含的值”,則将z設為“y的左孩子”     13             else right[y] ← z      // 情況3:(“z所包含的值” >= “y所包含的值”)将z設為“y的右孩子”      14  left[z] ← nil[T]                  // z的左孩子設為空     15  right[z] ← nil[T]                 // z的右孩子設為空。至此,已經完成将“節點z插入到二叉樹”中了。     16  color[z] ← RED                    // 将z着色為“紅色”     17  RB-INSERT-FIXUP(T, z)             // 通過RB-INSERT-FIXUP對紅黑樹的節點進行顔色修改以及旋轉,讓樹T仍然是一顆紅黑樹      
RB-INSERT-FIXUP(T, z)     01 while color[p[z]] = RED                                                  // 若“目前節點(z)的父節點是紅色”,則進行以下處理。     02     do if p[z] = left[p[p[z]]]                                           // 若“z的父節點”是“z的祖父節點的左孩子”,則進行以下處理。     03           then y ← right[p[p[z]]]                                        // 将y設定為“z的叔叔節點(z的祖父節點的右孩子)”     04                if color[y] = RED                                         // Case 1條件:叔叔是紅色     05                   then color[p[z]] ← BLACK                    ▹ Case 1   //  (01) 将“父節點”設為黑色。     06                        color[y] ← BLACK                       ▹ Case 1   //  (02) 将“叔叔節點”設為黑色。     07                        color[p[p[z]]] ← RED                   ▹ Case 1   //  (03) 将“祖父節點”設為“紅色”。     08                        z ← p[p[z]]                            ▹ Case 1   //  (04) 将“祖父節點”設為“目前節點”(紅色節點)     09                   else if z = right[p[z]]                                // Case 2條件:叔叔是黑色,且目前節點是右孩子     10                           then z ← p[z]                       ▹ Case 2   //  (01) 将“父節點”作為“新的目前節點”。     11                                LEFT-ROTATE(T, z)              ▹ Case 2   //  (02) 以“新的目前節點”為支點進行左旋。     12                           color[p[z]] ← BLACK                 ▹ Case 3   // Case 3條件:叔叔是黑色,且目前節點是左孩子。(01) 将“父節點”設為“黑色”。     13                           color[p[p[z]]] ← RED                ▹ Case 3   //  (02) 将“祖父節點”設為“紅色”。     14                           RIGHT-ROTATE(T, p[p[z]])            ▹ Case 3   //  (03) 以“祖父節點”為支點進行右旋。     15        else (same as then clause with "right" and "left" exchanged)      // 若“z的父節點”是“z的祖父節點的右孩子”,将上面的操作中“right”和“left”交換位置,然後依次執行。     16 color[root[T]] ← BLACK      

我了解的insert fixup僞碼:

While(目前節點z的父節點是紅色的話){     If(z的父節點是z祖父節點的左孩子){     Case1(父紅,叔紅)         父黑,叔黑,祖父紅,祖父設為目前節點。     Break;     Case2(父紅,叔黑,目前節點是右孩子)         父節點設為目前節點,以目前節點為支點左旋     Break;     Case3(父紅,叔黑,目前節點是左孩子)         父黑,祖父紅,以祖父為支點右旋。     Break;     }else{     Case4(父紅,叔紅)         父黑,叔黑,祖父紅,祖父設為目前節點。     Break;     Case5(父紅,叔黑,目前節點是左孩子)         父節點設為目前節點,以目前節點為支點右旋     Break;     Case6(父紅,叔黑,目前節點是右孩子)         父黑,祖父紅,以祖父為支點左旋。     Break;     }}color[root[T]] ← BLACK      

例子:

紅黑樹
紅黑樹

上圖的情況就屬于case6

新插入的節點是668,左圖是未fixup,有圖經過fixup之後

668的父節點是668祖父節點的右孩子,進入else,668的父親是紅色,叔父是NIL也就是黑色,且目前節點668是右節點,是以把父親設為黑色,祖父設為紅色,以祖父為支點左旋。得到右圖。現在目前節點是668,不滿足while,跳出循環。Fixup完畢.下面再加入531

紅黑樹

上圖的情況就屬于case1

新插入的節點是531,左圖是未fixup,有圖經過fixup之後

531的父節點是531祖父節點的左孩子,進入if,531的父親是紅色,叔父是紅色,是以把父親和叔父改為黑色,祖父改為紅色,祖父設為目前節點。此時目前節點沒有父親,跳出while,把根節點606設為黑色。Fixup完畢。下面加入496

紅黑樹
紅黑樹

上圖的情況就屬于case5

新插入的節點是496,左圖是未fixup,有圖經過fixup之後

496的父節點是496祖父節點的右孩子,進入else,496父親是紅色,叔父是黑色,且目前節點496是左孩子,是以父親設為目前節點,以目前節點為支點右旋。得到右圖,得到右圖之後直接進入case6.如下面所示

紅黑樹
紅黑樹

現在目前節點是531,531的父節點是531的祖父節點的右孩子,進入else,531的父親是紅色,叔父是黑色,并且目前節點531是右孩子,是以進入case6,父親496設為黑色,祖父489設為紅色,以祖父節點為支點左旋得到右圖。現在目前節點是531,不滿足while,跳出循環。Fixup完畢。

其他情況自己試試吧,http://sandbox.runjs.cn/show/2nngvn8w

紅黑樹删除

參考文獻

算法導論

js紅黑樹

skywang12345

上一篇: 紅黑樹

繼續閱讀