5张图带你彻底理解红黑树(含完整Java实现源码)

本文深入剖析红黑树的添加和删除操作,通过对比2-3-4树来解释红黑树的性质维护。提供了完整的Java代码实现,包括插入节点后对红黑树的调整以及删除节点后的修复策略。强调理解红黑树与2-3-4树的关系是理解其操作的关键,并指出错误理解和实现可能导致的后果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文主要聚焦在红黑树的添加删除的所有情况的归纳以及给出完整可运行的java实现代码,代码的正确性我已经验证,可以放心使用。

其实红黑树并不难懂,难懂的原因是没有理解红黑树产生的历史,以及红黑树与2-3-4树之间的关系。导致很多文章一上来就讨论红黑树的五条性质,然后让人云里雾里。看了网上很多中文博客文章,没有遇到一篇能完全讲明白的,很多文章都没有实现代码,有的给出了源码,但是代码并没能真正修复红黑树,更有甚者,画的图都不符合红黑树的性质。本文通过与2-3-4树对比的形式来给读者描述红黑树添加和删除所有可能的情况,以及在这些情况下如何修复红黑树的性质。并给出完整的Java源码实现。

注意,这里说的是普通红黑树,而不是算法4里面的左倾红黑树。左倾红黑树对应2-3树,是红黑树的一个变种,实现会相对普通红黑树简单,因为不需要考虑右倾情况。网上有些文章把普通红黑树对应到2-3树上,这是完全没有理解红黑树表现,不建议看。普通红黑树对应的应该是2-3-4树也就是4阶B树,

其实要把红黑树完全讲透,一篇文章是不够的。但是由于时间和精力有限。无法将涉及的知识都讲一遍,这篇文章默认读者有二叉搜索树(添加,删除),AVL树(旋转),B树和2-3-4树(理解红黑树的基础)的知识储备。如果没有这些知识储备,请务必先学习这些知识后再看本文。

红黑树的性质

  1. 根节点必须是黑色
  2. 节点的颜色只有红色和黑色
  3. NIL叶子节点都是黑色
  4. 红色节点的子节点必须是黑色(也就是不能出现两个连续的红色节点)
  5. 从任一节点到其每个NIL叶子(NIL节点)的所有简单路径都包含相同数目的黑色节点(也就是说黑色节点对应2-3-4树的高度)

红黑树的本质

红黑树其实就是通过五条性质约束二叉搜索树,使得这棵树可以转换成对应的2-3-4树。也就是说每一棵红黑树都有唯一一棵2-3-4树与之对应。
在这里插入图片描述
后面我们的添加删除后的调整都围绕着红黑树对应的2-3-4树进行的。所以一定要先理解2-3-4树的添加和删除,否则会看得云里雾里

2-3-4树(4阶B树)的添加和删除都是发生在B树最下面一层。红黑树中的黑色节点就是用来标记B树中的一个节点,所以这也是为什么要满足性质5的原因,如果不满足性质5,红黑树就不能对应到4阶B树上。然后性质4的约束,才能保证B树的阶是4阶

红黑树的插入以及其对应的2-3-4树的插入

Red-black 2-3-4 tree
MrDrBob, CC BY-SA 3.0, via Wikimedia Commons

通过上面这幅图片可以知道,红黑树的添加中对性质的维护完全是符合B树的添加。

例如:添加70。对应的2-3-4树节点上溢,红黑树,则是通过涂黑父亲节点和叔父节点来处理上溢。使得从根节点到叶子NIL节点的所有路径上的黑色节点数量加1。

下面通过2-3-4树的样式来绘制红黑树,强烈建议对比2-3-4树的添加和删除来观看学习。

节点之间的关系

在这里插入图片描述

添加节点所有可能情况和处理方案

首先添加的节点默认是红色,上溢的节点也是作为新添加的红色节点处理。
添加是通过判断父节点和叔父节点的颜色来处理不同的情况。
在这里插入图片描述

