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

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

SGI STL map 實現概述

根據上一節的紅黑樹分析, 結合 sgi stl map 的實現, 看看紅黑樹的源碼是如何實現的. 如下主要以代碼的註釋爲主. sgi stl map 底層實現是 _Rb_tree類, 爲了方便管理, _Rb_tree 內置了 _M_header, 用於記錄紅黑樹中的根節點, 最小節點和最大節點. 在插入刪除中都會對其進行維護. 找到一副美豔的圖片: rbtree_headernode

我只會展開插入和刪除的代碼. _Rb_tree 有 insert_unique() 和 insert_equal() 兩種, 前者不容許有重複值, 後者能夠. insert_unique() 判斷是否有重複值的方法利用了二叉搜索樹的性質. 細節請參看下面的代碼.linux

爲何選擇紅黑樹做爲底層實現

紅黑樹是一種類平衡樹, 但它不是高度的平衡樹, 但平衡的效果已經很好了. 補充說明另外一種 AVL 樹, 我以前的博文: 《編程珠璣,字字珠璣》讀書筆記完結篇——AVL樹算法

用過 STL map 麼, 你用過 linux 麼(這個說大了), 他們都有紅黑樹的應用. 當你對搜索的效率要求較高,而且數據常常改動的情景,你能夠用紅黑樹, 也就是 map.編程

至於, 爲何不用 AVL 樹做爲底層實現, 那是由於 AVL 樹是高度平衡的樹, 而每一次對樹的修改, 都要 rebalance, 這裏的開銷會比紅黑樹大. 紅黑樹插入只要兩次旋轉, 刪除至多三次旋轉. 但不能否認的是, AVL 樹搜索的效率是很是穩定的. 選取紅黑樹, 我認爲是一種折中的方案.函數

紅黑樹源代碼剖析

// sgi stl _Rb_tree 插入算法 insert_equal() 實現.
// 策略概述: insert_equal() 在紅黑樹找到本身的位置,
// 而後交由 _M_insert() 來處理接下來的工做.
// _M_insert() 會將節點插入紅黑樹中, 接着調整紅黑樹,
// 維持性質.
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::insert_equal(const _Value& __v)
{
  // 在紅黑樹中有頭結點和根節點的概念, 頭結點位於根節點之上,
  // 頭結點只爲管理而存在, 根節點是真正存儲數據的地方. 頭結點和根節點互爲父節點,
   // 是一種實現的技巧.
  _Link_type __y = _M_header; // 指向頭結點
  _Link_type __x = _M_root(); // _M_header->_M_parent, 即指向根節點

  // 尋找插入的位置
  while (__x != 0) {
    __y = __x;

    // 小於當前節點要走左邊, 大於等於當前節點走右邊
    __x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ?
            _S_left(__x) : _S_right(__x);
  }
  // __x 爲須要插入的節點的位置, __y 爲其父節點
  return _M_insert(__x, __y, __v);
}

