紅黑樹


性質:

紅黑樹是一棵二叉搜索樹,他在每個結點上添加了一個存儲位爲來標識結點的顏色。可以是RED或者BLACK。css

經過對不論什麼一條從根到葉子的簡單路徑上各個結點的顏色進行約束,保證沒有一條路徑比其它路徑長出2倍,因此是近似平衡的。html

  • 每個節點是紅色或者黑色
  • 根節點是黑色
  • 每個葉節點(NULL)是黑色的
  • 假設一個節點是紅色的。則它的兩個子節點都是黑色的
  • 對於每個結點,從該結點到其所有後代葉節點的簡單路徑上,均包括一樣數目的黑色節點

必定不會有兩個紅的節點相連,但是
會不會有兩個黑的節點相連?理論上是可以的。假設你認爲構造不出來,咱們假設一棵全黑的全然二叉樹,也符合上述性質。儘管這樣失去了紅黑樹的意義。
之因此我會想說上面的問題,我想到,黑節點的父節點必定是紅的嗎?看來也不必定。
(主觀臆斷,若有大神路過,發現錯誤請指正,謝啦)c++

結構體定義:

#define RED 0
#define BLACK 1

typedef struct RBTreeNode {
    bool color; 
    int value;
    RBTreeNode* left;
    RBTreeNode* right;
    RBTreeNode* parent;

    RBTreeNode(){
        color = BLACK;
        value = 0;
        left  = right = parent = NULL;
    }
}RBTreeNode;

RBTreeNode* Tnull;

int main() {
    Tnull = new RBTreeNode; // initialed by construct function
    return 0;
}

爲了便於處理邊界條件,所有爲空的指針都指向一個哨兵(Tnull),它是一個黑色節點,其它屬性不重要;git

黑高(black-height):

從某個節點x出發,到達一個葉子節點(此時的葉子節點就爲哨兵(Tnull))的隨意一條簡單路徑裏黑色節點的個數github

一棵有n個內部節點的紅黑樹的高度至多爲 2log(n+1) 算法

旋轉:

旋轉操做僅僅是輔助插入和刪除節點的操做,因此此時不用考慮顏色的變化,專一於指針的變化狀況就好
可以參照《算法導論》第三版中文版的圖13-2markdown

左旋:

//左旋
void LeftRotate(RBTreeNode* &root, RBTreeNode* x) {
    if (x == NULL) {
        cout << "Wrong input!\n";
        return;
    }

    RBTreeNode* y = x->right;   //暫存旋轉前x的右子樹

    if (y == NULL) {
        return;
    }

    x->right = y->left;             //設置旋轉後x的右子樹
    if (y->left != Tnull){
        y->left->parent = x;        //設置旋轉後x的右子樹的雙親
    }
    y->parent = x->parent;

    //先設置旋轉後y的雙親是因爲此時x的雙親尚未變
    //讓旋轉前x的雙親指向y
    if (x->parent == Tnull) {   //x旋轉前是樹根
        root = y;
    } else if (x == x->parent->left) { //x旋轉前是雙親的左子樹
        x->parent->left = y;
    } else if (x == x->parent->right) { //x旋轉前是雙親的右子樹
        x->parent->right = y;
    }

    y->left = x;    //設置旋轉後y的左子樹
    x->parent = y;  //設置旋轉後x的雙親爲旋轉後的y
}

可以參照凝視,分析一下設置的節點的順序。咱們依據這個思路可以寫出右旋:函數

//右旋
void RightRotate(RBTreeNode* &root, RBTreeNode* y) {
    if (y == NULL) {
        cout << "Wrong input!\n";
        return;
    }

    RBTreeNode* x = y->left;    //暫存旋轉前y的左子樹

    if (x == NULL) {
        return;
    }

    y->left = x->right;             //設置旋轉後y的左子樹
    if (x->right != Tnull){
        x->right->parent = y;       //設置旋轉後y的左子樹的雙親
    }
    y->parent = x->parent;

    //讓旋轉前y的雙親指向x
    if (y->parent == Tnull) {   //y旋轉前是樹根
        root = x;
    } else if (y == y->parent->left) { //y旋轉前是雙親的左子樹
        y->parent->left = x;
    } else if (y == y->parent->right) { //y旋轉前是雙親的右子樹
        y->parent->right = x;
    }

    x->right = y; //設置旋轉後x的右子樹
    y->parent = x; //設置旋轉後y的雙親爲x
}