添加节点后修复红黑树的Java代码实现

    /**
     * 新添加的节点默认都是--红色
     * <pre>
     * 1. 添加到根节点:
     *      1.1 直接添加,然后染黑
     * 2. 添加到非根节点
     *      2.1 parent是黑色
     *          直接添加
     *      2.2 parent是红色
     *          i. 叔父节点是黑色或者空(1.空,。2.黑色,)
     *             a.空(说明这个是在叶子节点添加的情况)
     *               grand节点涂红
     *               按照节点的当前的类型进行旋转(分别有四种类型LL,LR,RR,RL)
     *               旋转后grand的sibling涂红
     *             b.黑色(说明是添加节点导致上溢的情况,出现了叔父节点为黑色的情况。对应的2-3-4树,不在同一层,所以跟空的情况一样处理)。
     *          ii.叔父节点是红色(添加的节点,将会导致该层溢出)
     *             (父节点 & 叔父节点) 涂黑
     *             grand涂红 当做新添加节点处理
     * </pre>
     *
     * @param node
     */
    private void afterAdd(RBNode<E> node) {
        RBNode<E> currentNode;
        do {
            currentNode = node;
            if (node.parent == null) {//添加到根节点,或者上溢到根节点
                root = node;
                black(node);
            } else {//非根节点
                if (isRed(node.parent)) {//parent 是红色
                    RBNode<E> grand = node.parent.parent;
                    RBNode<E> parent = node.parent;
                    if (isBlack(parent.sibling())) {//叔父节点是空或者黑色。没有溢出
                        //旋转操作
                        red(grand);
                        if (parent.isLeftChild()) {//L
                            if (node.isLeftChild()) {//LL
                                black(parent);
                            } else {//LR
                                black(node);
                                rotateLeft(parent);
                            }
                            rotateRight(grand);
                        } else {//R
                            if (node.isRightChild()) {//RR
                                black(parent);
                            } else {//RL
                                black(node);
                                rotateRight(parent);
                            }
                            rotateLeft(grand);
                        }
                    } else {//叔父节点红色,溢出
                        // 红 <- 黑 -> 红 -> new红, new红 <- 红 <- 黑 -> 红
                        red(grand);
                        black(parent.sibling());
                        black(parent);
                        node = grand;//不使用递归
                    }
                } else {//parent 是黑色
                    //直接添加,不用调整修复。就满足了红黑树的性质
                }
            }
        } while (currentNode != node);
    }

删除节点所有可能情况和处理方案

删除节点中最为复杂的情况就是删除黑色叶子节点(删除2-2)的情况
在这里插入图片描述

