資料結構基礎03-紅黑樹

wuzhiaite發表於2020-09-01

紅黑樹

     紅黑樹是一種自平衡的二叉樹;

紅黑樹必須要遵循的規則:

  • 1.節點是紅色或黑色;

  • 2.根節點為黑色;

  • 3.每個葉子節點都是黑色的空節點;

  • 4.紅色節點不能有紅色的父節點或子節點

  • 5.從任一節點到葉子節點的黑色節點數必須一致;

紅黑樹的兩大操作:變色和旋轉

  • 變色:將節點的顏色由黑變紅,或者由紅變黑

  • 旋轉:通過有兩種旋轉方式

    • 左旋轉:將右孩子替換父節點成為新的父節點,原父節點順勢左轉成為新的父節點的左孩子;

    • 右旋轉:將左孩子替換父節點變成新的父節點,原父節點順勢向右成為新的父節點的右孩子;

資料結構基礎03-紅黑樹

備註:新插入的節點預設是紅色的;

紅黑樹插入新節點操作

情況1:新插入節點的父節點和父節點同一水平的叔叔節點,都是紅色時

  • 將父節點和叔叔節點變成黑色;

  • 祖父節點變成紅色;

  • 如果祖父節點是根節點時,變成黑色;

資料結構基礎03-紅黑樹

情況2:新插入節點的父節點為紅色,父節點同一水平的叔叔節點為黑色

這種要根據新插入的節點,在根節點的插入位置不同,有不同的情況

  • 最小子樹的左節點增加左子節點

    • 進行右旋操作
資料結構基礎03-紅黑樹
  • 最小子樹的左節點增加右子節點

    • 先進行左旋,調整節點

    • 再進行右旋,調整節點顏色

資料結構基礎03-紅黑樹
  • 最小子樹的右節點增加左子節點

    • 進行右旋操作

    • 再進行左旋操作

資料結構基礎03-紅黑樹
  • 最小子樹的右節點增加右子節點

    • 進行左旋操作
資料結構基礎03-紅黑樹

案例

  • 逐個增加10 70 32 34 13 56 56 21 3 62 4

具體可以看我的另一篇部落格:

https://www.cnblogs.com/perferect/p/13577085.html

實現程式碼(Java版本)

    java中Map集合,在JDK1.8之後,Entry陣列,同一個HASH的值儲存改成了紅黑樹儲存。以ConcurrentHashMap程式碼進行紅黑樹相關帶啊分析;
以下程式碼的git地址:https://gitee.com/wuzhiaite/cs_java/blob/master/src/test/java/com/wuzhiaite/javaweb/base/bitree/RedBlackTree.java

  • TreeNode 程式碼

    • 節點三個連線,一個顏色標記
class RedBlackNode< V extends Comparable<V>> {
        RedBlackNode<V> parent;  // 紅黑樹的連線
        RedBlackNode<V> left;
        RedBlackNode<V> right;
        boolean red;
        volatile V val;

        RedBlackNode(V val,
                     RedBlackNode<V> parent) {
            this.val = val ;
            this.parent = parent ;
        }

}
  • 紅黑樹的結構

    • 需要存放根節點
public final class RedBlackTree<V extends Comparable<V>> {

