算法導論讀書筆記(13)

算法導論讀書筆記(13)

紅黑樹

紅黑樹 是一種二叉查找樹,但在每一個結點上增長了一個存儲位表示結點的顏色,能夠是 REDBLACK 。經過對任何一條從根到葉子的路徑上的各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其餘路徑長出兩倍,於是是接近平衡的。紅黑樹(red-black tree)是許多「平衡的」查找樹中的一種,它能保證在最壞狀況下,基本的動態集合操做的時間爲 O ( lg n )。html

樹中每一個結點包含五個域: colorkeyleftrightp 。若是某結點沒有一個子結點或父結點,則該結點相應的指針爲 NIL 。咱們將這些 NIL 視爲指向二叉查找樹外結點(葉子)的指針,而把帶關鍵字的結點視爲樹的內結點。算法

一棵紅黑樹須要知足下面的 紅黑性質測試

  1. 每一個結點或是紅的,或是黑的。
  2. 根結點是黑的。
  3. 每一個葉結點( NIL )是黑的。
  4. 若是一個結點是紅的,則它的兩個孩子都是黑的。
  5. 對每一個結點,從該結點到其子孫結點的全部路徑上包含相同數目的黑結點。

下圖給出了一棵紅黑樹的例子。3d

爲了便於處理紅黑樹代碼中的邊界條件,咱們採用一個哨兵來表明 NIL 。對一棵紅黑樹來講,哨兵 T.nil 是一個與樹內普通結點有相同域的對象。它的 color 域爲 BLACK ,而其餘域能夠設爲任意容許的值。以下圖所示,全部指向 NIL 的指針都被替換成指向哨兵 T.nil 的指針。指針

使用哨兵後,能夠將結點 xNIL 孩子視爲一個其父結點爲 x 的普通結點。這裏咱們用一個哨兵 T.nil 來表明全部的 NIL (全部的葉子以及根部的父結點)。code

一般咱們將注意力放在紅黑樹的內部結點上,由於它們存儲了關鍵字的值。所以本文其他部分都將忽略紅黑樹的葉子,以下圖所示。htm

從某個結點 x 出發(不包括該結點)到達一個葉結點的任意一條路徑上,黑色結點的個數稱爲該結點 x黑高度 ,用bh( x )表示。紅黑樹的黑高度定義爲其根結點的黑高度。對象

引理
一棵有 n 個內結點的紅黑樹的高度至多爲2lg( n + 1 )。
blog

旋轉

當在含 n 個關鍵字的紅黑樹上運行時,查找樹操做 TREE-INSERTTREE-DELETE 的時間爲 O ( lg n )。因爲這兩個操做對樹作了修改,結果可能違反了紅黑樹的性質,爲保持紅黑樹的性質,就要改變樹中某些結點的顏色和指針結構。 get

指針結構的修改是經過 旋轉 來完成的,這是一種能保持二叉查找樹性質的查找樹局部操做。下圖給出了兩種旋轉:左旋和右旋。


當在某個結點 x 上作左旋時,咱們假設它的右孩子 y 不是 T.nilx 能夠爲樹內任意右孩子不是 T.nil 的結點。左旋以 xy 之間的鏈爲「支軸」進行。它使 y 稱爲該子樹新的根, x 成爲 y 的左孩子,而 y 的左孩子則成爲 x 的右孩子。

LEFT-ROTATE 的僞碼中,假設 x.right != T.nil ,而且根的父結點是 T.nil

LEFT-ROTATE(T, x)
1  y = x.right            // set y
2  x.right = y.left       // turn y's left subtree into s's right subtree
3  if y.left != T.nil
4      y.left.p = x
5  y.p = x.p
6  if x.p == T.nil
7      T.root = y
8  elseif x == x.p.left
9      x.p.left = y
10 else
11     x.p.right = y
12 y.left = x             // put x on y's left
13 x.p = y

下圖顯示了 LEFT-ROTATE 的操做過程。 RIGHT-ROTATE 的程序是對稱的。它們都在 O ( 1 )時間內完成。

RIGHT-ROTATE(T, x)
1  y = x.left
2  x.left = y.right
3  if y.right != T.nil
4      y.right.p = x
5  y.p = x.p
6  if x.p == T.nil
7      T.root = y
8  elseif x == x.p.left
9      x.p.left = y
10 else
11     x.p.right = right
12 y.right = x
13 x.p = y

插入