删除节点后修复红黑树的Java代码实现

    /**
     * <pre>
     *          ┌────────60───────┐
     *          │                 │
     *      ┌─R_55─┐           ┌─R_65─┐
     *      │      │           │      │
     *   ┌─50─┐    57─┐     ┌─63      70
     *   │    │       │     │
     * R_40  R_53    R_58 R_62
     *
     *
     * 可能会删除的节点有:
     * 1. 根节点:
     *      1.1 下溢导致的根节点删除
     *           ┌─25─┐
     *           │    │
     *          20    30
     *
     *          remove: 30(导致25下溢)
     *
     *           ┌─25
     *           │
     *         R_20
     *
     *          由于删除30会使得25产生下溢,但是下溢的是根节点,所以不需要处理。对应的2-3-4树的高度减1
     *
     *      1.2 非下溢导致的
     *          a.
     *           ┌─25
     *           │
     *         R_20
     *
     *          remove: 25
     *
     *          20
     *
     *          涂黑20(如果有红色子节点,要记得涂黑)
     *
     *          b.
     *          20
     *
     *          remove: 20
     *
     *          只有一个根节点不需要涂黑
     * 2. 非根节点:
     *                 ┌────────60───────┐
     *                 │                 │
     *             ┌─R_55─┐           ┌─R_65─┐
     *             │      │           │      │
     *          ┌─50─┐    57─┐     ┌─63      70
     *          │    │       │     │
     *        R_40  R_53    R_58 R_62
     *      2.1 红色叶子节点(40,53,58,62)
     *          直接删除即可
     *
     *      2.2 黑色节点
     *          2.2.1 黑色非叶子节点(57,63)-- 将红色叶子节点涂黑
     *
     *          2.2.2 黑色叶子节点(70(叶子),由于下溢导致被删除的黑色节点,也是需要当做叶子节点处理。)
     *          (根据红黑树的性质5,可知黑色节点对应的是2-3-4树的层级,所以必定还有其它黑色叶子节点在同一层级)
     *
     *              a.有黑色兄弟(在对应的2-3-4树中,被删除节点和他的兄弟在同一层)
     *
     *                i. 黑色兄弟的子节点是红色。向兄弟借
     *                  旋转让兄弟或者兄弟子节点替换parent,parent替换被删除叶子节点。使得红黑树对应的 2-3-4树最后一层保持不变
     *
     *                ii.黑色兄弟的子节点是黑色(空子节点和黑色字节点都当做黑色处理,因为无论哪一种,都无法借出)。向父亲借
     *                  将兄弟染红(向父节点借)
     *                    父亲是红色,就染黑
     *                    父亲是黑色,就下溢处理(这里特别要注意的地方是,下溢后,父节点就相当于新的被删除的节点,重新走afterRemove)
     *
     *              b.有红色兄弟(说明兄弟必然有黑色儿子)(在对应的2-3-4树中,被删除节点和他的兄弟在不在同一层,但是兄弟的黑儿子和被删除节点在同一层)
     *                       ┌──────77─────┐
     *                       │             │
     *                  ┌───55────┐      ┌─80─┐
     *                  │         │      │    │
     *                 17─┐    ┌─R_57─┐ 79 ┌─R_88─┐
     *                    │    │      │    │      │
     *                   R_29 56      62  84      93
     *                 remove: 77(实际删除的是后继节点79)
     *                       ┌──────79─────┐
     *                       │             │
     *                  ┌───55────┐      ┌─88─┐
     *                  │         │      │    │
     *                 17─┐    ┌─R_57─┐ 80─┐  93
     *                    │    │      │    │
     *                   R_29 56      62  R_84
     *                i.通过旋转将兄弟的黑儿子变成兄弟节点
     *                  按照a的情况处理
     *
     * 处理溢出的时候,防止将黑色节点的红色子节点染黑,导致不平衡。
     * </pre>
     *
     * @param node
     */
    private void afterRemove(RBNode<E> node) {
        RBNode<E> currentNode;
        boolean underflow = false;
        do {
            currentNode = node;
            if (node.parent == null) {//删除的是根节点
                if (underflow) {//下溢不做处理
                    underflow = false;
                } else {//删除度为1或者0的根节点导致的。如果删除的是度为1的根节点,这个时候要将根节点的红色子节点染黑
                    if (isRed(node.left)) {
                        black(node.left);
                    }
                    if (isRed(node.right)) {
                        black(node.right);
                    }
                }
            } else {//删除的是非根节点
                if (isRed(node)) {//删除的是红色节点,不用调整,因为不会影响红黑树的性质

                } else {//删除的是黑色节点。
                    // 删除非根节点的黑色子节点,被删除的节点必定有兄弟(性质5)
                    // 借的顺序是    兄弟 --> 兄弟的儿子 --> 父亲

                    //1. 黑(黑色叶子节点,下溢的黑色节点也要当做黑色叶子节点处理)
                    if (underflow || isBlack(node.left) && isBlack(node.right)) {
                        underflow = false;
                        //需要找兄弟节点借,如果兄弟节点没有,就找父节点借,节点会下溢
                        RBNode<E> parent = node.parent;
                        RBNode<E> sibling;
                        if (parent.hasTwoChildren()) {//在下溢的时候
                            sibling = node.isLeftChild() ? parent.right : parent.left;
                        } else {//在正常删除节点的时候,必然有一个子节点
                            sibling = parent.left == null ? parent.right : parent.left;
                        }

                        //兄弟节点是红色(兄弟必定有黑儿子),将兄弟的黑儿子变成兄弟
                        if (isRed(sibling)) {
                            red(parent);
                            black(sibling);
                            if (sibling.isLeftChild()) {
                                rotateRight(parent);
                                sibling = parent.left;
                            } else {
                                rotateLeft(parent);
                                sibling = parent.right;
                            }
                        }

                        //兄弟节点都是黑色的
                        if (isRed(sibling.left) || isRed(sibling.right)) {//兄弟有红色子节点,兄弟可以借
                            if (sibling.isLeftChild()) {//L
                                //先看左边有没有红色子节点,有的话。优先按照LL处理
                                //没有的话就是LR。通过左旋可以转成LL
                                if (isBlack(sibling.left)) {//LR
                                    rotateLeft(sibling);
                                    sibling = sibling.parent;
                                }

                                //此时都转成了LL情况,统一按照LL情况处理

                                if (colorOf(parent) == RBNode.BLACK) {
                                    black(sibling);
                                } else {
                                    red(sibling);
                                }
                                rotateRight(parent);
                            } else {
                                //RL转成RR来处理
                                if (isBlack(sibling.right)) {//RL
                                    rotateRight(sibling);
                                    sibling = sibling.parent;
                                }

                                //此时都转成了RR情况,统一按照RR情况处理

                                if (colorOf(parent) == RBNode.BLACK) {
                                    black(sibling);
                                } else {
                                    red(sibling);
                                }
                                rotateLeft(parent);
                            }
                            black(parent);
                            black(parent.sibling());
                        } else {//兄弟只有黑色子节点或者兄弟没有子节点。兄弟无法借出,需要从父节点借(父节点是黑色会产生下溢)
                            if (isRed(parent)) {
                                black(parent);
                                red(sibling);
                            } else {
                                red(sibling);
                                node = parent;
                                //实际节点是红<-黑,黑色节点下溢了。需要再执行afterRemove,但是不能让它被当有红色子节点的黑色节点处理
                                //而是当做被没有子节点的黑色节点处理(黑)
                                underflow = true;
                            }
                        }
                    } else {//1. 红<-黑   2. 黑->红      度为1的黑色节点
                        //将红色子节点染黑
                        if (isRed(node.left)) {
                            black(node.left);
                        } else if (isRed(node.right)) {
                            black(node.right);
                        }
                    }

                }
            }
        } while (currentNode != node);
    }

红黑树Java版完整实现源码

package com.kevin.datastructures.tree;

import java.util.*;

/**
 * 红黑树五条性质:
 * 1. 根节点必须是黑色
 * 2. 节点的颜色只有红色和黑色
 * 3. NIL节点都是黑色
 * 4. 红色节点的子节点必须是黑色(也就是不能出现两个连续的红色)
 * 5. 从任一节点到其每个叶子(NIL节点)的所有简单路径都包含相同数目的黑色节点(也就是说黑色节点才是对应2-3-4树的高度)
 *
 * Author: Kevin Xie
 * Email: lylwo317@gmail.com
 * @param <E>
 */
