紅黑樹並無咱們想象的那麼難(上)

<紅黑樹並無咱們想象的那麼難> 上、下兩篇已經完成, 但願能幫助到你們.html

紅黑樹並無想象的那麼難, 初學者以爲晦澀難讀多是由於狀況太多. 紅黑樹的狀況能夠經過歸結, 經過合併來獲得更少的狀況, 如此能夠加深對紅黑樹的理解. 網絡上的大部分成黑樹的講解由於沒有「合併」. 紅黑樹的五個性質:node

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

性質2. 根是黑色。算法

性質3. 全部葉子都是黑色(葉子是NIL節點)。bash

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

性質5. 從任一節點到其每一個葉子的全部簡單路徑 都包含相同數目的黑色節點。數據結構

紅黑樹的數據結構

摘自 sgi stl 紅黑樹數據結構定義:svg

typedef bool _Rb_tree_Color_type;
const _Rb_tree_Color_type _S_rb_tree_red = false;
const _Rb_tree_Color_type _S_rb_tree_black = true;

struct _Rb_tree_node_base
{
  typedef _Rb_tree_Color_type _Color_type;
  typedef _Rb_tree_node_base* _Base_ptr;

  _Color_type _M_color;
  _Base_ptr _M_parent;
  _Base_ptr _M_left;
  _Base_ptr _M_right;

  static _Base_ptr _S_minimum(_Base_ptr __x)
  {
    while (__x->_M_left != 0) __x = __x->_M_left;
    return __x;
  }

  static _Base_ptr _S_maximum(_Base_ptr __x)
  {
    while (__x->_M_right != 0) __x = __x->_M_right;
    return __x;
  }
};

template <class _Value>
struct _Rb_tree_node : public _Rb_tree_node_base
{
  typedef _Rb_tree_node<_Value>* _Link_type;
  _Value _M_value_field;
};

二叉搜索樹的插入刪除操做

在展開紅黑樹以前, 首先來看看普通二叉搜索樹的插入和刪除. 插入很容易理解, 比當前值大就往右走, 比當前值小就往左走. 詳細展開的是刪除操做.lua

二叉樹的刪除操做有一個技巧, 即在查找到須要刪除的節點 X; 接着咱們找到要麼在它的左子樹中的最大元素節點 M、要麼在它的右子樹中的最小元素節點 M, 並交換(M,X). 此時, M 節點必然至多隻有一個孩子; 最後一個步驟就是用 M 的子節點代替 M 節點就完成了. 因此, 全部的刪除操做最後都會歸結爲刪除一個至多隻有一個孩子的節點, 而咱們刪除這個節點後, 用它的孩子替換就行了. 將會看到 sgi stl map 就是這樣的策略.spa

在紅黑樹刪除操做講解中, 咱們假設代替 M 的節點是 N(下面的講述再也不出現 M).

紅黑樹的插入

插入新節點老是紅色節點, 由於不會破壞性質 5, 儘量維持全部性質.

假設, 新插入的節點爲 N, N 節點的父節點爲 P, P 的兄弟(N 的叔父)節點爲 U, P 的父親(N 的爺爺)節點爲 G. 因此有以下的印象圖:

rbtree0

插入節點的關鍵是:

  1. 插入新節點老是紅色節點
  2. 若是插入節點的父節點是黑色, 能維持性質
  3. 若是插入節點的父節點是紅色, 破壞了性質. 故插入算法就是經過從新着色或旋轉, 來維持性質

插入算法詳解以下, 走一遍紅黑樹維持其性質的過程:

第 0.0 種狀況, N 爲根節點, 直接 N->黑. over
第 0.1 種狀況, N 的父節點爲黑色, 這不違反紅黑樹的五種性質. over

第 1 種狀況, N,P,U 都紅(G 確定黑). 策略: G->紅, N,P->黑. 此時, G 紅, 若是 G 的父親也是紅, 性質又被破壞了, HACK: 能夠將 GPUN 當作一個新的紅色 N 節點, 如此遞歸調整下去; 特俗的, 若是碰巧將根節點染成了紅色, 能夠在算法的最後強制 root->黑.

rbtre01

第 2 種狀況, P 爲紅, N 爲 P 右孩子, N 爲紅, U 爲黑或缺乏. 策略: 旋轉變換, 從而進入下一種狀況:

rbtree02

第 3 種狀況, 可能由第二種變化而來, 但不是必定: P 爲紅, N 爲 P 左孩子, N 爲紅. 策略: 旋轉, 交換 P,G 顏色, 調整後, 由於 P 爲黑色, 因此不怕 P 的父節點是紅色的狀況. over

rbtree03

紅黑樹的插入就爲上面的三種狀況. 你能夠作鏡像變換從而獲得其餘的狀況.

紅黑樹的刪除

假設 N 節點見上面普通二叉樹刪除中的定義, P 爲 N 父節點, S 爲 N 的兄弟節點, SL,SR 分別是 S 的左右子節點. 有以下印象圖:

rbtree04 N 沒有任何的孩子!