    RedBlackNode root;

}
  • 紅黑樹的插入操作
 /**
     * 1.查詢插入值的父節點
     * 2.進行值插入和紅黑樹自平衡
     * @param v
     * @return
     */
    public RedBlackNode putTreeVal(V v){
        boolean searched = false;
        for (RedBlackNode<V> p = root;;) {
            int dir = 0;V pv;
            if (p == null) {//根節點
                root = new RedBlackNode(v,null);
                break;
            }
            else if ((pv = p.val).compareTo(v) < 0) //根節點值小於新插入值
                dir = -1;
            else if (pv.compareTo(v) > 0 )//跟節點值大於新插入節點
                dir = 1;
            else if (pv == v || (pv != null && pv.equals(pv)))//判斷是否已經插入過
                return p;
            else{
                //查詢是否已經存在該節點,如果存在則返回該節點
                //根的左子樹進行查詢判斷
                //根的右子樹進行查詢判斷
                if (!searched) {
                    RedBlackNode<V> q, ch;
                    searched = true;
                    if (((ch = p.left) != null &&
                            (q = ch.findTreeNode(v)) != null) ||
                            ((ch = p.right) != null &&
                                    (q = ch.findTreeNode(v)) != null))
                        return q;
                }
            }

            //1.提前將p的值賦值給一個新的變數px
            //2.根據上面值判斷結果,如果新插入值小於節點p,則p賦值為左子樹,否則賦值為右子樹
            //3.如果p為null,表示是可以插入值的節點
            RedBlackNode<V> xp = p;
            if ((p = (dir <= 0) ? p.left : p.right) == null) {
                RedBlackNode<V> x;
                x = new RedBlackNode<V>(v,xp);//生成新的節點,父節點賦值為上面查詢出來的可連線點
//                if (x != null)
//                    f.prev = x;
                //根據dir值,關聯新生節點和父節點
                if (dir <= 0)
                    xp.left = x;
                else
                    xp.right = x;
                if (!xp.red)//如果父節點是黑色的,則表明
                    x.red = true;
                else {
                    root = balanceInsertion(root, x);//進行平衡插入資料
                }
                break;
            }
        }
        return null;
    }

     /**
     *  插入資料
     * @param root
     * @param x
     * @return
     */
    private RedBlackNode<V> balanceInsertion(RedBlackNode<V> root, RedBlackNode<V> x) {
        x.red = true;
        for (RedBlackNode<V> xp, xpp, xppl, xppr;;) {
            //如果插入節點的父節點為空,則表示當前節點為根節點,變色並返回當前節點
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            //如果插入值的祖父節點空,或父節點是黑色時,返回父節點
            else if (!xp.red || (xpp = xp.parent) == null)
                return root;
            /**
             *         xpp
             *        /    \
             *       xp   xppr
             *       /
             *      x
             *下面的情況時父節點為紅色,且祖父節點不為空時
             *父節點是祖父節點的左節點時
             */
            if (xp == (xppl = xpp.left)) {
                //判斷叔叔節點如果是紅色,則父節點和叔叔節點變色
                if ((xppr = xpp.right) != null && xppr.red) {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;//祖父節點變紅
                    x = xpp;//將祖父節點賦值給當前節點,用於判斷祖父節點和祖父節點的父節點是否都為紅色
                }
                //父節點和叔叔節點顏色不同時
                else {
                    /**
                     *         xpp
                     *        /    \
                     *       xp   xppr
                     *         \
                     *          x
                     *
                     * 如果要插入的節點是父節點的右節點時進行左轉
                     * 即,新的節點給最小子樹的左節點,新增右子節點時進行左轉
                     */
                    if (x == xp.right) {
                        root = rotateLeft(root, x = xp);//這裡x=xp是因為,左旋是x替換xp的過程,所以用x=xp
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    /**
                     *         xpp
                     *        /    \
                     *       xp   xppr
                     *       /
                     *      x
                     *  先變色再旋轉
                     **/
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            }
            /**
             *  插入節點的父節點為祖父節點的右節點時
             */
            else {
                /**
                 * 父節點和兄弟節點為紅色時,同時變得,並將x變更為祖父節點
                 * 判斷祖父節點是否符合紅黑樹規則
                 */
                if (xppl != null && xppl.red) {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                /**
                 * 異色進行旋轉
                 */
                else {
                    /**
                     *         xpp
                     *        /    \
                     *       xpl   xp
                     *            /
                     *           x
                     *   進行右旋
                     *         xpp
                     *        /    \
                     *       xpl   xp
                     *               \
                     *                x
                     */
                    if (x == xp.left) {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    /**
                     * 1.xp非空的時候,先進行變色,再進行左旋
                     *          xp
                     *        /    \
                     *       xpp    x
                     *      /
                     *     xpl
                     */
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

  • 紅黑樹插入操作中設計到左旋和右旋操作

左旋操作

 
    /**
     *
     * 進行左旋轉
     * @param root
     * @param p
     * @return
     */
    private RedBlackNode<V> rotateLeft(RedBlackNode<V> root, RedBlackNode<V> p) {
        RedBlackNode<V> r, pp, rl;
        /**
         *         pp
         *        /    \
         *       p   ppr
         *        \
         *         r
         *        /  \
         *       rl  rr
         *  左旋操作:
         *  1.r節點的左節點rl變成p節點的左節點
         *  2.r節點的父節點變成p的父節點
         *  3.p的父節點變成r
         *         pp
         *        /    \
         *       r   ppr
         *      / \
         *     p   rr
         *    /
         *   rl
         *
         */
        if (p != null && (r = p.right) != null) {
            if ((rl = p.right = r.left) != null)
                rl.parent = p;
            if ((pp = r.parent = p.parent) == null)//如果pp節點為空,也即r為根節點,顏色變黑
                (root = r).red = false;
            else if (pp.left == p)
                pp.left = r;
            else
                pp.right = r;
            r.left = p;
            p.parent = r;
        }
        return root;
    }

右旋操作

 /**
     *  進行右旋
     * @param root
     * @param p
     * @return
     */
    private RedBlackNode<V> rotateRight(RedBlackNode<V> root, RedBlackNode<V> p) {
        /**
         *           p
         *        /    \
         *        l    pr
         *      /  \
         *    ll    lr
         * 右旋操作:
         * 1.p節點的左節點變成l的右節點
         * 2.l的父節點變成p的父節點
         * 3.p的父節點變成l
         *           l
         *        /    \
         *      ll      p
         *            /   \
         *          lr     pr
         */
        RedBlackNode<V>  l, pp, lr;
        if (p != null && (l = p.left) != null) {
            if ((lr = p.left = l.right) != null)
                lr.parent = p;
            if ((pp = l.parent = p.parent) == null)
                (root = l).red = false;
            else if (pp.right == p)
                pp.right = l;
            else
                pp.left = l;
            l.right = p;
            p.parent = l;
        }
        return root;
    }

其他操作-查詢節點

  • 查詢紅黑樹中的節點,和所有二叉樹一樣進行二分法進行查詢。程式碼也比較簡單
    //用於查詢節點的父節點
        final RedBlackNode<V> findTreeNode(V v) {
            if (v != null) {
                RedBlackNode<V> p = this;
                do  {
                    int dir; V pv; RedBlackNode<V> q;
                    RedBlackNode<V> pl = p.left, pr = p.right;
                    if ((pv = p.val).compareTo(v) > 0)//從左子樹查詢
                        p = pl;
                    else if ((pv = p.val).compareTo(v) < 0)//從右子樹查詢
                        p = pr;
                    else if ((pv = p.val) == v || (pv != null && p.val.equals(pv)))//如果剛好和當前節點相同,則返回
                        return p;
                    else if (pl == null)
                        p = pr;
                    else if (pr == null)
                        p = pl;
                    else if ((q = pr.findTreeNode(v)) != null)//如果沒有,則迴圈遍歷,知道查詢的父節點P為null
                        return q;
                    else
                        p = pl;
                } while (p != null);
            }
            return null;
        }


其他操作-刪除節點(比較複雜,瞭解)

  • 紅黑樹節點刪除涉及的情況比較多也比較複雜,大致分這幾種:

    • 刪除節點是根節點的;

    • 刪除節點有左右節點的情況;

    • 刪除節點只有左節點情況

    • 刪除節點只有右節點情況。

  • 刪除的思路:

    • 找到刪除節點的臨界節點,以上四種情況分別有不同處理,找到合適的臨界節點做替換節點

    • 調整樹結構,將刪除節點移位。

    • 斷開刪除節點與左右父節點的關聯,替換節點關聯刪除父節點

    • 替換節點變色和進行左右旋操作,紅黑樹進行自平衡

  • 具體程式碼如下:

 /**
     *  刪除節點
     * @param p
     * @return
     */
    public boolean removeTreeNode(RedBlackNode<V> p){
        RedBlackNode<V>  r, rl;
        if ((r = root) == null || r.right == null ||
                (rl = r.left) == null || rl.left == null)
            return true;
        try {
            /**
             *   sp
             *     \
             *      p
             *    /  \
             *   pl  pr
             *     /
             *   s
             *    \
             *     sr
             */
            RedBlackNode<V> replacement;
            RedBlackNode<V> pl = p.left;
            RedBlackNode<V> pr = p.right;
            if (pl != null && pr != null) {
                RedBlackNode<V> s = pr, sl;
                /**
                 * 1.刪除節點p的右節點pr
                 * 2.pr的左節點迴圈找到最小左子樹的左節點s
                 */
                while ((sl = s.left) != null)
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; //顏色交換
                RedBlackNode<V> sr = s.right;
                RedBlackNode<V> pp = p.parent;
                /**
                 *     pr
                 *       \
                 *        p
                 *  當pr==s時
                 * 要刪除的節點只有一個右節點時,進行交換
                 */
                if (s == pr) {
                    p.parent = s;
                    s.right = p;
                }
                else {
                    /**
                     *  以下的資料處理邏輯是這樣子的
                     *    pp                 pp                  pp
                     *     \                /                   /
                     *      p              s                   s
                     *    /  \           /  \                /  \
                     *   pl  pr   ————> pl   pr ————>       pl   pr
                     *     /               /                    /
                     *   s                p                    sr
                     *    \               \
                     *     sr             sr
                     *
                     *   1.找到要刪除節點p的右節點pr;
                     *   2.迴圈遍歷pr的所有子節點的左節點,找到最小子樹的左節點s;
                     *   3.p節點指向s的父節點,p成為s父節點的左子樹;
                     *   4.p的右子樹成為s節點的有子節點;
                     *   5.s的右子節點sr成為p的右子節點
                     *   6.s的成為p的左節點
                     */
                    RedBlackNode<V> sp = s.parent;
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null)
                    sr.parent = p;
                if ((s.left = pl) != null)
                    pl.parent = s;
                if ((s.parent = pp) == null)
                    r = s;
                else if (p == pp.left)
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            /**
             * 這個replacement分四種情況:
             * 1.p有左右節點,且p為根節點的最小值子樹的左節點(s),有sr右節點。則為右節點
             * 2.上面沒有右節點,則為p;
             * 3.p只有左節點pl,replacement=pl
             * 4.p只有右節點pr,replacement=pr
             */
            else if (pl != null)
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
                replacement = p;
            /**
             * 用replacement元素替換要刪除的p節點
             */
            if (replacement != p) {
                RedBlackNode<V> pp = replacement.parent = p.parent;//pr
                if (pp == null)
                    r = replacement;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }
            /**
             * 判斷刪除後的節點是否需要進行自平衡
             */
            root = (p.red) ? r : balanceDeletion(r, replacement);
            /**
             * 刪除p父親節點和p節點的關聯關係
             */
            if (p == replacement) {
                RedBlackNode<V> pp;
                if ((pp = p.parent) != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                    p.parent = null;
                }
            }
        } finally {

        }
        return false;
    }

    /**
     * 節點刪除
     * @param r
     * @param x
     * @return
     */
    private RedBlackNode balanceDeletion(RedBlackNode<V> r, RedBlackNode<V> x) {
        for (RedBlackNode<V>  xp, xpl, xpr;;)  {
            /**
             * 判斷是否為根節點
             */
            if (x == null || x == root)
                return root;
            else if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            else if (x.red) {
                x.red = false;
                return root;
            }
            /**
             *           xp              xp
             *         /    \           /
             *       xpl(x) xpr        x
             *             /   \
             *            sl    sr
             *
             *
             * 已知:1.x為xp左節點且為黑色
             *      2.xp節點肯定和x同色(因為紅黑樹不能有連續紅色節點,刪除掉中間節點後,只能都是紅色或黑色,上面判斷了x不能是紅色)
             * 操作:1.x的兄弟節點異色,進行左旋
             *
             *  上面分析了x(replacement)節點有5種情況
             */
            else if ((xpl = xp.left) == x) {
                if ((xpr = xp.right) != null && xpr.red) {
                    xpr.red = false;
                    xp.red = true;
                    root = rotateLeft(root, xp);
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                if (xpr == null)
                    x = xp;
                else {
                    RedBlackNode<V>  sl = xpr.left, sr = xpr.right;
                    if ((sr == null || !sr.red) &&
                            (sl == null || !sl.red)) {
                        xpr.red = true;
                        x = xp;
                    }
                    else {
                        if (sr == null || !sr.red) {
                            if (sl != null)
                                sl.red = false;
                            xpr.red = true;
                            root = rotateRight(root, xpr);
                            xpr = (xp = x.parent) == null ?
                                    null : xp.right;
                        }
                        if (xpr != null) {
                            xpr.red = (xp == null) ? false : xp.red;
                            if ((sr = xpr.right) != null)
                                sr.red = false;
                        }
                        if (xp != null) {
                            xp.red = false;
                            root = rotateLeft(root, xp);
                        }
                        x = root;
                    }
                }
            }
            else { // symmetric
                if (xpl != null && xpl.red) {
                    xpl.red = false;
                    xp.red = true;
                    root = rotateRight(root, xp);
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }
                if (xpl == null)
                    x = xp;
                else {
                    RedBlackNode<V>  sl = xpl.left, sr = xpl.right;
                    if ((sl == null || !sl.red) &&
                            (sr == null || !sr.red)) {
                        xpl.red = true;
                        x = xp;
                    }
                    else {
                        if (sl == null || !sl.red) {
                            if (sr != null)
                                sr.red = false;
                            xpl.red = true;
                            root = rotateLeft(root, xpl);
                            xpl = (xp = x.parent) == null ?
                                    null : xp.left;
                        }
                        if (xpl != null) {
                            xpl.red = (xp == null) ? false : xp.red;
                            if ((sl = xpl.left) != null)
                                sl.red = false;
                        }
                        if (xp != null) {
                            xp.red = false;
                            root = rotateRight(root, xp);
                        }
                        x = root;
                    }
                }
            }
        }
    }

相關文章