// sgi stl _Rb_tree 插入算法 insert_unique() 實現.
// 策略概述: insert_unique() 一樣也在紅黑樹中找到本身的位置; 咱們知道,
// 若是小於等於當前節點會往右走, 因此遇到一個相同鍵值的節點後, 會往右走一步,
// 接下來一直往左走, 因此下面的實現會對往左走的狀況作特殊的處理.
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
pair<typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator,
     bool>
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::insert_unique(const _Value& __v)
{
  _Link_type __y = _M_header; // 指向頭結點
  _Link_type __x = _M_root(); // 指向根節點, 可能爲空
  bool __comp = true;

  // 尋找插入的位置
  while (__x != 0) {
    __y = __x;
    __comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x));

    // 小於當前節點要走左邊, 大於等於當前節點走右邊
    __x = __comp ? _S_left(__x) : _S_right(__x);
  }

  iterator __j = iterator(__y); // 在 __y 上創建迭代器

  // 我認爲下面判斷樹中是否有存在鍵值的狀況有點繞,
  // 它充分利用了二叉搜索樹的性質, 如此作很 hack, 但不易理解.
  // 要特別注意往左邊插入的狀況.

  // HACKS:
  // 下面的 if 語句是比 __x 小走左邊的狀況: 會發現, 若是插入一個已存在的鍵的話,
  // __y 最終會定位到已存在鍵的右子樹的最左子樹.
  // 譬如, 紅黑樹中已經存在一個鍵爲 100 的節點, 其右孩子節點爲 101,
  // 此時若是再插入鍵爲 100 的節點, 由於 100<=100, 因此會往右走到達 101 節點,
  // 有 100<101, 繼而往左走, 會一直往左走.你們稍微畫一個例子就能理解.
  if (__comp)
    // 特殊狀況, 若是 __j 指向了最左孩子, 那麼確定要插入新節點.
    if (__j == begin())
      return pair<iterator,bool>(_M_insert(__x, __y, __v), true);
    // 其餘狀況, 這個時候也是往左邊插入, 若是存在重複的鍵值,
    // 那麼 --__j 能定位到此重複的鍵的節點.
    else
      --__j;

  // HACKS: 這裏比較的是 __j 和 __v, 若是存在鍵值, 那麼 __j == __v,
  // 會跳過 if 語句. 不然執行插入. 也就是說若是存在重複的鍵, 那麼 __j
  // 的值確定是等於 __v
  if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v)))
    return pair<iterator,bool>(_M_insert(__x, __y, __v), true);

  // 此時 __y.value = __v, 不容許插入, 返回鍵值所在位置
  return pair<iterator,bool>(__j, false);
}

// _M_insert() 是真正執行插入的地方.
// 策略概述: 插入策略已經在上篇中詳述, 能夠根據上篇文章的描述,
// 和下面代碼的註釋, 加深對紅黑樹插入算法裏理解
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::_M_insert(_Base_ptr __x_, _Base_ptr __y_, const _Value& __v)
{
  _Link_type __x = (_Link_type) __x_; // 新節點插入的位置.
  // 關於 __x 的疑問:
  // 1. 它被放到下面的, 第一個 if 語句中, 我以爲是沒有必要的,
  // 由於從調用 _M_insert() 的函數來看, __x 老是爲空.
  // 2. 既然 __x 是新節點插入的位置, 那麼爲何不直接在 __x 上建立節點,
  // 還要在下面經過比較來決定新節點是左孩子仍是右孩子;
  // 不如直接用指針的指針或者指針的引用來完成, 省去了下面的判斷.

  _Link_type __y = (_Link_type) __y_; // 新節點的父節點
  _Link_type __z; // 新節點的位置

  if (__y == _M_header || __x != 0 ||
      _M_key_compare(_KeyOfValue()(__v), _S_key(__y))) {
  // 新節點應該爲左孩子
    __z = _M_create_node(__v);
    _S_left(__y) = __z;               // also makes _M_leftmost() = __z
                                      //    when __y == _M_header
    if (__y == _M_header) {
      _M_root() = __z;
      _M_rightmost() = __z;
    }
    else if (__y == _M_leftmost())
      _M_leftmost() = __z;   // maintain _M_leftmost() pointing to min node
  }
  // 新節點應該爲右孩子
  else {
    __z = _M_create_node(__v);
    _S_right(__y) = __z;
    if (__y == _M_rightmost())
      _M_rightmost() = __z;  // maintain _M_rightmost() pointing to max node
  }
  _S_parent(__z) = __y;
  _S_left(__z) = 0;
  _S_right(__z) = 0;

  // 從新調整
  _Rb_tree_rebalance(__z, _M_header->_M_parent);

  // 更新紅黑樹節點數
  ++_M_node_count;

  // 返回迭代器類型
  return iterator(__z);
}