public class RedBlackTree<E> {

    private static class RBNode<E> {
        public static int RED = 0;
        public static int BLACK = 1;

        private int color = RED;

        E element;
        RBNode<E> left;
        RBNode<E> right;
        RBNode<E> parent;

        public boolean isLeaf() {
            return left == null && right == null;
        }

        public boolean isChild() {
            return parent != null;
        }

        public boolean isLeftChild() {
            return parent != null && parent.left == this;
        }

        public boolean hasTwoChildren() {
            return left != null && right != null;
        }

        public boolean isRightChild() {
            return parent != null && parent.right == this;
        }

        public RBNode<E> sibling() {//兄弟
            if (isLeftChild()) {
                return parent.right;
            }

            if (isRightChild()) {
                return parent.left;
            }

            return null;
        }

        public RBNode(E element, RBNode<E> parent) {
            this.element = element;
            this.parent = parent;
        }

        @Override
        public String toString() {
            String parentString = "null";
            if (parent != null) {
                parentString = parent.element.toString();
            }
            String nodeStr =  element + "_p(" + parentString + ")";
            String str = "";
            if (color == RED) {
                str = "R_";
            }
            return str + nodeStr;
        }
    }

    protected int size;

    protected RBNode<E> root;

    public boolean isEmpty() {
        return size == 0;
    }

    public int size() {
        return size;
    }

    public void clear() {
        root = null;
        size = 0;
    }

    private Comparator<E> comparator;

    public RedBlackTree() {
    }

    public RedBlackTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    /**
     * 添加一个元素要做一些这些:
     * 1.检查元素是否为空,空就不添加,因为添加空的元素,无法比较大小
     * 2.是否是第一个节点,是的话直接root = newNode
     * 3.不是第一个节点,找到合适的位置(叶子节点),然后添加上去,如果发现有相同的,就直接替换
     *
     * @param element
     */
    public void add(E element){
        if (element == null) {
            throw new IllegalArgumentException("element must not be null");
        }

        if (root == null) {
            root = createNode(element, null);
            afterAdd(root);
            size++;
            return;
        }

        RBNode<E> node = root;
        RBNode<E> parent;
        int compare;
        do {
            compare = compare(element, node.element);
            if (compare == 0) {
                node.element = element;
                return;
            }

            parent = node;
            if (compare < 0) {
                node = node.left;
            } else {//compare > 0
                node = node.right;
            }
        } while (node != null);

        RBNode<E> newNode = createNode(element, parent);
        if (compare < 0) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
        afterAdd(newNode);
        size++;
    }

    /**
     * 找前驱节点,找比当前节点小的所有节点中最大的节点
     *
     * node
     * if(有左子树){
     *    node.left.right.right...(左子树最右(最大)节点)
     * } else {
     *    //当前节点没有左子树
     *    if(当前节点是父节点的右子节点) {//父节点就是前驱
     *       node.parent
     *    } else if(当前节点是父节点的左子节点){//就往祖父节点找,直到找到祖父节点的右子节点为止
     *       node.parent.parent....(parent.right == )
     *    } else { //没有前驱节点
     *       return null //也就是最左边的叶子节点
     *    }
     * }
     *
     * @param node
     * @return
     */
    protected RBNode<E> predecessor(RBNode<E> node) {
        if (node == null) {
            return null;
        }

        if (node.left != null) {
            node = node.left;
            while (node.right != null) {
                node = node.right;
            }
            return node;
        } else {
            while (node.parent != null && node.parent.left == node) {
                node = node.parent;
            }

//            if (node.parent != null) {
//                return node.parent;
//            } else {
//                return null;
//            }

            //简化
            return node.parent;

        }
    }

    /**
     * 后继节点,与前驱相反,改left 为 right 就行
     * @param node
     * @return
     */
    protected RBNode<E> successor(RBNode<E> node) {
        if (node == null) {
            return null;
        }

        if (node.right != null) {
            node = node.right;
            while (node.left != null) {
                node = node.left;
            }
            return node;
        } else {
            while (node.parent != null && node.parent.right == node) {
                node = node.parent;
            }

//            if (node.parent != null) {
//                return node.parent;
//            } else {
//                return null;
//            }

            //简化
            return node.parent;

        }
    }

    public boolean contains(E element) {
        return findNode(element) != null;
    }

    /**
     *  通过,前序,中序,后序,层序遍历等手段找到元素
     */
    private RBNode<E> findNode(E element) {
        if (element == null) {
            return null;
        }
        Stack<RBNode<E>> nodeStack = new Stack<>();
        RBNode<E> node = root;
        do {
            while (node != null) {
                nodeStack.push(node);
                node = node.left;
            }

            if (!nodeStack.isEmpty()) {
                RBNode<E> pop = nodeStack.pop();
                if (pop.element.equals(element)) {
                    return pop;
                }
                node = pop.right;
            }

        } while (node != null || !nodeStack.isEmpty());
        return null;
    }

