数据结构基础03-红黑树

红黑树

     红黑树是一种自平衡的二叉树;

红黑树必须要遵循的规则:

  • 1.节点是红色或黑色;

  • 2.根节点为黑色;

  • 3.每个叶子节点都是黑色的空节点;

  • 4.红色节点不能有红色的父节点或子节点

  • 5.从任一节点到叶子节点的黑色节点数必须一致;

红黑树的两大操作:变色和旋转

  • 变色:将节点的颜色由黑变红,或者由红变黑

  • 旋转:通过有两种旋转方式

    • 左旋转:将右孩子替换父节点成为新的父节点,原父节点顺势左转成为新的父节点的左孩子;

    • 右旋转:将左孩子替换父节点变成新的父节点,原父节点顺势向右成为新的父节点的右孩子;

备注:新插入的节点默认是红色的;

红黑树插入新节点操作

情况1:新插入节点的父节点和父节点同一水平的叔叔节点,都是红色时

  • 将父节点和叔叔节点变成黑色;

  • 祖父节点变成红色;

  • 如果祖父节点是根节点时,变成黑色;

情况2:新插入节点的父节点为红色,父节点同一水平的叔叔节点为黑色

这种要根据新插入的节点,在根节点的插入位置不同,有不同的情况

  • 最小子树的左节点增加左子节点

    • 进行右旋操作
  • 最小子树的左节点增加右子节点

    • 先进行左旋,调整节点

    • 再进行右旋,调整节点颜色

  • 最小子树的右节点增加左子节点

    • 进行右旋操作

    • 再进行左旋操作

  • 最小子树的右节点增加右子节点

    • 进行左旋操作

案例

  • 逐个增加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;
                    }
                }
            }
        }
    }

posted @ 2020-09-01 09:53  PerfectLi  阅读(223)  评论(0编辑  收藏  举报