// 插入新節點後, 可能會破壞紅黑樹性質, _Rb_tree_rebalance() 負責維持性質.
// 其中:
// __x 新插入的節點
// __root 根節點
// 策略概述: 紅黑樹插入從新調整的策略已經在上篇中講述,
// 能夠結合上篇文章和這裏的代碼註釋,
// 理解紅黑樹的插入算法.
inline void
_Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  // 將新插入的節點染成紅色
  __x->_M_color = _S_rb_tree_red;

  while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) {
    // __x 的父節點也是紅色的狀況. 提示: 若是是黑色節點, 不會破壞紅黑樹性質.

    if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) {
      // 叔父節點
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right;

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

        if (__x == __x->_M_parent->_M_right) {
        // 第 2 種狀況, P 爲紅, N 爲 P 右孩子, U 爲黑或缺乏.
        // 策略: 旋轉變換, 從而進入下一種狀況:
          __x = __x->_M_parent;
          _Rb_tree_rotate_left(__x, __root);
        }
        // 第 3 種狀況, 可能由第二種變化而來, 但不是必定: P 爲紅, N 爲紅.
        // 策略: 旋轉, 交換 P,G 顏色, 調整後, 由於 P 爲黑色, 因此不怕
        // P 的父節點是紅色的狀況. over
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);
      }
    }
    else { // 下面的代碼是鏡像得出的, 腦補吧.
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left;
      if (__y && __y->_M_color == _S_rb_tree_red) {
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {
        if (__x == __x->_M_parent->_M_left) {
          __x = __x->_M_parent;
          _Rb_tree_rotate_right(__x, __root);
        }
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);
      }
    }
  }
  __root->_M_color = _S_rb_tree_black;
}

// 刪除算法, 直接調用底層的刪除實現 _Rb_tree_rebalance_for_erase().
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
inline void _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::erase(iterator __position)
{
  _Link_type __y =
    (_Link_type) _Rb_tree_rebalance_for_erase(__position._M_node,
                                              _M_header->_M_parent,
                                              _M_header->_M_left,
                                              _M_header->_M_right);
  destroy_node(__y);
  --_M_node_count;
}