    /**
     * 根据 element 找到Node,然后删除。
     *
     * @param element
     */
    public void remove(E element) {
        removeNode(findNode(element));
    }

    /**
     * 删除节点
     *
     * 1. 节点的度为2
     *      找到前驱或者后继节点,然后将前驱或者后继节点覆盖当前节点,接着将前驱或者后继节点删除即可
     * 2. 节点的度为1
     *      删除,然后将子节点接到父节点上
     * 3. 节点的度为0
     *      直接删除
     *
     * 综上所述,本质上删除的都是度为0或者1的节点
     *
     */
    private void removeNode(RBNode<E> node) {
        if (node == null) {
            return;
        }
        if (node.left != null && node.right != null) {//度为2的节点
            //如果直接删除,这个时候有两棵子树就好不连接到父节点了。这个时候要连接到父节,就应该在左子树中找到最大的节点(右子树中找到最小节点)
            //来代替原来节点的位置。(这样不但保持了二叉搜索树的性质,还解决了删除后连接到父节点的问题)

            //度为2的节点就找前驱或者后继节点来替代当前节点
            RBNode<E> successor = successor(node);
            //replace
            node.element = successor.element;
            node = successor;//前驱或者后继节点的度必然不会是2
        }

        //叶子节点直接删除
        if (node.isLeaf()) {//度为0的节点
            if (node == root) {
                root = null;
            } else {
                if (node.parent.left == node) {
                    node.parent.left = null;
                } else {
                    node.parent.right = null;
                }
            }
        } else {//度为1的节点
            if (node == root) {//node.parent == null
                if (node.left != null) {
                    root = node.left;
                } else {
                    root = node.right;
                }
                root.parent = null;
            } else {
                RBNode<E> replaceNode;
                if (node.left != null) {
                    replaceNode = node.left;
                } else {
                    replaceNode = node.right;
                }

                RBNode<E> parent = node.parent;
                if (parent.left == node) {
                    parent.left = replaceNode;
                } else {
                    parent.right = replaceNode;
                }

                if (replaceNode != null) {
                    replaceNode.parent = parent;
                }
            }
        }
        afterRemove(node);
        size--;
    }

    private void rotateLeft(RBNode<E> grand) {
        RBNode<E> rootNode = grand.parent;
        RBNode<E> parent = grand.right;
        RBNode<E> subTree = parent.left;

        parent.left = grand;
        grand.right = subTree;

        afterRotate(rootNode, grand, parent, subTree);
    }

    private void rotateRight(RBNode<E> grand) {
        RBNode<E> rootNode = grand.parent;
        RBNode<E> parent = grand.left;
        RBNode<E> subTree = parent.right;

        parent.right = grand;
        grand.left = subTree;

        afterRotate(rootNode, grand, parent, subTree);
    }

    private void afterRotate(RBNode<E> rootNode, RBNode<E> grand, RBNode<E> parent, RBNode<E> subTree) {
        if (grand.isLeftChild()) {
            rootNode.left = parent;
        } else if (grand.isRightChild()) {
            rootNode.right = parent;
        } else {//根节点
            root = parent;
        }

        //update node.parent
        if (subTree != null) {
            subTree.parent = grand;
        }
        grand.parent = parent;
        parent.parent = rootNode;
    }

    /**
     * 新添加的节点默认都是--红色
     * <pre>
     * 1. 添加到根节点:
     *      1.1 直接添加,然后染黑
     * 2. 添加到非根节点
     *      2.1 parent是黑色
     *          直接添加
     *      2.2 parent是红色
     *          i. 叔父节点是黑色或者空(1.空,。2.黑色,)
     *             a.空(说明这个是在叶子节点添加的情况)
     *               grand节点涂红
     *               按照节点的当前的类型进行旋转(分别有四种类型LL,LR,RR,RL)
     *               旋转后grand的sibling涂红
     *             b.黑色(说明是添加节点导致上溢的情况,出现了叔父节点为黑色的情况。对应的2-3-4树,不在同一层,所以跟空的情况一样处理)。
     *          ii.叔父节点是红色(添加的节点,将会导致该层溢出)
     *             (父节点 & 叔父节点) 涂黑
     *             grand涂红 当做新添加节点处理
     * </pre>
     *
     * @param node
     */
    private void afterAdd(RBNode<E> node) {
        RBNode<E> currentNode;
        do {
            currentNode = node;
            if (node.parent == null) {//添加到根节点,或者上溢到根节点
                root = node;
                black(node);
            } else {//非根节点
                if (isRed(node.parent)) {//parent 是红色
                    RBNode<E> grand = node.parent.parent;
                    RBNode<E> parent = node.parent;
                    if (isBlack(parent.sibling())) {//叔父节点是空或者黑色。没有溢出
                        //旋转操作
                        red(grand);
                        if (parent.isLeftChild()) {//L
                            if (node.isLeftChild()) {//LL
                                black(parent);
                            } else {//LR
                                black(node);
                                rotateLeft(parent);
                            }
                            rotateRight(grand);
                        } else {//R
                            if (node.isRightChild()) {//RR
                                black(parent);
                            } else {//RL
                                black(node);
                                rotateRight(parent);
                            }
                            rotateLeft(grand);
                        }
                    } else {//叔父节点红色,溢出
                        // 红 <- 黑 -> 红 -> new红, new红 <- 红 <- 黑 -> 红
                        red(grand);
                        black(parent.sibling());
                        black(parent);
                        node = grand;//不使用递归
                    }
                } else {//parent 是黑色
                    //直接添加,不用调整修复。就满足了红黑树的性质
                }
            }
        } while (currentNode != node);
    }