插入:

爲何新插入的節點要標記爲紅色?假設不這樣會添加黑高,從而違反性質5
這裏的函數處理邊界狀況和異常狀況會比較麻煩….
分析狀況可以參考《算法導論》和一篇我看到的比較好的博文,結合在一塊兒;
博文:http://www.cnblogs.com/xuqiang/archive/2011/05/16/2047001.html
我也在代碼中添加了很是多凝視,實現過程是基於《算法導論》的僞碼,但願可以幫助理解post

代碼:
插入過程:ui

//先在外面建立好節點z再插入,過程很是像二叉搜索樹的插入
void Insert(RBTreeNode* &root, RBTreeNode* z) {

    RBTreeNode* y = Tnull;
    RBTreeNode* x = root;

    while(x && x != Tnull) { //用x遍歷去找合適的插入位置, y始終保持是x的雙親
        y = x;
        if (z->value < x->value) {
            x = x->left;
        } else {
            x = x->right;
        }
    }

    z->parent = y;
    if (y == Tnull) {
        root = z;
    } else if (z->value < y->value) {
        y->left = z;
    } else {
        y->right = z;
    }
    z->left = z->right = Tnull;
    z->color = RED;
    //InOrder(root);
    InsertFixUp(root, z);
}

插入以後的維護過程:

void InsertFixUp(RBTreeNode* & root, RBTreeNode* &z) {  
    while(z->parent && z->parent->color == RED) { //因爲z的顏色被設置爲紅色,此時違反性質4

        if (z->parent->parent != Tnull&& z->parent == z->parent->parent->left) {  
            //插入點的父節點是爺爺節點的左子樹 
            RBTreeNode* y = z->parent->parent->right; //y是叔叔節點
            if (y->color == RED) {    //case 1 叔叔節點是紅色
                z->parent->color = BLACK; //父親節點
                y->color = BLACK;   //叔叔節點
                z->parent->parent->color =  RED; //爺爺節點
                z = z->parent->parent; //此時爺爺節點有可能也違反規定。咱們尾遞歸上去
            } else if (z == z->parent->right) { //case 2 叔叔節點是黑色
                z = z->parent;
                LeftRotate(root, z); //假設插入節點是父節點的右子樹,旋過去
            }
            if (z->parent != Tnull) {
                z->parent->color = BLACK;
                if (z->parent->parent != Tnull) {
                    z->parent->parent->color = RED;
                    RightRotate(root, z->parent->parent);
                }
            }

        } else if (z->parent->parent != Tnull && z->parent == z->parent->parent->right){                                                                        
            //插入點的父節點是爺爺節點的右子樹
            RBTreeNode* y = z->parent->parent->left; //y是叔叔節點
            if (y->color == RED) {    //case 1 叔叔節點是紅色
                z->parent->color = BLACK; //父親節點
                y->color = BLACK;   //叔叔節點
                z->parent->parent->color =  RED; //爺爺節點
                z = z->parent->parent; //此時爺爺節點有可能也違反規定。咱們尾遞歸上去
            } else if (z == z->parent->left) { //case 2 叔叔節點是黑色
                z = z->parent;
                RightRotate(root, z); //假設插入節點是父節點的左子樹,旋過去

            }

            if (z->parent != Tnull) {
                z->parent->color = BLACK;
                if (z->parent->parent != Tnull ) {
                    z->parent->parent->color = RED;
                    LeftRotate(root, z->parent->parent);
                }
            }
        }
    }

    root->color = BLACK;
}

刪除:

看到書上說與插入相比,刪除操做要略微複雜些。。

。。。


首先一個替換的子例程:

//以v爲根的子樹替換一棵以u爲根的子樹
void TransPlant(RBTreeNode* & root, RBTreeNode* u, RBTreeNode* & v) {
    if (u->parent == Tnull) {
        root = v;       
    } else if (u == u->parent->left) {
        u->parent->left = v;
    } else {
        u->parent->right = v;
    }
    v->parent = u->parent;
}

而後是刪除的過程:

void Delete(RBTreeNode* & root, RBTreeNode* z) {
    if (root == NULL) {
        cout << "Already empty!" << endl;
        return;
    }
    if (z == NULL) {
        cout << "Not exist!" << endl;
        return;
    }
    // y節點有兩種狀況:
    // 1. z節點刪除後。y節點是要在樹中去替代z節點的節點
    // 2. y節點指向被刪除的z節點
    // 但是無論哪一種狀況。假設y的原來的顏色爲黑色,那麼就會引發紅黑樹性質的破壞
    RBTreeNode* y = z; //指向待刪除節點
    RBTreeNode* x; //x來記錄刪除後(移動到y節點的原始位置)的節點的位置
    bool y_original_color = y->color; //記錄原來待刪除節點的顏色
    //下面兩個case是待刪除節點僅僅有一個子樹或者沒有子樹
    if (z->left == Tnull) {
        x = z->right;
        TransPlant(root, z, z->right);
    } else if (z->right == Tnull) {
        x = z->left;
        TransPlant(root, z, z->left);
    } else { //待刪除節點有兩個子樹
        y = Min(z->right); //記錄待刪除節點的後繼
        y_original_color = y->color; //記錄待刪除節點的後繼的顏色
        x = y->right;
        if (y->parent == z) { //說明後繼是待刪除節點的右子樹的樹根
                x->parent = y;  
        } else { //把後繼換到右子樹的樹根
            TransPlant(root, y, y->right); 
            y->right = z->right;
            y->right->parent = y;
        }
        TransPlant(root, z, y);
        y->left = z->left;
        y->left->parent = y;
        y->color = z->color;
    }
    if (y_original_color == BLACK)
        DeleteFixUp(root, x); 
}

我在凝視裏已經寫了x表明什麼和y表明什麼,既然是y的移動有可能改變紅黑樹的性質,那麼DeleteFixUp() 的參數爲何是x呢?

y原來的顏色爲黑色:
假設y爲刪除的節點。那麼少了一個黑節點,y將本身的顏色下推給x;
假設y是z的後繼,要去替代z,那麼咱們將y的顏色設置爲z原來的顏色(上面代碼第38行)。爲了維持紅黑樹的性質,仍是將本身的顏色下推給了x;
而後x就變成了紅黑色或者雙重黑色

那麼也就是說刪除後。樹的其它性質儘管保持,但是x這個節點違反了性質1(因爲有雙重顏色),因此咱們FixUp的關鍵就在於x,因此參數是x;

DeleteFixUp:

void DeleteFixUp(RBTreeNode* & root, RBTreeNode* x) {
    RBTreeNode* w; //保持w爲x的兄弟
    while(x != root && x->color == BLACK) {
        if (x == x->parent->left) {
            w = x->parent->right;
            if (w->color == RED) { //case 1 w的顏色爲紅色
                w->color = BLACK;
                x->parent->color = RED;
                LeftRotate(root, x->parent);
                w = x->parent->right;
            }
            if (w->left->color == BLACK && w->right->color == BLACK) { //case 2 
                w->color = RED;
                x = x->parent;  //向上尾遞歸
            }
            else {
                if (w->right->color == BLACK) { //case 3 w的右子樹爲黑色
                    w->left->color = BLACK;
                    w->color = RED;
                    RightRotate(root, w);
                    w = x->parent->right;
                }
                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->right->color = BLACK;
                LeftRotate(root, x->parent);

                x = root;
            }       
        } else {
            w = x->parent->left;
            if (w->color == RED) { //case 1 w的顏色爲紅色
                w->color = BLACK;
                x->parent->color = RED;
                RightRotate(root, x->parent);
                w = x->parent->left;
            }
            if (w->left->color == BLACK && w->right->color == BLACK) { //case 2 
                w->color = RED;
                x = x->parent;  //向上尾遞歸
            }
            else {
                if (w->left->color == BLACK) { //case 3 w的左子樹爲黑色
                    w->right->color = BLACK;
                    w->color = RED;
                    LeftRotate(root, w);
                    w = x->parent->left;
                }
                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->left->color = BLACK;
                RightRotate(root, x->parent);
                x = root;
            }
        }
    }
    x->color = BLACK;
}

Debug了好幾天,最終寫完了。

假設想測試的話,可以去這裏:
https://github.com/preke/DataStructure/blob/master/RedBlackTree.c%2B%2B 這裏有完整的代碼(包括中序、前序遍歷,查找,最小值等等)。

相關文章
相關標籤/搜索