五、紅黑樹

1. 2-3-4樹介紹

2-3-4是四階的B樹,他屬于一種多路查找樹,他的結(jié)構(gòu)有以下限制:
所有葉子結(jié)點都擁有相同的深度。
結(jié)點只能是2-結(jié)點,3-結(jié)點,4-結(jié)點之一。

  • 2-結(jié)點:包含1個元素的結(jié)點,有2個子結(jié)點。
  • 3-結(jié)點:包含2個元素的結(jié)點,有3個子結(jié)點。
  • 4-結(jié)點:包含3個元素的結(jié)點,有4個子結(jié)點。
    所有節(jié)點必須至少包含1個元素。
    元素始終保持排序順序,整體上保持二叉查找樹的性質(zhì),即父結(jié)點大于左子樹結(jié)點,小于右子樹結(jié)點。而且結(jié)點有多個元素時,每個元素大于他左邊和他左子樹的元素。
    下圖是一個典型的2-3-4樹:


    2-3-4樹

2-3-4樹的生成

234樹添加數(shù)據(jù),有兩種情況。

第一種:添加數(shù)據(jù)的節(jié)點的數(shù)據(jù)個數(shù)小于3個直接添加。
第二種:添加數(shù)據(jù)的節(jié)點的數(shù)據(jù)個數(shù)等于3個,需要將節(jié)點的3個數(shù)據(jù)分成3個234樹節(jié)點,中間的數(shù)據(jù)成為左右兩邊數(shù)據(jù)的雙親節(jié)點,然后根據(jù)添加數(shù)據(jù)的大小加到左右兩邊的子節(jié)點中。
這種向上分裂添加數(shù)據(jù)的有點在于能夠保證左右子樹高度一致,無需平衡234樹的左右高度。





2-3-4樹和紅黑樹的等價關(guān)系

2-3-4樹轉(zhuǎn)化為紅黑樹

2. 紅黑樹

紅黑樹,Red-Black Tree「RBT」是一個自平衡(不是絕對的平衡)的二叉查找樹(BST),樹上的每個節(jié)點都遵循下面的規(guī)則:
1.每個節(jié)點要么是黑色,要么是紅色。
2.根節(jié)點是黑色。
3.每個葉子節(jié)點(NIL)是黑色。
4.每個紅色結(jié)點的兩個子結(jié)點一定都是黑色。
5.任意一結(jié)點到每個葉子結(jié)點的路徑都包含數(shù)量相同的黑結(jié)點。

紅黑樹能自平衡,它靠的是什么?三種操作:左旋、右旋和變色

enum RBColor{
    BLACK,
    RED
};
typedef int KEY_TYPE;
typedef struct RBNode{
    struct RBNode *parent;
    struct RBNode *left;
    struct RBNode *right;
    enum RBColor color;
    KEY_TYPE key;
    void *value;
}RBNode, *RBTree;

//左旋轉(zhuǎn)
void leftRotate(RBTree *root,RBNode *node)
{
   
    RBNode *parent = node->parent;
    
    RBNode *right = node->right;
    RBNode *rleft = right->left;
    
    right->left = node;
    node->parent = right;
    node->right = rleft;
    if(rleft)
        rleft->parent = node;
    
    right->parent = parent;
    if(parent == NULL){
        *root = right;
    }else if(parent->left == node)
        parent->left = right;
    else if (parent->right == node)
        parent->right = right;

}

//右旋轉(zhuǎn)
void rightRotate(RBTree *root,RBNode *node)
{
    RBNode *parent = node->parent;
    
    RBNode *left = node->left;
    RBNode *lright = left->right;
    
    left->right = node;
    node->parent = left;
    
    node->left = lright;
    if(lright)
        lright->parent = node;
    
    left->parent = parent;
    if(parent == NULL)
        *root = left;
    else if (parent->left == node)
        parent->left = left;
    else if (parent->right == node)
        parent->right = left;
}

enum RBColor colorOf(RBNode *node)
{
    return node!=NULL ? node->color : BLACK;
}

void setColor(RBNode *node, enum RBColor color)
{
    if(node == NULL)
        return;
    node->color = color;
}

插入操作

1. 3結(jié)點插入結(jié)點情況

3結(jié)點插入子結(jié)點

3結(jié)點插入子結(jié)點情況

以下情況需要調(diào)整:

四種情況需要調(diào)整
第一種調(diào)整情況
第二種調(diào)整情況
  • 第二種先左旋然后跟第一種情況相同
  • 后兩種調(diào)整情況跟這兩種相同,只是方向相反而已。
2. 4結(jié)點插入子結(jié)點情況
4節(jié)點插入子結(jié)點
4結(jié)點插入子結(jié)點情況
  • 這些情況都需要調(diào)整
第一種調(diào)整情況
  • 其他三種情況都是將父結(jié)點和叔叔結(jié)點變成黑色,爺爺結(jié)點變紅色

