紅黑樹筆記

摘要

本博文默認讀者已經明白了二叉搜索樹的插入和刪除算法,熟練的掌握左右旋。本文適合掌握BiTree和AVL樹的讀者,但想學習紅黑樹的讀者。ios

簡介

​ 紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯數組。它是在1972年由Rudolf Bayer發明的,當時被稱爲平衡二叉B樹(symmetric binary B-trees)。後來,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改成現在的「紅黑樹」。紅黑樹和AVL樹相似,都是在進行插入和刪除操做時經過特定操做保持二叉查找樹的平衡,從而得到較高的查找性能。它雖然是複雜的,但它的最壞狀況運行時間也是很是良好的,而且在實踐中是高效的: 它能夠在O(log n)時間內作查找,插入和刪除,這裏的n 是樹中元素的數目。c++

性質

性質1. 節點是紅色或黑色。
性質2. 根節點是黑色。
性質3. 每一個葉結點(NIL)是黑色的
性質4. 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
性質5. 從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。算法

——from《算法導論》P174數組

定義

定義1. 樹中的每個結點具備五個屬性:color, key, left, right, p
定義2. 哨兵T.nil是一個樹的普通結點,它的color屬性爲BLACK,其餘隨意。
定義3. 從某個結點x出發(不含該結點)到達一個葉結點的任意一條簡單路徑上的黑色結點個數稱爲該結點的黑高,記爲bh(x)。數據結構

——from《算法導論》P174less

難點

​ 紅黑樹,經過紅黑狀態來描述整棵樹的平衡狀況。它的插入操做和刪除操做與基本的二叉搜索樹一致,可是增長的對紅黑狀態的維護和維持樹的平衡,即顏色的調整平衡結構的調整ide

插入算法

描述(3case)

​ 插入一個結點,這個結點默認是紅色(若是父結點是黑色,幾乎不用調整結構)。當其父結點同爲紅色時須要調整,不然破壞了性質4(每一個紅色節點的兩個子節點都是黑色)。若是它的叔父結點的顏色是紅色,那麼僅僅經過改變顏色,便可維護紅黑性質。不然須要利用左右旋調整結構。性能

!!!!注意!!!!
case 2調整後的"Z" 爲調整前的"Z.P",同理調整後的"Z.P"是調整前的"Z"。學習

FIXUP僞碼

RB-INSERT-FIXUP(T, z)
while z.p.color == RED          //父結點紅色,違反性質4
    if z.p == z.p.p.left        //父結點是祖父結點左孩子
        y = z.p.p.right         //得到叔父結點
        if y.color == RED       //case 1:父結點和叔父結點同紅色
            z.p.color = BLACK
            y.color = BLACK
            z.p.p.color = RED   //!!注意root.p=NIL NIL.color=BLACK
            z = z.p.p           //!!自下向上調整
        else if z == z.p.right  //case 2:z是父結點的右孩子
               z = z.p
               LEFT-ROTATE(T, z)//左旋完成後由case2變成case3
            z.p.color = BLACK   //case 3:叔父z是父結點的左孩子
            z.p.p.color = RED
            RIGHT-ROTATE(T, z.p.p)
    else (same as then clause with 「right」 and 「left」 exchanged)
                                //這是一個鏡像關係,交換左右旋轉條件
T.root.color = BLACK            //保持根結點的顏色

注意:ui

僞碼的"else if"與高級語言中的"else if"不一樣,至關於高級語言的"else {if }"。高級語言中的"else if {}"在僞碼中記爲"elseif"

刪除算法

描述(4case)

討論如下幾個問題:

問1:二叉搜索樹的刪除操做都作了什麼?
答1:刪除非葉子結點y,實質上是在y的子孫結點中找到一個結點x代替y

問2:在紅黑樹中x替代y,他們的顏色變化有哪幾種可能?
答2:四種可能性,記[y.color,x.color],那麼有[RED,RED],[RED,BLACK],,[BLACK,RED],[BLACK,BLACK]

問3:這四種變化,會不會影響紅黑性質?若是影響了紅黑性質,具體破壞哪一條性質?
答3:

[y.color,x.color] 影響的性質
[RED,RED] 沒有任何影響
[RED,BLACK] 沒有任何影響
[BLACK,RED] 性質2,性質4,性質5
[BLACK,BLACK] 性質5

——緣由參考《算法導論》P184

達成這三個共識,下面的僞碼就不難理解了,其中變量y-original-color存儲了發生變化前的y顏色