向一棵含 n 個結點的紅黑樹 T 中插入一個新結點 z 的操做可在 O ( lg n )時間內完成。首先將結點 z 插入樹 T 中,就好像 T 是一棵普通的二叉查找樹同樣,而後將 z 着爲紅色。爲保證紅黑性質,這裏要調用一個輔助程序 RB-INSERT-FIXUP 來對結點從新着色並旋轉。調用 RB-INSERT 會將 z 插入紅黑樹 T 內,假設 zkey 域已經事先被賦值。

RB-INSERT(T, z)
1  y = T.nil
2  x = T.root
3  while x != T.nil
4      y = x
5      if z.key < x.key
6          x = x.left
7      else
8          x = x.right
9  z.p = y
10 if y == T.nil
11     T.root = z
12 elseif z.key < y.key
13     y.left = z
14 else
15     y.right = z
16 z.left = T.nil
17 z.right = T.nil
18 z.color = RED
19 RB-INSERT-FIXUP(T, z)

過程 RB-INSERT 的運行時間爲 O ( lg n )。過程 TREE-INSERTRB-INSERT 之間有四處不一樣。首先,在 TREE-INSERT 內的全部的 NIL 都被 T.nil 代替。其次,在 RB-INSERT 的第16,17行中,設置 z.leftz.rightT.nil ,來保持正確的樹結構。第三,在第18行將 z 着爲紅色。第四,在最後一行,調用 RB-INSERT-FIXUP 來保持紅黑性質。

RB-INSERT-FIXUP(T, z)
1  while z.p.color == RED
2      if z.p == z.p.p.left                              // z的父結點是其父結點的左孩子
3          y = z.p.p.right                               // 令y爲z的叔父結點
4          if y.color == RED
5              z.p.color = BLACK                         // case 1
6              y.color = BLACK                           // case 1
7              z.p.p.color = RED                         // case 1
8              z = z.p.p                                 // case 1
9          else
10             if z == z.p.right
11                 z = z.p                               // case 2
12                 LEFT-ROTATE(T, z)                     // case 2
13             z.p.color = BLACK                         // case 3
14             z.p.p.color = RED                         // case 3
15             RIGHT-ROTATE(T, z.p.p)                    // case 3
16     else                                              // z的父結點是其父結點的右孩子
17         y = z.p.p.left                                // 令y爲z的叔父結點
18         if y.color = RED
19             z.p.color = BLACK
20             y.color = BLACK
21             z.p.p.color = RED
22             z = z.p.p
23         else
24             if z = z.p.left
25                 z = z.p
26                 RIGHT-ROTATE(T, z)
27             z.p.color = BLACK
28             z.p.p.color = RED
29             LEFT-ROTATE(T, z.p.p)
30 T.root.color = BLACK

下圖顯示了在一棵紅黑樹上 RB-INSERT-FIXUP 是如何操做的。

要理解 RB-INSERT-FIXUP 的工做過程,須要分三個主要步驟來分析其代碼。首先,肯定當結點 z 被插入並着色爲紅色後,紅黑性質有哪些不能保持。其次,分析 while 循環的總目標。最後,具體分析 while 循環中的三種狀況。

在調用 RB-INSERT-FIXUP 時,紅黑性質中的性質1和性質3會繼續成立,由於新插入結點的子女都是哨兵 T.nil 。性質5也會成立,由於結點 z 代替了(黑色)哨兵,且結點 z 自己是具備哨兵子女的紅色結點。所以,可能被破壞的就是性質2和性質4。這是由於 z 被着爲紅色,若是 z 是根結點則破壞了性質2,若是 z 的父結點是紅色則破壞了性質4。上圖a顯示在結點 z 被插入後性質4被破壞。

要保持樹的紅黑性質,實際上一共要考慮六種狀況,但其中三種與另外三種是對稱的,區別在於 z 的父結點 z.pz 的祖父結點 z.p.p 的左孩子仍是右孩子。這裏只討論 z.p 是左孩子的狀況。

上面僞碼中狀況1和狀況2,3的區別在於 z 的叔父結點的顏色有所不一樣。若是 y 是紅色,則執行狀況1。不然,控制轉移到狀況2和狀況3上。在全部三種狀況中, z 的祖父 z.p.p 都是黑色的,由於它的父結點 z.p 是紅色的,故性質4只在 zz.p 之間被破壞了。

狀況1 : z 的叔父結點 y 是紅色的

下圖顯示的是狀況1(第5~8行)的情況。只有在 z.py 都是紅色的時候才執行。既然 z.p.p 是黑色的,咱們能夠將 z.py 都着爲黑色以解決 zz.p 都是紅色的問題,將 z.p.p 着爲紅色以保持性質5。而後把 z.p.p 看成新增的結點 z 來重複 while 循環。指針 z 在樹中上移兩層。