    /**
     * <pre>
     *          ┌────────60───────┐
     *          │                 │
     *      ┌─R_55─┐           ┌─R_65─┐
     *      │      │           │      │
     *   ┌─50─┐    57─┐     ┌─63      70
     *   │    │       │     │
     * R_40  R_53    R_58 R_62
     *
     *
     * 可能会删除的节点有:
     * 1. 根节点:
     *      1.1 下溢导致的根节点删除
     *           ┌─25─┐
     *           │    │
     *          20    30
     *
     *          remove: 30(导致25下溢)
     *
     *           ┌─25
     *           │
     *         R_20
     *
     *          由于删除30会使得25产生下溢,但是下溢的是根节点,所以不需要处理。对应的2-3-4树的高度减1
     *
     *      1.2 非下溢导致的
     *          a.
     *           ┌─25
     *           │
     *         R_20
     *
     *          remove: 25
     *
     *          20
     *
     *          涂黑20(如果有红色子节点,要记得涂黑)
     *
     *          b.
     *          20
     *
     *          remove: 20
     *
     *          只有一个根节点不需要涂黑
     * 2. 非根节点:
     *                 ┌────────60───────┐
     *                 │                 │
     *             ┌─R_55─┐           ┌─R_65─┐
     *             │      │           │      │
     *          ┌─50─┐    57─┐     ┌─63      70
     *          │    │       │     │
     *        R_40  R_53    R_58 R_62
     *      2.1 红色叶子节点(40,53,58,62)
     *          直接删除即可
     *
     *      2.2 黑色节点
     *          2.2.1 黑色非叶子节点(57,63)-- 将红色叶子节点涂黑
     *
     *          2.2.2 黑色叶子节点(70(叶子),由于下溢导致被删除的黑色节点,也是需要当做叶子节点处理。)
     *          (根据红黑树的性质5,可知黑色节点对应的是2-3-4树的层级,所以必定还有其它黑色叶子节点在同一层级)
     *
     *              a.有黑色兄弟(在对应的2-3-4树中,被删除节点和他的兄弟在同一层)
     *
     *                i. 黑色兄弟的子节点是红色。向兄弟借
     *                  旋转让兄弟或者兄弟子节点替换parent,parent替换被删除叶子节点。使得红黑树对应的 2-3-4树最后一层保持不变
     *
     *                ii.黑色兄弟的子节点是黑色(空子节点和黑色字节点都当做黑色处理,因为无论哪一种,都无法借出)。向父亲借
     *                  将兄弟染红(向父节点借)
     *                    父亲是红色,就染黑
     *                    父亲是黑色,就下溢处理(这里特别要注意的地方是,下溢后,父节点就相当于新的被删除的节点,重新走afterRemove)
     *
     *              b.有红色兄弟(说明兄弟必然有黑色儿子)(在对应的2-3-4树中,被删除节点和他的兄弟在不在同一层,但是兄弟的黑儿子和被删除节点在同一层)
     *                       ┌──────77─────┐
     *                       │             │
     *                  ┌───55────┐      ┌─80─┐
     *                  │         │      │    │
     *                 17─┐    ┌─R_57─┐ 79 ┌─R_88─┐
     *                    │    │      │    │      │
     *                   R_29 56      62  84      93
     *                 remove: 77(实际删除的是后继节点79)
     *                       ┌──────79─────┐
     *                       │             │
     *                  ┌───55────┐      ┌─88─┐
     *                  │         │      │    │
     *                 17─┐    ┌─R_57─┐ 80─┐  93
     *                    │    │      │    │
     *                   R_29 56      62  R_84
     *                i.通过旋转将兄弟的黑儿子变成兄弟节点
     *                  按照a的情况处理
     *
     * 处理溢出的时候,防止将黑色节点的红色子节点染黑,导致不平衡。
     * </pre>
     *
     * @param node
     */
    private void afterRemove(RBNode<E> node) {
        RBNode<E> currentNode;
        boolean underflow = false;
        do {
            currentNode = node;
            if (node.parent == null) {//删除的是根节点
                if (underflow) {//下溢不做处理
                    underflow = false;
                } else {//删除度为1或者0的根节点导致的。如果删除的是度为1的根节点,这个时候要将根节点的红色子节点染黑
                    if (isRed(node.left)) {
                        black(node.left);
                    }
                    if (isRed(node.right)) {
                        black(node.right);
                    }
                }
            } else {//删除的是非根节点
                if (isRed(node)) {//删除的是红色节点,不用调整,因为不会影响红黑树的性质

                } else {//删除的是黑色节点。
                    // 删除非根节点的黑色子节点,被删除的节点必定有兄弟(性质5)
                    // 借的顺序是    兄弟 --> 兄弟的儿子 --> 父亲

                    //1. 黑(黑色叶子节点,下溢的黑色节点也要当做黑色叶子节点处理)
                    if (underflow || isBlack(node.left) && isBlack(node.right)) {
                        underflow = false;
                        //需要找兄弟节点借,如果兄弟节点没有,就找父节点借,节点会下溢
                        RBNode<E> parent = node.parent;
                        RBNode<E> sibling;
                        if (parent.hasTwoChildren()) {//在下溢的时候
                            sibling = node.isLeftChild() ? parent.right : parent.left;
                        } else {//在正常删除节点的时候,必然有一个子节点
                            sibling = parent.left == null ? parent.right : parent.left;
                        }

                        //兄弟节点是红色(兄弟必定有黑儿子),将兄弟的黑儿子变成兄弟
                        if (isRed(sibling)) {
                            red(parent);
                            black(sibling);
                            if (sibling.isLeftChild()) {
                                rotateRight(parent);
                                sibling = parent.left;
                            } else {
                                rotateLeft(parent);
                                sibling = parent.right;
                            }
                        }

                        //兄弟节点都是黑色的
                        if (isRed(sibling.left) || isRed(sibling.right)) {//兄弟有红色子节点,兄弟可以借
                            if (sibling.isLeftChild()) {//L
                                //先看左边有没有红色子节点,有的话。优先按照LL处理
                                //没有的话就是LR。通过左旋可以转成LL
                                if (isBlack(sibling.left)) {//LR
                                    rotateLeft(sibling);
                                    sibling = sibling.parent;
                                }

                                //此时都转成了LL情况,统一按照LL情况处理

                                if (colorOf(parent) == RBNode.BLACK) {
                                    black(sibling);
                                } else {
                                    red(sibling);
                                }
                                rotateRight(parent);
                            } else {
                                //RL转成RR来处理
                                if (isBlack(sibling.right)) {//RL
                                    rotateRight(sibling);
                                    sibling = sibling.parent;
                                }

                                //此时都转成了RR情况,统一按照RR情况处理

                                if (colorOf(parent) == RBNode.BLACK) {
                                    black(sibling);
                                } else {
                                    red(sibling);
                                }
                                rotateLeft(parent);
                            }
                            black(parent);
                            black(parent.sibling());
                        } else {//兄弟只有黑色子节点或者兄弟没有子节点。兄弟无法借出,需要从父节点借(父节点是黑色会产生下溢)
                            if (isRed(parent)) {
                                black(parent);
                                red(sibling);
                            } else {
                                red(sibling);
                                node = parent;
                                //实际节点是红<-黑,黑色节点下溢了。需要再执行afterRemove,但是不能让它被当有红色子节点的黑色节点处理
                                //而是当做被没有子节点的黑色节点处理(黑)
                                underflow = true;
                            }
                        }
                    } else {//1. 红<-黑   2. 黑->红      度为1的黑色节点
                        //将红色子节点染黑
                        if (isRed(node.left)) {
                            black(node.left);
                        } else if (isRed(node.right)) {
                            black(node.right);
                        }
                    }

                }
            }
        } while (currentNode != node);
    }