問4:怎樣恢復紅黑性質呢?
答4:

[y.color,x.color] 解決的辦法
[BLACK,RED] 把紅塗成黑
[BLACK,BLACK] emmmm

問5:[BLACK,BLACK]怎麼破壞了性質5?咋弄?
答5:移動y將致使先前包含y的任何簡單路徑上黑結點個數少1,所以y的任何祖先都不知足性質5。那麼,咱們有兩條思路:1.把包含y的任何簡單路徑上增長一個黑色結點(不能破壞其餘路徑的紅黑性質,且這個紅色結點只能是y的父結點)2.把其餘路徑上的一個黑色結點變成紅結點。

FIXUP僞碼

這是爲了方便沒有《算法導論》時參考閱讀的:

RB-DELETE-FIXUP(T, x)
while x!= T.root and x.color == BLACK
    if x == x.p.left                    //x爲左孩子
        w = x.p.right                   //w是x的兄弟結點
        if w.color = RED                //case 1:
            w.color = BLACK
            x.p.color = RED
            LEFT-ROTATE(T, x.p)
            w = x.p.right               //轉換爲case 2,或者case 三、case 4
        if w.left.color == BLACK and w.right.color == BLACK
            w.color = RED               //case 2:
            x = x.p
        else 
            if w.right.color == BALCK
                w.left.color = BLACK    //case 3:   
                w.color = RED
                RIGHT-ROTATE(T, w)
                w = x.p.right           //轉換爲case4
            w.color = x.p.color         //case 4:
            x.p.color = BLACK
            w.right.color = BLACK
            LEFT-ROTATE(T, x.p)
            x = T.root
    else (same as then clause with 「right」 and 「left」 exchanged)
                                    //這是一個鏡像關係,交換左右旋轉條件
x.color = BLACK                     //把紅塗成黑

圖(a)中A是黑色,D是紅色。圖(b)中B是紅色或者黑色。

再根據僞碼分析圖:
case 1 :交換了BD顏色,左旋B。調整後ABC構成case 二、case 3 或者case 4的狀況(具體取決於c的孩子結點)。
case 2:將D的顏色由黑色改成紅色,利用思路2。可是,結點B可能還有兄弟結點,他們的路徑也須要你減1。這就至關於回到了問題的起點,利用while循環自下向上調整。
case 3:交換了CD顏色,右旋B。調整後ABCD構成了case 4中的ABDE。
case 4:比較調整完成後Alpha、Beta的路徑上的黑結點數,增長了1,其餘子樹均保持不變。這是思路1的結果。因此直接令x = T.root終止循環

附錄 c++ _RB_Tree的調用接口

#include <iostream>
#include <ext/functional>
#include <bits/stl_tree.h> //std::_Rb_tree
#include <bits/stl_function.h>

int main()
{
    std::_Rb_tree<int, int, __gnu_cxx::identity<int>, std::less<int>> myTree;
    std::cout << myTree.empty() << std::endl; //1
    std::cout << myTree.size() << std::endl;  //0

    myTree._M_insert_unique(3);
    myTree._M_insert_unique(8);
    myTree._M_insert_unique(5);
    myTree._M_insert_unique(9);
    myTree._M_insert_unique(13);
    myTree._M_insert_unique(5);               //not effect, since using _M_insert_unique().
    std::cout << myTree.empty() << std::endl; //0
    std::cout << myTree.size() << std::endl;  //5
    std::cout << myTree.count(5) << std::endl;//1

    myTree._M_insert_equal(5);
    myTree._M_insert_equal(5);
    std::cout << myTree.size() << std::endl;  //7, since using _M_insert_equal().
    std::cout << myTree.count(5) << std::endl;//3
    return 0;
}

——參考侯捷老師的講義

後記

​ 插入和刪除算法我花費了3個下午來推理,其中插入算法推理迅速完成。可是我倒在了刪除算法的case之中,其中的case列舉和轉換讓我直接迷路。本博客與《算法概論》的惟一不一樣在,原書將[BLACK,BLACK]破壞的性質5轉換破壞性質1,讓x結點成爲雙重黑色的非根結點(這個SmartPoint減少了正向推理過程當中的大量分析)。可是我沒有采用這樣的論述,是由於這樣作勢必會在四種case分析中討論這個SmartPoint的妙處,論述難度指數爆炸。因此我採用解說僞碼的論述,而不是正向的推理。若是你有興趣的話,不妨閱讀原書仔細體味。

相關文章
相關標籤/搜索