天天看點

紅黑樹-RBT(二、基本操作之插入)

一、紅黑樹插入節點

  1、時間複雜度:向一顆含有n個節點的紅黑樹中插入一個新節點的操作可在O(lgn)時間内完成。

  2、步驟

    1)、将節點z插入樹T内,就好像T是一顆普通的二叉查找樹一樣,然後将z着為紅色。

    2)、為保證紅黑性質可以得到保持,調用一個輔助程式RB-INSERT-FIXUP

  3、RB-Insert僞代碼

1 RB-INSERT(T, z)  
 2  y ← nil[T]                        // 建立節點“y”,将y設為空節點。
 3  x ← root[T]                       // 設“紅黑樹T”的根節點為“x”
 4  while x ≠ nil[T]                  // 找出要插入的節點“z”在二叉樹T中的位置“y”
 5      do y ← x                      
 6         if key[z] < key[x]  
 7            then x ← left[x]  
 8            else x ← right[x]  
 9  p[z] ← y                          // 設定 “z的父親” 為 “y”
10  if y = nil[T]                     
11     then root[T] ← z               // 情況1:若y是空節點,則将z設為根
12     else if key[z] < key[y]        
13            then left[y] ← z       // 情況2:若“z所包含的值” < “y所包含的值”,則将z設為“y的左孩子”
14             else right[y] ← z      // 情況3:(“z所包含的值” >= “y所包含的值”)将z設為“y的右孩子” 
15 left[z] ← nil[T]                  // z的左孩子設為空
16 right[z] ← nil[T]                 // z的右孩子設為空。至此,已經完成将“節點z插入到二叉樹”中了。
17 color[z] ← RED                    // 将z着色為“紅色”
18 RB-INSERT-FIXUP(T, z)             // 通過RB-INSERT-FIXUP對紅黑樹的節點進行顔色修改以及旋轉,讓樹T仍然是一顆紅黑樹      

  4、RB-INSERT-FIXUP

1 RB-INSERT-FIXUP(T, z)
 2  while color[p[z]] = RED                                                  // 若“目前節點(z)的父節點是紅色”,則進行以下處理。
 3      do if p[z] = left[p[p[z]]]                                           // 若“z的父節點”是“z的祖父節點的左孩子”,則進行以下處理。
 4            then y ← right[p[p[z]]]                                        // 将y設定為“z的叔叔節點(z的祖父節點的右孩子)”
 5                 if color[y] = RED                                         // Case 1條件:叔叔是紅色
 6                    then color[p[z]] ← BLACK                    ▹ Case 1   //  (01) 将“父節點”設為黑色。
 7                         color[y] ← BLACK                       ▹ Case 1   //  (02) 将“叔叔節點”設為黑色。
 8                         color[p[p[z]]] ← RED                   ▹ Case 1   //  (03) 将“祖父節點”設為“紅色”。
 9                         z ← p[p[z]]                            ▹ Case 1   //  (04) 将“祖父節點”設為“目前節點”(紅色節點)
10                    else if z = right[p[z]]                                // Case 2條件:叔叔是黑色,且目前節點是右孩子
11                            then z ← p[z]                       ▹ Case 2   //  (01) 将“父節點”作為“新的目前節點”。
12                                 LEFT-ROTATE(T, z)              ▹ Case 2   //  (02) 以“新的目前節點”為支點進行左旋。
13                            color[p[z]] ← BLACK                 ▹ Case 3   // Case 3條件:叔叔是黑色,且目前節點是左孩子。(01) 将“父節點”設為“黑色”。
14                            color[p[p[z]]] ← RED                ▹ Case 3   //  (02) 将“祖父節點”設為“紅色”。
15                           RIGHT-ROTATE(T, p[p[z]])            ▹ Case 3   //  (03) 以“祖父節點”為支點進行右旋。
16         else (same as then clause with "right" and "left" exchanged)      // 若“z的父節點”是“z的祖父節點的右孩子”,将上面的操作中“right”和“left”交換位置,然後依次執行。
17  color[root[T]] ← BLACK       

  根據被插入節點的父節點的情況,可以将"當節點z被着色為紅色節點,并插入二叉樹"劃分為三種情況來處理。

  1)被插入的節點是根節點。我們隻需要直接把此節點塗為黑色。

  2)被插入的節點的父節點是黑色。什麼也不需要做。節點被插入後,仍然是紅黑樹。

  3)被插入的節點的父節點是紅色。  那麼,該情況與紅黑樹的“特性(5)”相沖突。這種情況下,被插入節點是一定存在非空祖父節點的;進一步的講,被插入節點也一定存在叔叔節點(即使叔叔節點為空,我們也視之為存在,空節點本身就是黑色節點)。了解這點之後,我們依據"叔叔節點的情況",将這種情況進一步劃分為3種情況(Case)。