    @SuppressWarnings("unchecked")
    private int compare(E e1, E e2) {
        if (comparator != null) {
            return comparator.compare(e1, e2);
        }

        return ((Comparable<E>) e1).compareTo(e2);
    }

    private int colorOf(RBNode<E> node) {
        return node == null ? RBNode.BLACK : node.color;
    }

    private boolean isBlack(RBNode<E> node) {
        return colorOf(node) == RBNode.BLACK;
    }

    private boolean isRed(RBNode<E> node) {
        return colorOf(node) == RBNode.RED;
    }

    private void red(RBNode<E> node) {
        if (node != null) {
            node.color = RBNode.RED;
        }
    }

    private void black(RBNode<E> node) {
        if (node != null) {
            node.color = RBNode.BLACK;
        }
    }

    /**
     * 检查当前红黑树是否符合红黑树的第1,4,5点性质
     */
    public void checkRBTreeProperties() {
        if (!isBlack(root)) {//不符合性质(1)
            throw new IllegalStateException("root node color isn't black. root = " + root);
        }

        if (root == null) {
            return;
        }

        //黑节点数量
        int rootNodeBlackHeight = 0;

        RBNode<E> calHeightNode = root;
        while (calHeightNode != null) {
            if (isBlack(calHeightNode)) {
                rootNodeBlackHeight++;
            }
            calHeightNode = calHeightNode.left;
        }
        rootNodeBlackHeight++;//加入NIL节点到统计的总数中

        Queue<RBNode<E>> nodeQueue = new LinkedList<>();
        nodeQueue.offer(root);
        while (!nodeQueue.isEmpty()) {

            RBNode<E> node = nodeQueue.poll();

            if (!node.hasTwoChildren()) {//计算度为1或者度为0的节点到根节点的黑色节点数量是否等于rootNodeBlackHeight,以及是否存在两个连续红色节点
                RBNode<E> iteratorNode = node;
                int blackCount = 0;
                int preColor = RBNode.BLACK;
                while (iteratorNode != null) {//往根节点遍历,看是否存在连续两个红色节点
                    if (isBlack(iteratorNode)) {
                        blackCount++;//统计黑色节点数量
                    } else {//当前遍历的节点是红色
                        if (preColor == RBNode.RED) {//前一个节点也是红色,不满足性质(4)
                            throw new IllegalStateException("This is not a red-black tree. Because has continues reb node at " + node);
                        }
                    }
                    preColor = iteratorNode.color;
                    iteratorNode = iteratorNode.parent;
                }
                blackCount++;//加入NIL节点到统计的总数中

                if (blackCount != rootNodeBlackHeight) {//黑色节点数量不相等,不满足性质(5)
                    throw new IllegalStateException("This is not a red-black tree. " +
                            "Because blackCount = " + blackCount + " rootNodeBlackHeight = " + rootNodeBlackHeight + " at " + node);
                }
            }

            if (node.left != null) {
                nodeQueue.offer(node.left);
            }

            if (node.right != null) {
                nodeQueue.offer(node.right);
            }
        }
    }