void fixAfterInsert(RBTree *root,RBNode* node);
//二叉樹插入操作,非遞歸
void insert(RBTree root,KEY_TYPE key, void *value)
{
    RBNode *t = root;
    if(t == NULL)
    {
        root = (RBNode *)malloc(sizeof(RBNode));
        root->key = key;
        root->value = value;
        root->parent = NULL;
        return;;
    }
    
    int cmp;
    //1.找到插入位置(找到新增結(jié)點的父結(jié)點)
    RBNode *parent;
    do {
        parent = t;
        cmp = key - t->key;
        if(cmp < 0)
            t = t->left;
        else if (cmp > 0)
            t = t->right;
        else
        {
            t->value = value;
            return;
        }
            
    } while (t != NULL);
    
    RBNode *node = (RBNode *)malloc(sizeof(RBNode));
    node->key = key;
    node->value = value;
    node->parent = parent;
    
    if(cmp < 0)
        parent->left = node;
    else
        parent->right = node;
    
    //旋轉(zhuǎn)和變色 調(diào)整紅黑樹的平衡
    fixAfterInsert(&root, node);
}


void fixAfterInsert(RBTree *root,RBNode* node)
{
    node->color = RED;
    // 2結(jié)點不用調(diào)整,3 4結(jié)點才需要調(diào)整
    while(node != NULL && node != *root && node->parent->color == RED){
        if(node->parent == node->parent->parent->left){
            //需要調(diào)整的只剩下4種情況,有叔叔節(jié)點和沒有叔叔節(jié)點
            RBNode *uncle = node->parent->parent->right;
            if(colorOf(uncle) == RED){
                //叔叔節(jié)點如果是紅色,表示有叔叔節(jié)點
                //變色+循環(huán)
                //父親和叔叔變?yōu)楹谏?爺爺變?yōu)榧t色
                setColor(node->parent, BLACK);
                setColor(uncle, BLACK);
                setColor(node->parent->parent, RED);
                
                //循環(huán)往上處理
                node = node->parent->parent;
            }else{ //沒有叔叔節(jié)點
                if(node == node->parent->right){
                    //如果是右節(jié)點需要將父結(jié)點向左旋轉(zhuǎn)
                    node = node->parent;
                    leftRotate(root, node);
                }
                //父親變?yōu)楹谏?爺爺變?yōu)榧t色
                setColor(node->parent, BLACK);
                setColor(node->parent->parent, RED);
                
                //爺爺結(jié)點向右旋轉(zhuǎn)
                rightRotate(root, node->parent->parent);
            }
        }else{
            //需要調(diào)整的也是4種情況,剛好和上面4種情況左右相反
            RBNode *uncle = node->parent->parent->left;
            if(colorOf(uncle) == RED){
                setColor(node->parent, BLACK);
                setColor(uncle, BLACK);
                setColor(node->parent->parent, RED);
                
                //循環(huán)往上處理
                node = node->parent->parent;
            }else{
                if(node == node->parent->left){
                    node = node->parent;
                    rightRotate(root, node);
                }
                
                //父親變?yōu)楹谏?爺爺變?yōu)榧t色
                setColor(node->parent, BLACK);
                setColor(node->parent->parent, RED);
                //爺爺結(jié)點向左旋轉(zhuǎn)
                leftRotate(root, node->parent->parent);
                
            }
        }
    }
    
    setColor(*root,BLACK);
}

紅黑樹的刪除操作

  • 刪除規(guī)則按二叉搜索樹的刪除規(guī)則:
  1. 要刪除的結(jié)點是葉子結(jié)點:直接刪除,并將父結(jié)點指針置為NULL
  2. 要刪除的結(jié)點有一個孩子結(jié)點:刪除該結(jié)點,并將父結(jié)點的指針指向這個孩子結(jié)點
  3. 要刪除的結(jié)點有左右子樹:用另一個結(jié)點替代被刪除的結(jié)點:左子樹最大元素或右子樹的最小元素
  • 紅黑樹刪除和調(diào)整:
    二叉樹的刪除轉(zhuǎn)換為2-3-4樹的刪除刪除的其實就是2-3-4樹的最底層
    其實就是刪除2節(jié)點3節(jié)點4節(jié)點
    刪除3和4節(jié)點的時候可以通過本身的替換處理掉但是刪除2節(jié)點的時候,自己是不能夠處理的
  1. 刪除節(jié)點自己能夠搞定
  2. 刪除節(jié)點自己搞不定,向兄弟節(jié)點借 兄弟節(jié)點借
  3. 刪除節(jié)點自己搞不定,向兄弟節(jié)點借 兄弟節(jié)點不借
情況2的調(diào)整
情況3的調(diào)整
RBNode *Find(RBTree root, KEY_TYPE key)
{
    RBNode *node = root;
    while (node) {
        if(key < node->key)
            node = node->left;
        else if (key > node->key)
            node = node->right;
        else
            break;
    }
    return node;
}
RBNode *FindMax(RBNode *RBT)
{
    if(RBT)
        while (RBT->right) {
            RBT = RBT->right;
        }
    return RBT;
}

