<紅黑樹並無咱們想象的那麼難> 上、下兩篇已經完成, 但願能幫助到你們.html
根據上一節的紅黑樹分析, 結合 sgi stl map 的實現, 看看紅黑樹的源碼是如何實現的. 如下主要以代碼的註釋爲主. sgi stl map 底層實現是 _Rb_tree類, 爲了方便管理, _Rb_tree 內置了 _M_header, 用於記錄紅黑樹中的根節點, 最小節點和最大節點. 在插入刪除中都會對其進行維護. 找到一副美豔的圖片: node
我只會展開插入和刪除的代碼. _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