    public RBNode<E> createNode(E element, RBNode<E> parent) {
        return new RBNode<>(element, parent);
    }

    public static void main(String[] args) {
        RedBlackTree<Integer> rbtree = new RedBlackTree<>();
		for (int i = 0; i < 10; i++) {
			Random ran = new Random();
			HashSet<Integer> hs = new HashSet<>();
			for (;;) {
				int tmp = ran.nextInt(1000)+1;
				hs.add(tmp);
				if(hs.size() == 100) break;
			}

			for (Integer datum : hs.toArray(new Integer[0])) {
				System.out.println("add: " + datum);
				rbtree.add(datum);
				rbtree.checkRBTreeProperties();
			}

			for (Integer datum : hs.toArray(new Integer[0])) {
				System.out.println("remove: " + datum);
				rbtree.remove(datum);
                rbtree.checkRBTreeProperties();
			}

		}
    }
}
Docker是一种流行的容器化技术,通过轻量级、隔离性强的容器来运行应用程序。下面我将通过十你深入理解Docker容器镜像。 1. 第一展示了Docker容器镜像的关系。镜像是Docker的基础组件,它是一个只读的模板,包了运行应用程序所需的所有文件配置。容器是从镜像创建的实例,它具有自己的文件系统、网络进程空间。 2. 第二展示了Docker容器的隔离性。每个容器都有自己的文件系统,这意味着容器之间的文件互不干扰。此外,每个容器还有自己的网络进程空间,使得容器之间的网络进程相互隔离。 3. 第三展示了Docker镜像容器的可移植性。镜像可以在不同的主机上运行,只需在目标主机上安装Docker引擎即可。容器也可以很容易地在不同的主机上迁移,只需将镜像传输到目标主机并在其上创建容器。 4. 第四展示了Docker容器的快速启动。由于Docker容器与主机共享操作系统内核,启动容器只需几秒钟的时间。这使得快速部署扩展应用程序成为可能。 5. 第五展示了Docker容器的可重复性。通过使用Dockerfile定义镜像构建规则,可以确保每次构建的镜像都是相同的。这样,可以消除由于环境差异导致的应用程序运行问题。 6. 第六展示了Docker容器的资源隔离性。Docker引擎可以为每个容器分配一定数量的CPU、内存磁盘空间,确保容器之间的资源不会互相干扰。 7. 第七展示了Docker容器的可扩展性。通过使用Docker Swarm或Kubernetes等容器编排工具,可以在多个主机上运行管理大规模的容器群集。 8. 第八展示了Docker镜像的分层结构。镜像由多个只读层组成,每个层都包一个或多个文件。这种分层结构使得镜像的存储传输变得高效。 9. 第九展示了Docker容器的生命周期。容器可以通过创建、启动、停止销毁等命令来管理。这使得容器的维护管理变得简单。 10. 第十展示了Docker容器的应用场景。Docker容器广泛应用于开发、测试、部署运维等领域。它可以提供一致的开发运行环境,简化了应用程序的管理交付过程。 通过这十,希望能让大家更深入地理解Docker容器镜像的概念、特性应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值