1. 程式人生 > >紅黑樹並沒有我們想象的那麼難(上)

紅黑樹並沒有我們想象的那麼難(上)

紅黑樹並沒有我們想象的那麼難 上、下兩篇已經完成, 希望能幫助到大家.

紅黑樹並沒有想象的那麼難, 初學者覺得晦澀難讀可能是因為情況太多. 紅黑樹的情況可以通過歸結, 通過合併來得到更少的情況, 如此可以加深對紅黑樹的理解. 網路上的大部分紅黑樹的講解因為沒有「合併」. 紅黑樹的五個性質:

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

性質2. 根是黑色。

性質3. 所有葉子都是黑色(葉子是NIL節點)。

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

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

紅黑樹的資料結構

摘自 sgi stl 紅黑樹資料結構定義:

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;
};

二叉搜尋樹的插入刪除操作

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

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

在紅黑樹刪除操作講解中, 我們假設代替 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 的原始碼.

搗亂 2013-9-25

     <!-- 作者和時間 -->

相關推薦

沒有我們想象那麼(下)

// sgi stl _Rb_tree 插入演算法 insert_equal() 實現. // 策略概述: insert_equal() 在紅黑樹找到自己的位置, // 然後交由 _M_insert() 來處理接下來的工作. // _M_insert() 會將節點插入紅黑樹中, 接著調整紅黑樹, // 維持性

沒有我們想象那麼()

紅黑樹並沒有我們想象的那麼難 上、下兩篇已經完成, 希望能幫助到大家. 紅黑樹並沒有想象的那麼難, 初學者覺得晦澀難讀可能是因為情況太多. 紅黑樹的情況可以通過歸結, 通過合併來得到更少的情況, 如此可以加深對紅黑樹的理解. 網路上的大部分紅黑樹的講解因為沒有「合併」. 紅黑樹的五個