刪除節點的關鍵是:

  1. 若是刪除的是紅色節點, 不破壞性質
  2. 若是刪除的是黑色節點, 那麼這個路徑上就會少一個黑色節點, 破壞了性質. 故刪除算法就是經過從新着色或旋轉, 來維持性質

刪除算法詳解以下, 走一遍紅黑樹維持其性質的過程:

第 0.0 狀況, N 爲根節點. over
第 0.1 狀況, 刪除的節點爲紅. over
第 0.2 狀況, 刪除節點爲黑, N 爲紅. 策略: N->黑, 從新平衡. over

第 1 狀況, N,P,S,SR,SL 都黑. 策略: S->紅. 經過 PN,PS 的黑色節點數量相同了, 但會比其餘路徑多一個, 解決的方法是在 P 上從狀況 0 開始繼續調整. 爲何要這樣呢? HANKS: 由於既然 PN,PS 路徑上的黑節點數量相同並且比其餘路徑會少一個黑節點, 那何不將其總體當作了一個 N 節點! 這是遞歸原理.

rbtree05

第 2 狀況, S 紅, 根據紅黑樹性質 P,SL,SR 必定黑. 策略: 旋轉, 交換 P,S 顏色. 處理後關注的範圍縮小, 下面的狀況對應下面的框圖, 算法從框圖從新開始, 進入下一個狀況:

rbtree06

第 2.1 狀況, S,SL,SR 都黑. 策略: P->黑. S->紅, 由於經過 N 的路徑多了一個黑節點, 經過 S 的黑節點個數不變, 因此維持了性質 5. over. 將看到, sgi stl map 源代碼中將第 2.1 和第 1 狀況合併成一種狀況, 下節展開.

rbtree07

第 2.2.1 狀況, S,SR 黑, SL 紅. 策略: 旋轉, 變換 SL,S 顏色. 從而又進入下一種狀況:

rbtree08
第 2.2.2 狀況, S 黑, SR 紅. 策略: 旋轉, 交換 S,P 顏色, SR->黑色, 從新得到平衡.

rbtree09

上面狀況標號 X.X.X 並非說這些關係是嵌套的, 只是這樣展開容易理解. 此時, 解釋三個地方:

  1. 經過 N 的黑色節點數量多了一個
  2. 經過 SL 的黑色節點數量不變
  3. 經過 SR 的黑色節點數量不變

紅黑樹刪除從新調整僞代碼以下:

// 第 0.0 狀況, N 爲根節點. over
if N.parent == NULL:
    return;

// 第 0.1 狀況, 刪除的節點爲紅. over
if color == RED:
    return;

// 第 0.2 狀況, 刪除節點爲黑, N 爲紅, 簡單變換: N->黑, 從新平衡. over
if color == BLACK && N.color == RED:
    N.color = BLACK;

// 第 1 種狀況, N,P,S,SR,SL 都黑. 策略: S->紅. 經過 N,S 的黑色節點數量相同了, 但會比其餘路徑多一個, 解決的方法是在 P 上從狀況 0 開始繼續調整.
if N,P,S,SR,SL.color == BLACK:
    S.color = RED;

    // 調整節點關係
    N = P
    N.parent = P.parent
    S = P.paernt.another_child
    SL = S.left_child
    SR = S.right_child
    continue;

// 第 2 狀況, S 紅, 根據紅黑樹性質 P,SR,SL 必定黑. 旋轉, 交換 P,S 顏色. 此時關注的範圍縮小, 下面的狀況對應下面的框圖, 算法從框圖從新開始.
if S.color == RED:
    rotate(P);
    swap(P.color,S.color);

    // 調整節點關係
    S = P.another_child
    SL = S.left_child
    SR = S.right_child

// 第 2.1 狀況, S,SL,SR 都黑. 策略: P->黑. S->紅, 由於經過 N 的路徑多了一個黑節點, 經過 S 的黑節點個數不變, 因此維持了性質 5. over. 將看到, sgi stl map 源代碼中將第 2.1 和第 1 狀況合併成一種狀況, 下節展開.
if S,SL,SR.color == BLACK:
    P.color = BLACK;
    S.color = RED;
    return

// 第 2.2.1 狀況, S,SR 黑, SL 紅. 策略: 旋轉, 變換 SL,S 顏色. 從而又進入下一種狀況:
if  S,SR.color == BLACK && SL.color == RED:
    rotate(P);
    swap(S.color,SL.color);

    // 調整節點關係
    S = SL
    SL = S.left_child
    SR = S.right_child

// 第 2.2.2 狀況, S 黑, SR 紅. 策略: 旋轉, 交換 S,P 顏色.
if S.color == BLACK && SR.color == RED:
    rotate(P);
    swap(P.color,S.color);
    return;

總結

因此, 插入的狀況只有三種, 刪除的狀況只有兩種. 上面的分析, 作鏡像處理, 就能獲得插入刪除的所有算法, 腦補吧. 從上面的分析來看, 紅黑樹具備如下特性: 插入刪除操做都是 0(lnN), 且最多旋轉三次.

下節中會重點展開 sgi stl map 的源代碼.

參考文檔: wikipedia

搗亂 2013-9-25

http://daoluan.net

相關文章
相關標籤/搜索