現象說明 處理政策
Case 1 目前節點的父節點是紅色,且目前節點的祖父節點的另一個子節點(叔叔節點)也是紅色。

(01) 将“父節點”設為黑色。

(02) 将“叔叔節點”設為黑色。

(03) 将“祖父節點”設為“紅色”。

(04) 将“祖父節點”設為“目前節點”(紅色節點);即,之後繼續對“目前節點”進行操作。

Case 2 目前節點的父節點是紅色,叔叔節點是黑色,且目前節點是其父節點的右孩子

(01) 将“父節點”作為“新的目前節點”。

(02) 以“新的目前節點”為支點進行左旋。

Case 3 目前節點的父節點是紅色,叔叔節點是黑色,且目前節點是其父節點的左孩子

(01) 将“父節點”設為“黑色”。

(02) 将“祖父節點”設為“紅色”。

(03) 以“祖父節點”為支點進行右旋。

  上面三種情況(Case)處理問題的核心思路都是:将紅色的節點移到根節點;然後,将根節點設為黑色。下面對它們詳細進行介紹。

    1. (Case 1)叔叔是紅色

      1.1 現象說明

        目前節點(即,被插入節點)的父節點是紅色,且目前節點的祖父節點的另一個子節點(叔叔節點)也是紅色。

      1.2 處理政策

        (01) 将“父節點”設為黑色。

        (02) 将“叔叔節點”設為黑色。

        (03) 将“祖父節點”設為“紅色”。

        (04) 将“祖父節點”設為“目前節點”(紅色節點);即,之後繼續對“目前節點”進行操作。

       下面談談為什麼要這樣處理。(建議了解的時候,通過下面的圖進行對比)

          “目前節點”和“父節點”都是紅色,違背“特性(4)”。是以,将“父節點”設定“黑色”以解決這個問題。

          但是,将“父節點”由“紅色”變成“黑色”之後,違背了“特性(5)”:因為,包含“父節點”的分支的黑色節點的總數增加了1。  解決這個問題的辦法是:将“祖父節點”由“黑色”變成紅色,同時,将“叔叔節點”由“紅色”變成“黑色”。關于這裡,說明幾點:第一,為什麼“祖父節點”之前是黑色?這個應該很容易想明白,因為在變換操作之前,該樹是紅黑樹,“父節點”是紅色,那麼“祖父節點”一定是黑色。 第二,為什麼将“祖父節點”由“黑色”變成紅色,同時,将“叔叔節點”由“紅色”變成“黑色”;能解決“包含‘父節點’的分支的黑色節點的總數增加了1”的問題。這個道理也很簡單。“包含‘父節點’的分支的黑色節點的總數增加了1” 同時也意味着 “包含‘祖父節點’的分支的黑色節點的總數增加了1”,既然這樣,我們通過将“祖父節點”由“黑色”變成“紅色”以解決“包含‘祖父節點’的分支的黑色節點的總數增加了1”的問題; 但是,這樣處理之後又會引起另一個問題“包含‘叔叔’節點的分支的黑色節點的總數減少了1”,現在我們已知“叔叔節點”是“紅色”,将“叔叔節點”設為“黑色”就能解決這個問題。 是以,将“祖父節點”由“黑色”變成紅色,同時,将“叔叔節點”由“紅色”變成“黑色”;就解決了該問題。

          按照上面的步驟處理之後:目前節點、父節點、叔叔節點之間都不會違背紅黑樹特性,但祖父節點卻不一定。若此時,祖父節點是根節點,直接将祖父節點設為“黑色”,那就完全解決這個問題了;若祖父節點不是根節點,那我們需要将“祖父節點”設為“新的目前節點”,接着對“新的目前節點”進行分析。

      1.3 示意圖