增刪操作的程式碼實現(

這幾天一直在學習紅黑樹,由於是第一次接觸,所以剛開始覺得挺麻煩的,經過這幾天的各種google,終於對其的插入和刪除操作有了一定了解,現在就分享下: 紅黑樹的定義:紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色為紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑

,並非想象中的那麼複雜

紅黑樹是非常popular的一種self-adjusted的平衡二叉排序樹。通常他給我們的印象是很複雜,有很多case,要小心的旋轉。有人說,曾將在某公司的面試時,被要求實現紅黑樹。他覺得這很沒有道理,幾乎很少有人能在不參考教科書的情況下,記清楚那麼多的case。在這一章裡,

「碼農讀書」:我們沒有自己想象那麼理性

作為碼農/程式設計師,我們經常認為自己是非常理性的。其實不僅是我們自己這麼認為,甚至我們身邊的朋友們在談到對我們的評價時,在聊到對程式設計師這個職業的印象時,都會普遍的認為這是一個非常理性、思維邏輯非常清晰的群體。但事實真的是這樣嗎? 我曾經也非常自信於自己的理性。但最近看了一本大塊頭書《思考,快與慢

真的沒你想的那麼

寫本文的原由是昨晚做夢居然夢到了在看原始碼,於是便有了此文...... 雖然標題是關於紅黑樹的,不過此文是結合圖片,通過分析TreeMap的原始碼,讓你理解起來不是那麼枯燥(前方高能,此文圖片眾多,慎入)。 作者 | 馬雲飛 責編 | 胡巍巍

Java轉python機器學習,沒有大家想象那麼美好!

輾轉幾年Java開發,換了幾份工作,沒一個穩定的學習、工作過程。中間也相親幾次,都是沒啥結果。換工作頻繁也嚴重打亂了和姑娘接觸的節奏。糟心工作連著遇到幾次,也怪自己眼光有問題。 2018也找了2次工作,中間有4、5個月沒有工作。看了個世界盃,看了個亞運會。也怪自己這段時間一直是換工作、找工作,節

真的沒你想的那麼

概述 TreeMap是紅黑樹的java實現,紅黑樹能保證增、刪、查等基本操作的時間複雜度為O(lgN)。 首先我們來看一張TreeMap的繼承體系圖: 還是比較直觀的,這裡來簡單說一下繼承體系中不常見的介面NavigableMap和SortedMa

死磕Java:J.U.C之ConcurrentHashMap轉換分析

作者:chessy來源:Java技術驛站在【死磕Java併發】-----J.U.C之Java併發

數據結構(5) 第五天 快速排序、歸排序、堆排序、高級數據結構介紹:平衡二叉、B/B+

平衡二叉樹 let b+樹 堆排 mark 9.png 思想 incr 相等 01 上次課程回顧 希爾排序 又叫減少增量排序 increasement = increasement / 3 + 1 02 快速排序思想 思想: 分治法 + 挖坑

遠端辦公,也許沒有想象那麼輕鬆

春節假期結束已經一個多星期了,隨著疫情的逐漸控制,很多企業陸陸續續開啟了復工模式,但為了安全考慮,有一部分企業仍然採取延遲復工的策略,比如大部分的網際網路公司,像我們熟悉的阿里、騰訊就規定復工時間延遲到2月17日,而企業的日常工作就通過遠端辦公的方式展開,藉著這次東風,很多人體驗到了遠端辦公的魅力之處,更有甚

【bzoj3227】

發現 blog ret amp 這樣的 spa 兩個 include log 神TM的紅黑樹,其實本質上應該還是一種樹dp的問題…… 一開始想了一個比較裸的樹dp,後來發現還有更強的做法。 每個前端黑節點是看作一個物品,然後這就是很典型的樹形dp的問題。 不過可以這麽考慮,

圖解集合7:概念、的插入及旋轉操作詳細解讀

集合 得到 2個 排序。 數據流 except boolean 修正 split 原文地址http://www.cnblogs.com/xrq730/p/6867924.html,轉載請註明出處,謝謝! 初識TreeMap 之前的文章講解了兩種Map,分別是HashMa

C++實現

con colors end ase 復制代碼 設置 typename ucc 技術 1 /* 2 * rbtree.h 3 * 1. 每個節點是紅色或者黑色 4 * 2. 根節點是黑色 5 * 3. 每個葉子節點是黑色(該葉子節點就空的節點)

數據結構學習筆記-排序/隊/棧/鏈/堆/查找/

算法 數據結構排序:插入排序:每次從剩余數據中選取一個最小的,插入已經排序完成的序列中合並排序:將數據分成左右兩組分別排序,然後合並,對每組數據的排序遞歸處理。冒泡排序:重復交換兩個相鄰元素,從a[1]開始向a[0]方向冒泡,然後a[2]...當a[i]無法繼續往前擠的時候說明前面的更小了,而且越往前越小(擠

查找(一)史最簡單清晰的解說

ont 演示 detail align article 向上 節點 動態插入 列表 查找(一) 我們使用符號表這個詞來描寫敘述一張抽象的表格。我們會將信息(值)存儲在當中,然後依照指定的鍵來搜索並獲取這些信息。鍵和值的詳細意義取決於不同的應用。 符號表中可能會保

教你透徹了解

black ade 我們 工作 key 針對 otn strong lean 教你透徹了解紅黑樹 作者:July、saturnman 2010年12月29日 本文參考:Google、算法導論、STL源碼剖析、計算機程序設計藝術。 推薦閱讀: Left-

else if 滿足 編碼 使用 由於 imap ica 困難 十分 轉自:http://www.cnblogs.com/yangecnu/p/Introduce-2-3-Search-Tree.html 前面一篇文章介紹了2-3查找樹,可以看到,2-3查找樹能保證在插

與AVL

target 相同 spa search htm 解決 evel 所有應用 二叉搜索樹 概述:本文從排序二叉樹作為引子,講解了紅黑樹,最後把紅黑樹和AVL樹做了一個比較全面的對比。 1 排序二叉樹 排序二叉樹是一種特殊結構的二叉樹,可以非常方便地對樹中所有節點進行排

------ luogu P3369 【模板】普通平衡(Treap/SBT)

div child lin main false tchar clas char als 二次聯通門 : luogu P3369 【模板】普通平衡樹(Treap/SBT) 近幾天閑來無事。。。就把各種平衡樹都寫了一下。。。 下面是紅黑樹(Red Black Tree)