天天看點

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹

作者:July   二零一一年一月九日

-----------------------------

本文參考:

I、  The Art of Computer Programming Volume I

II、 Introduction to Algorithms, Second Edition

III、The Annotated STL Sources

IV、 Wikipedia

V、  Algorithms In C Third Edition

VI、 本人寫的關于紅黑樹的前三篇文章:

第一篇:教你透徹了解紅黑樹:

<a href="http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx" target="_blank">http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx</a>

第二篇:紅黑樹算法的層層剖析與逐漸實作

<a href="http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6109153.aspx" target="_blank">http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6109153.aspx</a>

第三篇:教你徹底實作紅黑樹:紅黑樹的c源碼實作與剖析

<a href="http://blog.csdn.net/v_JULY_v/archive/2011/01/03/6114226.aspx" target="_blank">http://blog.csdn.net/v_JULY_v/archive/2011/01/03/6114226.aspx</a>

---------------------------------------------

前言:

1、有讀者反應,說看了我的前幾篇文章,對紅黑樹的了解還是不夠透徹。

2、我個人覺得,如果我一步一步,用圖+代碼來闡述各種插入、删除情況,可能會更直覺易懂。

3、既然寫了紅黑樹,那麼我就一定要把它真正寫好,讓讀者真正徹底明白紅黑樹。

本文相對我前面紅黑樹相關的3篇文章,主要有以下幾點改進:

1.圖、文字叙述、代碼編寫,彼此對應,明朗而清晰。

2.宏觀總結,紅黑樹的性質與插入、删除情況的認識。

3.代碼來的更直接,結合圖,給你最直覺的感受,徹底明白紅黑樹。

ok,首先,以下幾點,你現在應該是要清楚明白了的:

I、紅黑樹的五個性質:

1)每個結點要麼是紅的,要麼是黑的。

2)根結點是黑的。

3)每個葉結點,即空結點(NIL)是黑的。

4)如果一個結點是紅的,那麼它的倆個兒子都是黑的。

5)對每個結點,從該結點到其子孫結點的所有路徑上包含相同數目的黑結點。

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

II、紅黑樹插入的幾種情況:

情況1,z的叔叔y是紅色的。

情況2:z的叔叔y是黑色的,且z是右孩子

情況3:z的叔叔y是黑色的,且z是左孩子

III、紅黑樹删除的幾種情況。

情況1:x的兄弟w是紅色的。

情況2:x的兄弟w是黑色的,且w的倆個孩子都是黑色的。

情況3:x的兄弟w是黑色的,且w的左孩子是紅色,w的右孩子是黑色。

情況4:x的兄弟w是黑色的,且w的右孩子是紅色的。

除此之外,還得明确一點:

IV、我們知道,紅黑樹插入、或删除結點後,

可能會違背、或破壞紅黑樹的原有的性質,

是以為了使插入、或删除結點後的樹依然維持為一棵新的紅黑樹,

那就要做倆方面的工作:

1、部分結點顔色,重新着色

2、調整部分指針的指向,即左旋、右旋。

V、并差別以下倆種操作:

1)紅黑樹插入、删除結點的操作,RB-INSERT(T, z),RB-DELETE(T, z)

2).紅黑樹已經插入、删除結點之後,

為了保持紅黑樹原有的紅黑性質而做的恢複與保持紅黑性質的操作。

如RB-INSERT-FIXUP(T, z),RB-DELETE-FIXUP(T, x)

以上這5點,我已經在我前面的2篇文章,都已闡述過不少次了,希望,你現在已經透徹明了。

---------------------------------------------------------------------

本文,着重圖解分析紅黑樹插入、删除結點後為了維持紅黑性質而做修複工作的各種情況。

[下文各種插入、删除的情況,與我的第二篇文章,紅黑樹算法的實作與剖析相對應]

ok,開始。

一、在下面的分析中,我們約定:

要插入的節點為,N

父親節點,P

祖父節點,G

叔叔節點,U

兄弟節點,S

如下圖所示,找一個節點的祖父和叔叔節點:

node grandparent(node n)     //祖父

