天天看點

紅黑樹的增加(插入)和删除

紅黑樹的增加(插入)和删除

☼ 紅黑樹之介紹:

-----------形态上是特殊的二叉搜尋樹【特殊展現在顔色上,同時在邏輯上它是等價于4階B樹的】

❀ 紅黑樹是怎麼等價于4 階B 樹的? ---------紅黑樹要變成B樹:需要将紅結點和黑結點進行合并(黑色作為根【也是中間元素】)。

✿ 紅黑-->B 樹: 結點有四種情況是:①紅-黑-紅、②紅-黑、③黑-紅、④黑

● 可以插入的位置的所有情況:

紅黑樹的增加(插入)和删除

◼ 紅黑樹必須滿足以下 5 條性質:

1. 節點是 RED 或者 BLACK

2. 根節點是 BLACK

3. 葉子節點(外部節點,空節點)都是 BLACK

4. RED 節點的子節點都是 BLACK:

✓ RED 節點的 parent 都是 BLACK

✓ 從根節點到葉子節點的所有路徑上不能有 2 個連續的 RED 節點

5. 從任一節點到葉子節點的所有路徑都包含相同數目的 BLACK 節點

一、紅黑樹結點的添加(插入)--添加必是添加到 -B樹葉子子結點的位置:

(1) 4階 B 樹的葉子結點一般有以下四種情況:

①    紅<---黑--->紅    ② 紅<---黑        ③ 黑--->紅      ④ 黑

(2) 分類讨論:

(2-1)當父結點是黑色(有四種情況)時【上圖的 7、8、11、12】,直接插入;【有可能是第一個結點-根(記得染黑根)】;

(2-2)剩下8種情況根據 叔父結點是否為紅色進行劃分:

(2-2-1)叔父是紅色時【上圖的 1、2、3、4】,舉個栗子:   紅(新插入的)<---紅<---黑【根】--->紅 (對于4階B樹而言,發生了上溢了,需要,進行分裂處理,根(染紅)上去,然後父結點、叔父結點染黑);即 :

紅黑樹的增加(插入)和删除

 (2-2-2)叔父不是紅色時【上圖的 5、6、9、10】,舉個栗子:   紅(新插入的)<---紅<---黑【根】:為了滿足紅黑樹定義的性質四:

   紅(新插入的)<---紅(變黑)<---黑【根】(變紅,因為B樹的結點組合中是紅黑紅(隻有一個黑作為中間元素的根))

  ●  現在需要修改:中間的黑色為根,需要修改那個<---的方向:改為:

  紅(新插入的)<---紅(變黑) 【根】--->黑【根】(變紅)

[觀察此刻情況,修改這種修改,符合右旋,再觀察其實就是跟AVL 的LL型原理一緻啦]

❀ 紅黑樹整個添加之後的處理的代碼如下:

@Override
    protected void afterAdd(Node<E> node) {
        // 判斷父結點
        Node<E> parent = node.parent;
        // 添加的是根【當添加第一個結點時】/ 或者上溢到了根結點時
        if (parent == null) {
            black(node);
            return;
        }
        // 若父結點是黑色時,不用處理,直接傳回
        if (isBlack(parent))
            return;

        // 若叔父結點是紅色的[B 樹的上溢]
        Node<E> uncle = parent.sibling();
        Node<E> grand = red(parent.parent);
        if (isRed(uncle)) {
            // 染黑爸爸、叔叔,把祖父染成紅色,相當于新插入的結點
            black(uncle);
            black(parent);
//            red(grand);
//            afterAdd(grand);
            // ① 上溢時,也要染紅祖父結點
//            afterAdd(red(grand));
            afterAdd(grand);
            return;
        }
        //觀察一下,對代碼進行優化,共同擁有的抽到外部之類的
        // 來到這裡叔父不是紅色
        if (parent.isLeftChild()) { // L
            // ② 旋轉時,也要 染紅結點
//            red(grand);
            
            if (node.isLeftChild()) { // LL
                //染黑父結點,染紅祖父結點
                black(parent);
//                red(grand);
                //右旋
//                rotateRight(grand);
            } else { // LR
                //染紅自己、染黑祖父結點
                black(node);
//                red(grand);
                //左旋後右旋
                rotateLeft(parent);
//                rotateRight(grand);
            }
            
            rotateRight(grand);
        } else { // R
            // ② 旋轉時,也要 染紅結點
//            red(grand);
            
            if (node.isLeftChild()) { // RL
                //染紅自己、染黑祖父結點
                black(node);
//                red(grand);
                //左旋後右旋
                rotateRight(parent);
//                rotateLeft(grand);
            } else { // RR
                //染黑父結點,染紅祖父結點
                black(parent);
//                red(grand);
                //左旋
//                rotateLeft(grand);
            }
            
            rotateLeft(grand);
        }
    }      

二、紅黑樹結點的删除--删除必是 -B樹葉子子結點的位置:

● 可以删除的位置的所有情況:

紅黑樹的增加(插入)和删除
紅黑樹的增加(插入)和删除

(2-1) 删除的直接是紅色結點時【上圖的 1、3、5、6】【2也一樣(因為它是度為2,最終删除的要麼是前驅的1或者後驅的3)】,就直接删除,不用處理。

(2-2) 删除的是黑色的結點:

(2-2-1)用來替代的結點是紅色時【上圖的 4、7】,處理:将替代的結點【5、6】染成黑色(因為替代成為了B樹的葉子結點了,根是黑色的)。

 (2-2-2)用來替代的結點是黑色時【上圖的 8】,處理:

紅黑樹的增加(插入)和删除

 ❀ 紅黑樹整個删除結點之後的處理的代碼如下:

@Override
    protected void afterRemove(Node<E> node) {
        //要删除結點是紅色,直接删除,不用處理
//        if(isRed(node))    return;
        //要删除的結點是黑色 ,然後用于替換删除結點的子結點是紅色,然後替換結點即可
        if(isRed(node)) {    //這裡的處理是包含了上面if(isRed(node))的情況
            black(node);
            return;
        }
        Node<E> parent = node.parent;
        //删除的是根結點,也是直接删除不用處理
        if(parent == null)    return;

        // 要删除結點是黑色,而且是葉子結點,沒有可以替換的結點時,
        // 判斷被删除的node 是左還是右,直接通過sibling 去拿就已經不準了,因為葉子結點已經删除了,拿不到哦 了
        // 需重新判斷一下
        boolean left = parent.left == null || node.isLeftChild();
        Node<E> sibling = left ? parent.right : parent.left;
        if (left) { // 被删除的是左邊的,兄弟結點在右邊
            if (isRed(sibling)) { // 兄弟結點是紅色,相當于以前删除時,先考慮删除度為2,将度為2的進行第一步處理後,它就變成了與删除度為1、0 的情況一緻了
                black(sibling); // 等下變成根
                red(parent);
                rotateLeft(parent);// 通過左邊旋轉,侄子變成了我的兄弟了
                // 更換兄弟
                sibling = parent.right;
            }
            // 現在來到這裡的兄弟都是黑色的啦
            if (isBlack(sibling.left) && isBlack(sibling.right)) { // 黑兄弟的孩子是黑色的,不能借【沒有紅色結點可以外借】
                // 借不了,隻能讓父結點下來進行跟兄弟合并【将父結點染黑(變成根),兄弟染紅】
                // 染色之前還需要考慮父結點原先是不是黑色【黑色會導緻下溢】
                boolean parentBlack = isBlack(parent);
                black(parent);
                red(sibling);
                if (parentBlack) {
                    // 下溢,遞歸處理,相當于父結點被删除
                    afterRemove(parent);
                }

            } else { // 兄弟結點至少有一個紅色子結點【可以外借時】
                // 即接下來的情況: 黑【根】->紅 紅<-黑【根】 紅<-黑【根】->紅
                // 兄弟結點的右邊是黑色,兄弟要先旋轉【即先處理情況: 黑->紅,處理完後,接下來變成了 RR的情況,與後邊兩者是一樣隻需要左旋轉】
                if (isBlack(sibling.right)) {
                    rotateRight(sibling);
                    sibling = parent.right; // 修改兄弟結點位置
                }

                // 染色處理【上去的兄弟,要保證顔色與原來的相同,結構不變 】
                color(sibling, colorOf(parent));

                // 兄弟的兒子【可以借的結點】要上去作為根
                black(sibling.right);
                // 父結點要旋轉下來作為根
                black(parent);

                rotateLeft(parent);
            }

        } else { // 删除結點是在右邊的,兄弟結點在左邊的
            if (isRed(sibling)) { // 兄弟結點是紅色,相當于以前删除時,先考慮删除度為2,将度為2的進行第一步處理後,它就變成了與删除度為1、0 的情況一緻了
                black(sibling); // 等下變成根
                red(parent);
                rotateRight(parent);// 通過右邊旋轉,侄子變成了我的兄弟了
                // 更換兄弟
                sibling = parent.left;
            }
            // 現在來到這裡的兄弟都是黑色的啦
            if (isBlack(sibling.left) && isBlack(sibling.right)) { // 黑兄弟的兄弟是黑色的,不能借【沒有紅色結點可以外借】
                // 借不了,隻能讓父結點下來進行跟兄弟合并【将父結點染黑(變成根),兄弟染紅】
                // 染色之前還需要考慮父結點原先是不是黑色【黑色會導緻下溢】
                boolean parentBlack = isBlack(parent);
                black(parent);
                red(sibling);
                if (parentBlack) {
                    // 下溢,遞歸處理,相當于父結點被删除
                    afterRemove(parent);
                }

            } else { // 兄弟結點至少有一個紅色子結點【可以外借時】
                // 即接下來的情況: 黑【根】->紅 紅<-黑【根】 紅<-黑【根】->紅
                // 兄弟結點的左邊是黑色,兄弟要先旋轉【即先處理情況: 黑->紅,處理完後,接下來變成了 LL的情況,與後邊兩者是一樣隻需要右旋轉】
                if (isBlack(sibling.left)) {
                    rotateLeft(sibling);
                    sibling = parent.left; // 修改兄弟結點位置
                }

                // 染色處理【上去的兄弟,要保證顔色與原來的相同,結構不變 】
                color(sibling, colorOf(parent));

                // 兄弟的兒子【可以借的結點】要上去作為根
                black(sibling.left);
                // 父結點要旋轉下來作為根
                black(parent);

                rotateRight(parent);

            }
        }
    }