RBNode *FindMin(RBNode *RBT)
{
    if(RBT)
        while (RBT->left) {
            RBT = RBT->left;
        }
    return RBT;
}
void fixAfterDelete(RBTree root,RBNode *node);
void* delete(RBTree *root, KEY_TYPE key)
{
    RBNode *node = Find(*root, key);
    RBNode *tmp ,*parent,*child=NULL;
    if(node == NULL)
        return NULL;
    void *oldValue = node->value;
    if(node->left != NULL && node->right != NULL)
    {
        RBNode *rightMin = FindMin(node->right);
        node->key = rightMin->key;
        node->value = rightMin->value;
        node = rightMin;
    }
    tmp = node;
    parent = node->parent;
    child = node->left ? node->left : node->right;
    

    
    if(child){
        child->parent = parent;
        if(parent == NULL)
            *root = child;
        else if(parent->left == node)
            parent->left = child;
        else if(parent->right == node)
            parent->right = child;
        
        if(colorOf(node) == BLACK)
        {
            fixAfterDelete(*root,child);
        }
        
    }else{
        if(colorOf(node) == BLACK)
        {
            fixAfterDelete(*root,node);
        }
        if(parent == NULL)
            *root = child;
        else if(parent->left == node)
            parent->left = child;
        else if(parent->right == node)
            parent->right = child;
    }
       
        
    free(node);
    return oldValue;
}

/**
 刪除結(jié)點后的調(diào)整工作
 2-3-4樹刪除操作
 1. 刪除3/4結(jié)點 自己能搞定的
 2. 刪除2結(jié)點 自己搞不定,需要兄弟節(jié)點借,兄弟節(jié)點借
     父親結(jié)點下去,兄弟節(jié)點找個結(jié)點替換父親結(jié)點位置
 3. 刪除2結(jié)點 自己搞不定,需要兄弟借 兄弟不接
 */
void fixAfterDelete(RBTree root,RBNode *node)
{
    //情況2和3
    while (node != root && colorOf(node) == BLACK){
        //判斷node是父結(jié)點的左孩子還是右孩子
        if(node == node->parent->left){
            //1.找到兄弟節(jié)點
            RBNode *rNode = node->parent->right;
            //判斷是不是真正的兄弟節(jié)點
            if(colorOf(rNode) == RED){
                //不是真正的兄弟結(jié)點 變色 旋轉(zhuǎn)
                setColor(rNode, BLACK);
                setColor(node->parent, RED);
                leftRotate(&root, node->parent);
                rNode = node->parent->right;
            }
            //當(dāng)兄弟結(jié)點一個子結(jié)點都沒有 不借
            if(colorOf(rNode->left) == BLACK && colorOf(rNode->right) == BLACK){
                //3.兄弟結(jié)點 不借
                setColor(rNode, RED);
                node = node->parent;
            }else{
                //2.兄弟結(jié)點 借
                //如果兄弟節(jié)點的子結(jié)點是其左子結(jié)點 需要先變色 右轉(zhuǎn)一次
                if(colorOf(rNode->right) == BLACK){
                    //右子節(jié)點為空,左節(jié)點不為空
                    setColor(rNode->left, BLACK);
                    setColor(rNode, RED);
                    rightRotate(&root, rNode);
                    
                    rNode = node->parent->right;
                }
                //需要根據(jù)父結(jié)點做一次左旋操作 變色
                setColor(rNode, colorOf(node->parent));
                setColor(node->parent, BLACK);
                setColor(rNode->right, BLACK);
                
                leftRotate(&root, node->parent);
                node = root;
            }
  
            
        }else{
            //1.找到兄弟節(jié)點
            RBNode *lNode = node->parent->left;
            //判斷是不是真正的兄弟節(jié)點
            if(colorOf(lNode) == RED){
                //不是真正的兄弟結(jié)點 變色 旋轉(zhuǎn)
                setColor(lNode, BLACK);
                setColor(node->parent, RED);
                leftRotate(&root, node->parent);
                lNode = node->parent->left;
            }
            //當(dāng)兄弟結(jié)點一個子結(jié)點都沒有 不借
            if(colorOf(lNode->left) == BLACK && colorOf(lNode->right) == BLACK){
                //3.兄弟結(jié)點 不借
                setColor(lNode, RED);
                node = node->parent;
            }else{
                //2.兄弟結(jié)點 借
                //如果兄弟節(jié)點的子結(jié)點是其右子結(jié)點 需要先變色 左轉(zhuǎn)一次
                if(colorOf(lNode->left) == BLACK){
                    //左子節(jié)點為空,右節(jié)點不為空
                    setColor(lNode->right, BLACK);
                    setColor(lNode, RED);
                    leftRotate(&root, lNode);
                    
                    lNode = node->parent->left;
                }
                //需要根據(jù)父結(jié)點做一次左旋操作 變色
                setColor(lNode, colorOf(node->parent));
                setColor(node->parent, BLACK);
                setColor(lNode->right, BLACK);
                
                rightRotate(&root, node->parent);
                node = root;
            }
        }
    }
    //情況1:替換的結(jié)點為紅色 我們只需要變色為黑色即可
    setColor(node, BLACK);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內(nèi)容