{

     return n-&gt;parent-&gt;parent;

 }

 node uncle(node n)              //叔叔

     if (n-&gt;parent == grandparent(n)-&gt;left)

         return grandparent(n)-&gt;right;

     else

         return grandparent(n)-&gt;left;

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

二、紅黑樹插入的幾種情況

情形1: 新節點N位于樹的根上,沒有父節點

void insert_case1(node n) {

     if (n-&gt;parent == NULL)

         n-&gt;color = BLACK;

         insert_case2(n);

情形2: 新節點的父節點P是黑色

void insert_case2(node n) {

     if (n-&gt;parent-&gt;color == BLACK)

         return; /* 樹仍舊有效 */

         insert_case3(n);

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情形3:父節點P、叔叔節點U,都為紅色,

[對應第二篇文章中,的情況1:z的叔叔是紅色的。]

void insert_case3(node n) {

     if (uncle(n) != NULL &amp;&amp; uncle(n)-&gt;color == RED) {

         n-&gt;parent-&gt;color = BLACK;

         uncle(n)-&gt;color = BLACK;

         grandparent(n)-&gt;color = RED;

         insert_case1(grandparent(n));   //因為祖父節點可能是紅色的,違反性質4,遞歸情形1.

     }

         insert_case4(n);   //否則,叔叔是黑色的,轉到下述情形4處理。

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

此時新插入節點N做為P的左子節點或右子節點都屬于上述情形3,上圖僅顯示N做為P左子的情形。

情形4: 父節點P是紅色,叔叔節點U是黑色或NIL; 

插入節點N是其父節點P的右孩子,而父節點P又是其父節點的左孩子。

[對應我第二篇文章中,的情況2:z的叔叔是黑色的,且z是右孩子]

void insert_case4(node n) {

     if (n == n-&gt;parent-&gt;right &amp;&amp; n-&gt;parent == grandparent(n)-&gt;left) {

         rotate_left(n-&gt;parent);

         n = n-&gt;left;

     } else if (n == n-&gt;parent-&gt;left &amp;&amp; n-&gt;parent == grandparent(n)-&gt;right) {

         rotate_right(n-&gt;parent);

         n = n-&gt;right;

     insert_case5(n);    //轉到下述情形5處理。

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情形5: 父節點P是紅色,而叔父節點U 是黑色或NIL,

要插入的節點N 是其父節點的左孩子,而父節點P又是其父G的左孩子。

[對應我第二篇文章中,情況3:z的叔叔是黑色的,且z是左孩子。]

void insert_case5(node n) {

     n-&gt;parent-&gt;color = BLACK;

     grandparent(n)-&gt;color = RED;

     if (n == n-&gt;parent-&gt;left &amp;&amp; n-&gt;parent == grandparent(n)-&gt;left) {

         rotate_right(grandparent(n));

     } else {

         /* 反情況,N 是其父節點的右孩子,而父節點P又是其父G的右孩子 */

         rotate_left(grandparent(n));

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

三、紅黑樹删除的幾種情況

上文我們約定,兄弟節點設為S,我們使用下述函數找到兄弟節點:

struct node * sibling(struct node *n)  //找兄弟節點

        if (n == n-&gt;parent-&gt;left)

                return n-&gt;parent-&gt;right;

        else

                return n-&gt;parent-&gt;left;

}

情況1: N 是新的根。

void

delete_case1(struct node *n)

        if (n-&gt;parent != NULL)

                delete_case2(n);

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情形2:兄弟節點S是紅色

[對應我第二篇文章中,情況1:x的兄弟w是紅色的。]

void delete_case2(struct node *n)

        struct node *s = sibling(n);

        if (s-&gt;color == RED) {

                n-&gt;parent-&gt;color = RED;

                s-&gt;color = BLACK;

                if (n == n-&gt;parent-&gt;left)

                        rotate_left(n-&gt;parent);  //左旋

                else

                        rotate_right(n-&gt;parent);

        } 

        delete_case3(n);

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情況 3: 兄弟節點S是黑色的,且S的倆個兒子都是黑色的。但N的父節點P,是黑色。

[對應我第二篇文章中,情況2:x的兄弟w是黑色的,且兄弟w的倆個兒子都是黑色的。

(這裡,父節點P為黑)]

void delete_case3(struct node *n)

        if ((n-&gt;parent-&gt;color == BLACK) &amp;&amp;

            (s-&gt;color == BLACK) &amp;&amp;

            (s-&gt;left-&gt;color == BLACK) &amp;&amp;

            (s-&gt;right-&gt;color == BLACK)) {

                s-&gt;color = RED;

                delete_case1(n-&gt;parent);

        } else

                delete_case4(n);

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情況4: 兄弟節點S 是黑色的、S 的兒子也都是黑色的,但是 N 的父親P,是紅色。

[還是對應我第二篇文章中,情況2:x的兄弟w是黑色的,且w的倆個孩子都是黑色的。

(這裡,父節點P為紅)]

void delete_case4(struct node *n)

        if ((n-&gt;parent-&gt;color == RED) &amp;&amp;

                n-&gt;parent-&gt;color = BLACK;

                delete_case5(n);

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情況5: 兄弟S為黑色,S 的左兒子是紅色,S 的右兒子是黑色,而N是它父親的左兒子。

//此種情況,最後轉化到下面的情況6。

[對應我第二篇文章中,情況3:x的兄弟w是黑色的,w的左孩子是紅色,w的右孩子是黑色。]

void delete_case5(struct node *n)

        if  (s-&gt;color == BLACK) 

                if ((n == n-&gt;parent-&gt;left) &amp;&amp;

                    (s-&gt;right-&gt;color == BLACK) &amp;&amp;

                    (s-&gt;left-&gt;color == RED)) { 

                        // this last test is trivial too due to cases 2-4.

                        s-&gt;color = RED;

                        s-&gt;left-&gt;color = BLACK;

                        rotate_right(s);

                } else if ((n == n-&gt;parent-&gt;right) &amp;&amp;

                           (s-&gt;left-&gt;color == BLACK) &amp;&amp;

                           (s-&gt;right-&gt;color == RED)) {

                       // this last test is trivial too due to cases 2-4.

                        s-&gt;right-&gt;color = BLACK;

                        rotate_left(s);

                }

        }

        delete_case6(n);  //轉到情況6。

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

情況6: 兄弟節點S是黑色,S的右兒子是紅色,而 N 是它父親的左兒子。

[對應我第二篇文章中,情況4:x的兄弟w是黑色的,且w的右孩子時紅色的。]

void delete_case6(struct node *n)

        s-&gt;color = n-&gt;parent-&gt;color;

        n-&gt;parent-&gt;color = BLACK;

        if (n == n-&gt;parent-&gt;left) {

                s-&gt;right-&gt;color = BLACK;

                rotate_left(n-&gt;parent);

        } else {

                s-&gt;left-&gt;color = BLACK;

                rotate_right(n-&gt;parent);

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

//呵呵,畫這12張圖,直接從中午畫到了晚上。希望,此文能讓你明白。

四、紅黑樹的插入、删除情況時間複雜度的分析

因為每一個紅黑樹也是一個特化的二叉查找樹,

是以紅黑樹上的隻讀操作與普通二叉查找樹上的隻讀操作相同。

然而,在紅黑樹上進行插入操作和删除操作會導緻不再符合紅黑樹的性質。

恢複紅黑樹的屬性需要少量(O(log n))的顔色變更(實際是非常快速的)和

不超過三次樹旋轉(對于插入操作是兩次)。

雖然插入和删除很複雜,但操作時間仍可以保持為 O(log n) 次。

一步一圖一代碼,一定要讓你真正徹底明白紅黑樹【轉】

ok,完。

後記:

此紅黑樹系列,前前後後,已經寫了4篇文章,如果讀者讀完了這4篇文章,

對紅黑樹有一個相對之前來說,比較透徹的了解,

那麼,也不枉費,我花這麼多篇幅、花好幾個鐘頭去畫紅黑樹了。

真正了解一個資料結構、算法,最緊要的還是真正待用、實踐的時候體會。

歡迎,各位,将現在、或以後學習、工作中運用此紅黑樹結構、算法的經驗與我分享。

謝謝。:D。

----------------------------------------

        作者聲明:

        本人July對本部落格所有文章和資料享有版權,轉載、或引用任何内容請注明出處。

        向您的厚道緻敬。謝謝。二零一一年一月九日。

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