狀況2 : z 的叔父結點 y 是黑色的,並且 z 是右孩子

狀況3 : z 的叔父結點 y 是黑色的,並且 z 是左孩子

在狀況2和狀況3中, z 的叔父結點 y 是黑色的。這兩種狀況經過 zz.p 的左孩子仍是右孩子來區別。在狀況2中,結點 z 是其父結點的右孩子。咱們馬上使用一個左旋來將此情況轉變爲狀況3,此時結點 z 成爲左孩子。由於 zz.p 都是紅色的,因此所作的旋轉對結點的黑高度和性質5都無影響。至此, z 的叔父結點 y 老是黑色的,另外 z.p.p 存在且其身份保持不變。在狀況3中,要改變某些結點的顏色,並做一次右旋以保持性質5。這樣,因爲在一行中再也不有兩個連續的紅色結點,全部的處理到此結束。

刪除

n 個結點的紅黑樹上的其它基本操做同樣,對一個結點的刪除要花 O ( lg n )時間。

首先,咱們須要自定義一個相似於 TREE-DELETE 中調用的 TRANSPLANT 的子程序。該過程接收三個參數,紅黑樹 T 以及兩棵子樹 uv 。過程用子樹 v 來替代子樹 u 在樹中的位置。

RB-TRANSPLANT(T, u, v)
1 if u.p == T.nil
2     T.root = v
3 elseif u == u.p.left
4     u.p.left = v
5 else
6     u.p.right = v
7 v.p = u.p

過程 RB-TRANSPLANTTRANSPLANT 有兩點不一樣。首先,第1行使用哨兵 T.nil 替代 NIL 。其次,第7行的賦值語句再也不須要條件。

過程 RB-DELETETREE-DELETE 相似,可是多了些代碼。有些代碼用於跟蹤記錄可能破壞紅黑性質的結點 y 的狀態。若是待刪除的結點 z 的孩子結點少於兩個,那麼能夠直接從樹中刪除 z ,並讓 y 等於 z 。若是待刪除的結點 z 有兩個孩子,令 yz 的後繼,並用 y 替代 z 在樹中的位置。咱們還要記住 y 在刪除或移動以前的顏色。因爲結點 x 也可能破壞樹的紅黑性質,咱們也須要跟蹤記錄下這個佔據告終點 y 最初位置的結點 x 的狀態。刪除結點 z 後,過程 RB-DELETE 還要調用 RB-DELETE-FIXUP 以保持紅黑性質。

RB-DELETE(T, z)
1  y = z
2  y-original-color = y.color
3  if z.left == T.nil
4      x = z.right
5      RB-TRANSPLANT(T, z, z.right)
6  elseif z.right == T.nil
7      x = z.left
8      RB-TRANSPLANT(T, z, z.left)
9  else
10     y = TREE-MINIMUM(z.right)
11     y-original-color = y.color
12     x = y.right
13     if y.p == z
14         x.p = y
15     else
16         RB-TRANSPLANT(T, y, y.right)
17         y.right = z.right
18         y.right.p = y
19     RB-TRANSPLANT(T, z, y)
20     y.left = z.left
21     y.left.p = y
22     y.color = z.color
23 if y-original-color == BLACK
24     RB-DELETE-FIXUP(T, x)

RB-DELETETREE-DELETE 主要的不一樣之處羅列以下:

  • 咱們維護了一個結點 y 。第1行令 y 指向告終點 z (此時 z 爲待刪結點且它的孩子結點少於兩個)。當 z 有兩個孩子結點時,第10行令 y 指向 z 的後繼,而後 y 會取代 z 在樹中的位置。
  • 因爲 y 的顏色可能發生變化,變量 y-original-color 保存了 y 在發生改變以前的顏色。在爲 y 賦值後,第2行和第10行馬上設置了該變量。若是 z 有兩個孩子結點,那麼 y != z 而且 y 會佔據結點 z 在紅黑樹中的初始位置;第22行將 y 的顏色設置成和 z 同樣。咱們須要保存 y 的初始顏色以便在過程 RB-DELETE 結尾處作測試;若是它是黑色的,那麼刪除或移動結點 y 就會破壞紅黑性質。
  • 咱們還要跟蹤記錄結點 x 的狀態。第4,7和12行的賦值語句令 x 指向 y 的孩子結點或哨兵 T.nil
  • 一旦結點 x 移入 y 的初始位置,屬性 x.p 老是指向 y 的父結點,哪怕 x 是哨兵 T.nil 也同樣。除非 zy 的父結點(此時 z 有兩個孩子且 y 是它的右孩子)。對 x.p 的賦值操做在過程 RB-TRANSPLANT 中第7行執行(經過觀察能夠看出來,在第5,8和16行被調用的 RB-TRANSPLANT ,其傳遞的第二個參數就是 x )。
  • 最後,若是結點 y 是黑色的,咱們可能會破壞某些紅黑性質,這就須要調用 RB-DELETE-FIXUP 來保持紅黑性質。