紅黑樹-RBT(二、基本操作之插入)

    2. (Case 2)叔叔是黑色,且目前節點是右孩子

      2.1 現象說明

        目前節點(即,被插入節點)的父節點是紅色,叔叔節點是黑色,且目前節點是其父節點的右孩子

      2.2 處理政策

        (01) 将“父節點”作為“新的目前節點”。

        (02) 以“新的目前節點”為支點進行左旋。

          下面談談為什麼要這樣處理。(建議了解的時候,通過下面的圖進行對比)

            首先,将“父節點”作為“新的目前節點”;接着,以“新的目前節點”為支點進行左旋。 為了便于了解,我們先說明第(02)步,再說明第(01)步;為了便于說明,我們設定“父節點”的代号為F(Father),“目前節點”的代号為S(Son)。

為什麼要“以F為支點進行左旋”呢?根據已知條件可知:S是F的右孩子。而之前我們說過,我們處理紅黑樹的核心思想:将紅色的節點移到根節點;然後,将根節點設為黑色。既然是“将紅色的節點移到根節點”,那就是說要不斷的将破壞紅黑樹特性的紅色節點上移(即向根方向移動)。 而S又是一個右孩子,是以,我們可以通過“左旋”來将S上移!   

            按照上面的步驟(以F為支點進行左旋)處理之後:若S變成了根節點,那麼直接将其設為“黑色”,就完全解決問題了;若S不是根節點,那我們需要執行步驟(01),即“将F設為‘新的目前節點’”。那為什麼不繼續以S為新的目前節點繼續處理,而需要以F為新的目前節點來進行處理呢?這是因為“左旋”之後,F變成了S的“子節點”,即S變成了F的父節點;而我們處理問題的時候,需要從下至上(由葉到根)方向進行處理;也就是說,必須先解決“孩子”的問題,再解決“父親”的問題;是以,我們執行步驟(01):将“父節點”作為“新的目前節點”。

      2.2 示意圖

紅黑樹-RBT(二、基本操作之插入)

    3. (Case 3)叔叔是黑色,且目前節點是左孩子

      3.1 現象說明

        目前節點(即,被插入節點)的父節點是紅色,叔叔節點是黑色,且目前節點是其父節點的左孩子

      3.2 處理政策

        (01) 将“父節點”設為“黑色”。

        (02) 将“祖父節點”設為“紅色”。

        (03) 以“祖父節點”為支點進行右旋。

          下面談談為什麼要這樣處理。(建議了解的時候,通過下面的圖進行對比)

            為了便于說明,我們設定“目前節點”為S(Original Son),“兄弟節點”為B(Brother),“叔叔節點”為U(Uncle),“父節點”為F(Father),祖父節點為G(Grand-Father)。

            S和F都是紅色,違背了紅黑樹的“特性(4)”,我們可以将F由“紅色”變為“黑色”,就解決了“違背‘特性(4)’”的問題;但卻引起了其它問題:違背特性(5),因為将F由紅色改為黑色之後,所有經過F的分支的黑色節點的個數增加了1。那我們如何解決“所有經過F的分支的黑色節點的個數增加了1”的問題呢? 我們可以通過“将G由黑色變成紅色”,同時“以G為支點進行右旋”來解決。

      2.3 示意圖

紅黑樹-RBT(二、基本操作之插入)

提示:上面的進行Case 3處理之後,再将節點"120"當作目前節點,就變成了Case 2的情況。