// 刪除節點底層實現, 刪除可能會破壞紅黑樹性質,
// _Rb_tree_rebalance()
// 負責維持性質. 其中:
// __z 須要刪除的節點
// __root 根節點
// __leftmost 紅黑樹內部數據, 即最左子樹
// __rightmost 紅黑樹內部數據, 即最右子樹
// 策略概述: _Rb_tree_rebalance_for_erase() 會根據
// 刪除節點的位置在紅黑樹中找到頂替刪除節點的節點,
// 即無非是刪除節點左子樹的最大節點或右子樹中的最小節點,
// 此處用的是有一種策略. 接着, 會調整紅黑樹以維持性質.
// 調整的算法已經在上篇文章中詳述, 能夠根據上篇文章的描述
// 和此篇的代碼註釋, 加深對紅黑樹刪除算法的理解.
inline _Rb_tree_node_base*
_Rb_tree_rebalance_for_erase(_Rb_tree_node_base* __z,
                             _Rb_tree_node_base*& __root,
                             _Rb_tree_node_base*& __leftmost,
                             _Rb_tree_node_base*& __rightmost)
{
  // __z 是要刪除的節點

  // __y 最終會指向要刪除的節點
  _Rb_tree_node_base* __y = __z;
  // N 節點
  _Rb_tree_node_base* __x = 0;
  // 記錄 N 節點的父節點
  _Rb_tree_node_base* __x_parent = 0;

  // 只有一個孩子或者沒有孩子的狀況
  if (__y->_M_left == 0)     // __z has at most one non-null child. y == z.
    __x = __y->_M_right;     // __x might be null.
  else
    if (__y->_M_right == 0)  // __z has exactly one non-null child. y == z.
      __x = __y->_M_left;    // __x is not null.

    // 有兩個非空孩子
    else {                   // __z has two non-null children.  Set __y to
      __y = __y->_M_right;   //   __z's successor.  __x might be null.

      // __y 取右孩子中的最小節點, __x 記錄他的右孩子(可能存在右孩子)
      while (__y->_M_left != 0)
        __y = __y->_M_left;
      __x = __y->_M_right;
    }

  // __y != __z 說明有兩個非空孩子的狀況,
  // 此時的刪除策略就和文中提到的普通二叉搜索樹刪除策略同樣:
  // __y 記錄了 __z 右子樹中最小的節點
  // __x 記錄了 __y 的右孩子
  // 用 __y 頂替 __z 的位置, __x 頂替 __y 的位置, 最後用 __y 指向 __z,
  // 從而 __y 指向了要刪除的節點
  if (__y != __z) {          // relink y in place of z.  y is z's successor

    // 將 __z 的記錄轉移至 __y 節點
    __z->_M_left->_M_parent = __y;
    __y->_M_left = __z->_M_left;

    // 若是 __y 不是 __z 的右孩子, __z->_M_right 有左孩子
    if (__y != __z->_M_right) {

      __x_parent = __y->_M_parent;

      // 若是 __y 有右孩子 __x, 必須有那個 __x 替換 __y 的位置
      if (__x)
        // 替換 __y 的位置
        __x->_M_parent = __y->_M_parent;

      __y->_M_parent->_M_left = __x;      // __y must be a child of _M_left
      __y->_M_right = __z->_M_right;
      __z->_M_right->_M_parent = __y;
    }
    // __y == __z->_M_right
    else
      __x_parent = __y;

    // 若是 __z 是根節點
    if (__root == __z)
      __root = __y;

    // __z 是左孩子
    else if (__z->_M_parent->_M_left == __z)
      __z->_M_parent->_M_left = __y;

    // __z 是右孩子
    else
      __z->_M_parent->_M_right = __y;

    __y->_M_parent = __z->_M_parent;
    // 交換須要刪除節點 __z 和 替換節點 __y 的顏色
    __STD::swap(__y->_M_color, __z->_M_color);
    __y = __z;
    // __y now points to node to be actually deleted
  }
  // __y != __z 說明至多一個孩子
  else {                        // __y == __z
    __x_parent = __y->_M_parent;
    if (__x) __x->_M_parent = __y->_M_parent;

    // 將 __z 的父親指向 __x
    if (__root == __z)
      __root = __x;
    else
      if (__z->_M_parent->_M_left == __z)
        __z->_M_parent->_M_left = __x;
      else
        __z->_M_parent->_M_right = __x;

    // __leftmost 和 __rightmost 是紅黑樹的內部數據, 由於 __z 多是
    // __leftmost 或者 __rightmost, 所以須要更新.
    if (__leftmost == __z)
      if (__z->_M_right == 0)        // __z->_M_left must be null also
        // __z 左右孩子都爲空, 沒有孩子
        __leftmost = __z->_M_parent;
    // makes __leftmost == _M_header if __z == __root
      else
        __leftmost = _Rb_tree_node_base::_S_minimum(__x);

    if (__rightmost == __z)
      if (__z->_M_left == 0)         // __z->_M_right must be null also
        __rightmost = __z->_M_parent;
    // makes __rightmost == _M_header if __z == __root
      else                      // __x == __z->_M_left
        __rightmost = _Rb_tree_node_base::_S_maximum(__x);

    // __y 一樣已經指向要刪除的節點
  }

  // __y 指向要刪除的節點
  // __x 即爲 N 節點
  // __x_parent 指向 __x 的父親, 即 N 節點的父親
  if (__y->_M_color != _S_rb_tree_red) {
    // __y 的顏色爲黑色的時候, 會破壞紅黑樹性質

    while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black))
      // __x 不爲紅色, 即爲空或者爲黑. 提示: 若是 __x 是紅色, 直接將 __x 替換成黑色

      if (__x == __x_parent->_M_left) { // 若是 __x 是左孩子

        _Rb_tree_node_base* __w = __x_parent->_M_right; // 兄弟節點

        if (__w->_M_color == _S_rb_tree_red) {
          //第 2 狀況, S 紅, 根據紅黑樹性質P,SL,SR 必定黑.
          // 策略: 旋轉, 交換 P,S 顏色.

          __w->_M_color = _S_rb_tree_black;
          __x_parent->_M_color = _S_rb_tree_red; // 交換顏色
          _Rb_tree_rotate_left(__x_parent, __root); // 旋轉
          __w = __x_parent->_M_right; // 調整關係
        }

        if ((__w->_M_left == 0 ||
             __w->_M_left->_M_color == _S_rb_tree_black) &&
            (__w->_M_right == 0 ||
             __w->_M_right->_M_color == _S_rb_tree_black)) {
          // 提示: 這是 第 1 狀況和第 2.1 狀況的合併, 由於處理的過程是同樣的.
          // 但他們的狀況仍是要分門別類的. 已經在文章中詳細支出,
          // 彷佛大多數的博文中沒有提到這一點.

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

          // 第 2.1 狀況, S,SL,SR 都黑.
          // 策略: P->黑. S->紅, 由於經過 N 的路徑多了一個黑節點,
          // 經過 S 的黑節點個數不變, 因此維持了性質 5. over

          // 可能你們會有疑問, 不對啊, 2.1 的狀況,
          // 策略是交換父節點和兄弟節點的顏色, 此時怎麼沒有對父節點的顏色賦值呢?
          // HACKS: 這就是合併狀況的好處, 由於就算此時父節點是紅色,
          // 並且也將兄弟節點顏色改成紅色, 你也能夠將 PS,PN 當作一個紅色的 N 節點,
          // 這樣在下一個循環當中, 這個 N 節點也會變成黑色. 由於此函數最後有一句話:
          // if (__x) __x->_M_color = _S_rb_tree_black;
          // 合併狀況, 節省代碼量

          // 固然是能夠分開寫的

          // 兄弟節點染成黑色
          __w->_M_color = _S_rb_tree_red;

          // 調整關係
          __x = __x_parent;
          __x_parent = __x_parent->_M_parent;
        } else {
          if (__w->_M_right == 0 ||
              __w->_M_right->_M_color == _S_rb_tree_black) {
            // 第 2.2.1 狀況, S,SR 黑, SL 紅.
            // 策略: 旋轉, 變換 SL,S 顏色.

            if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
            __w->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_right(__w, __root);

            // 調整關係
            __w = __x_parent->_M_right;
          }

          // 第 2.2.2 狀況, S 黑, SR 紅.
          // 策略: 旋轉, 交換 S,P 顏色, SR->黑色, 從新得到平衡.
          __w->_M_color = __x_parent->_M_color;
          __x_parent->_M_color = _S_rb_tree_black;
          if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
          _Rb_tree_rotate_left(__x_parent, __root);
          break;
        }                        // 下面的代碼是鏡像得出的, 腦補吧.
      } else {                  // same as above, with _M_right <-> _M_left.
        _Rb_tree_node_base* __w = __x_parent->_M_left;
        if (__w->_M_color == _S_rb_tree_red) {
          __w->_M_color = _S_rb_tree_black;
          __x_parent->_M_color = _S_rb_tree_red;
          _Rb_tree_rotate_right(__x_parent, __root);
          __w = __x_parent->_M_left;
        }
        if ((__w->_M_right == 0 ||
             __w->_M_right->_M_color == _S_rb_tree_black) &&
            (__w->_M_left == 0 ||
             __w->_M_left->_M_color == _S_rb_tree_black)) {
          __w->_M_color = _S_rb_tree_red;
          __x = __x_parent;
          __x_parent = __x_parent->_M_parent;
        } else {
          if (__w->_M_left == 0 ||
              __w->_M_left->_M_color == _S_rb_tree_black) {
            if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
            __w->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_left(__w, __root);
            __w = __x_parent->_M_left;
          }
          __w->_M_color = __x_parent->_M_color;
          __x_parent->_M_color = _S_rb_tree_black;
          if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
          _Rb_tree_rotate_right(__x_parent, __root);
          break;
        }
      }
    if (__x) __x->_M_color = _S_rb_tree_black;
  }
  return __y;
}

搗亂 2013-9-29lua

http://daoluan.net.net

相關文章
相關標籤/搜索