RB-DELETE 中,若是被刪除的結點 y 是黑色的,則會產生三個問題。首先,若是 y 原來是根結點,而 y 的某個紅色孩子成爲了新的根,這就違反了性質2。其次,若是 xx.p 都是紅色的,就違反了性質4。第三,刪除 y 可能致使其路徑上黑結點的個數少1,這就違反了性質5。補救的一個辦法就是把結點 x 視爲還有額外一重黑色。即,若是將任意包含結點 x 的路徑上黑結點個數加1,則性質5成立。當將黑結點 y 刪除時,將其黑色「下推」至其子結點。這樣問題變爲結點 x 可能既不是紅色,也不是黑色,從而違反了性質1。這時須要調用 RB-DELETE-FIXUP 來糾正。

RB-DELETE-FIXUP(T, x)
1  while x != T.root and x.color == BLACK
2      if x == x.p.left
3          w = x.p.right
4          if w.color == RED
5              w.color = BLACK                                          // case 1
6              x.p.color = RED                                          // case 1
7              LEFT-ROTATE(T, x.p)                                      // case 1
8              w = x.p.right                                            // case 1
9          if w.left.color == BLACK and w.right.color == BLACK
10             w.color = RED                                            // case 2
11             x = x.p                                                  // case 2
12         else
13             if w.right.color == BLACK
14                 w.left.color = BLACK                                 // case 3
15                 w.color = RED                                        // case 3
16                 RIGHT-ROTATE(T, w)                                   // case 3
17                 w = x.p.right                                        // case 3
18             w.color = x.p.color                                      // case 4
19             x.p.color = BLACK                                        // case 4
20             w.right.color = BLACK                                    // case 4
21             LEFT-ROTATE(T, x.p)                                      // case 4
22             x = T.root                                               // case 4
23     else (same as then clause with "right" and "left" exchanged)
24 x.color = BLACK

過程 RB-DELETE-FIXUP 能夠恢復性質1,2和4。這裏僅說明性質1。過程當中 while 循環的目標是將額外的黑色沿樹上移,直到:

  1. x 指向一個紅黑結點,此時,在第24行,將 x 着爲黑色;
  2. x 指向根,這是能夠簡單地消除額外的黑色,或者
  3. 作必要的旋轉和顏色改變。

while 循環中, x 老是指向具備雙重黑色的那個非根結點。用 w 表示 x 的兄弟。算法中的四種狀況在下圖中加以說明。首先要說明的是在每種狀況中的變換是如何保持性質5的。關鍵思想就在每種狀況下,從(其包括)子樹的根到每棵子樹之間的黑結點個數(包括 x 的額外黑色)並不被變換所改變。所以,性質5在變換以前成立,以後依然成立。

狀況1 : x 的兄弟 w 是紅色的

RB-DELETE-FIXUP 第5~8行和上圖a。由於 w 必須有紅色孩子,咱們能夠改變 wx.p 的顏色,再對 x.p 作一次左旋,並且紅黑性質得以繼續保持, x 的新兄弟是旋轉以前 w 的某個孩子,其顏色爲黑色。這樣,狀況1就轉換成狀況2,3或4。

狀況2 : x 的兄弟 w 是黑色的,且 w 的兩個孩子都是黑色的

RB-DELETE-FIXUP 第10~11行和上圖b。由於 w 和兩個孩子都是黑色的,故從 xw 上各去掉一重黑色,從而 x 只有一重黑色而 w 爲紅色。爲了補償去掉的黑色,須要在原 x.p 內新增一重額外黑色。而後新結點 x 在最後被着爲黑色。

狀況3 : x 的兄弟 w 是黑色的, w 的左孩子是紅色的,右孩子是黑色的

RB-DELETE-FIXUP 第14~17行和上圖c。此時能夠交換 w 和其左孩子 w.left 的顏色,並對 w 右旋,而紅黑性質依然保持,且從狀況3轉換成了狀況1。

狀況4 : x 的兄弟 w 是黑色的,且 w 的右孩子是紅色的

RB-DELETE-FIXUP 第18~22行和上圖d。經過作顏色的修改並對 x.p 作一次左旋,能夠去掉 x 的額外黑色並把它變成單獨黑色。將 x 置爲根後, while 會在測試其循環條件時結束。

相關文章
相關標籤/搜索