红黑树理论详解与Java实现

文章目录

  • 基本定义
  • 五大性质
  • 红黑树和2-3-4树的关系
  • 红黑树和2-3-4树各结点对应关系
  • 添加结点到红黑树
    • 注意事项
    • 添加的所有情况
  • 添加导致不平衡
    • 叔父节点不是红色节点(祖父节点为红色)
      • 添加不平衡LL/RR
      • 添加不平衡LR/RL
    • 叔父节点是红色节点(祖父节点为黑色)
  • 删除
    • 删除红色节点
    • 删除黑色节点
      • 删除黑色叶子节点——情况一
      • 删除黑色叶子节点——情况二
      • 删除黑色叶子节点——情况三
      • 删除黑色叶子节点——情况四
  • 红黑树与AVL(平衡二叉树)树比较
  • 红黑树与B树B+树比较
  • 完整的Java测试代码
    • RedBlackTree
    • BinaryTreeInfo
    • BinaryTrees
    • InorderPrinter
    • LevelOrderPrinter
    • Printer
    • Strings
  • 总结

基本定义

红黑树是一颗二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是红色或者黑色
通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。

五大性质

1、结点必须是红色或者黑色。
2、根节点是黑色。
3、叶子结点(外部结点、空结点,不是传统上认为的那种叶子结点,如图中的那些NIL结点)都是黑色
4、红色结点的子结点都是黑色
于是有推论:
  4.1、红色结点的父结点都是黑色
  4.2、从根结点到叶子结点的所有路径上不能有两个连续的红色结点
5、从任一结点到叶子结点(外部结点、空结点,不是传统上认为的那种叶子结点,如图中的那些NIL结点)的所有路径都包含相同数目的黑色结点
如图所示:
在这里插入图片描述

红黑树和2-3-4树的关系

在这里插入图片描述
红黑树和4阶B树(2-3-4树)具有等价性
黑色节点与它的红色子节点融合在一起,形成一个B树节点
红黑树的黑色节点个数与4阶B树的节点总个数相等
在这里插入图片描述

红黑树和2-3-4树各结点对应关系

红黑红、黑红、红黑、黑
在这里插入图片描述

添加结点到红黑树

注意事项

一般新添加的节点默认为红色,这样对红黑树的性质影响最小(性质1、2、3、5都满足,性质4不一定)
如果新添加的节点是根节点,染成黑色即可

添加的所有情况

添加结点到红黑树总共有12中情况;

有4种情况满足红黑树的性质,父节点为黑色节点,因此不需要做任何处理。如下图所示的4种紫红色节点添加情况
在这里插入图片描述

有8种情况不满足红黑树的性质,父节点为红色节点(但其实可以归纳为3种情况)。如下图所示的8种紫红色节点添加情况。
在这里插入图片描述

添加导致不平衡

添加结点导致红黑树出现不平衡的情况。

叔父节点不是红色节点(祖父节点为红色)

添加不平衡LL/RR

判断条件:叔父节点不是红色节点
父节点是祖父节点的左孩子,自己是父节点的左孩子(LL)
父节点是祖父节点的右孩子,自己是父节点的右孩子(RR)
如何恢复平衡:
1、父节点染成黑色,祖父节点染成红色
2、对祖父节点进行旋转操作(LL是右旋,RR是左旋)

在这里插入图片描述

添加不平衡LR/RL

判断条件:叔父节点不是红色节点
父节点是祖父节点的左孩子,自己是父节点的右孩子(LR)
父节点是祖父节点的右孩子,自己是父节点的左孩子(RL)
如何恢复平衡:
1、把自己染成黑色,祖父节点染成红色
2、进行两次旋转,第一次旋转是转换成LL/RR情况,第二次旋转恢复平衡
2.1、LR:先按照父节点左旋,变成LL,再按照祖父节点右旋
2.2、RL:先按照父节点右旋,变成RR,再按照祖父节点左旋
在这里插入图片描述

叔父节点是红色节点(祖父节点为黑色)

判断条件:叔父节点为红色
如何恢复平衡:
1、父节点、叔父节点都染成黑色
2、祖父节点染成红色,并把祖父节点当成新添加的节点,继续处理
2.1、如果祖父节点染成红色没有引起双红现象,则处理结束
2.2、如果祖父节点染成红色也导致双红现象,则继续按照是LL/RR,LR/RL,还是叔父节点为红色的三种情况处理,最差情况是处理直到根节点,把根节点染成了红色,这个时候只需要把根节点染成黑色即可。
在这里插入图片描述

删除

B树中,最后真正被删除的元素都在叶子节点中。详细见B树(B-tree、B-树)理论详解

在这里插入图片描述
删除节点就是上面图的4种情况。

删除红色节点

直接删除,无需任何调整

删除黑色节点

有3种情况
1、拥有两个红色子节点的黑色节点不可能直接被删除,因为会找它的红色子节点替代删除,因此这种情况无需考虑
2、拥有1个红色子节点的黑色节点 (删除黑色节点后,把替代的红色子节点染成黑色即可)
3、黑色叶子节点 (情况比较复杂)
在这里插入图片描述

删除黑色叶子节点——情况一

如果兄弟节点是红色节点,
1、把兄弟节点染成黑色,父节点染成红色,对父节点进行旋转2、此时兄弟节点变成黑色,可以继续按照情况2,3,4进行处理
在这里插入图片描述
如图所示,40结点的兄弟结点是20,父结点是30。

删除黑色叶子节点——情况二

黑色兄弟节点没有一个红色子节点,
1、如果父节点是红色,把兄弟节点染成红色,父节点染成黑色,完成平衡维护。
2、如果父节点是黑色,把兄弟节点染成红色,把父节点当成待删除的节点,向上递归或循环处理(依然有情况1,2,3,4)。
在这里插入图片描述

删除黑色叶子节点——情况三

兄弟节点是左节点,黑色兄弟节点的左孩子是黑色节点,为LR场景,需要先转变为LL,方便后面的平衡旋转。
1、把兄弟节点的右孩子设为黑色,兄弟节点设为红色,对兄弟节点进行左旋,确保兄弟节点有一个红色左孩子,此时变成情况4。
在这里插入图片描述

删除黑色叶子节点——情况四

兄弟节点是左节点,兄弟节点的左孩子是红色节点,LL场景,
1、把兄弟节点的颜色设置为父节点的颜色,父节点和兄弟节点的左孩子都设置为黑色,
对父节点进行右旋,恢复平衡。
在这里插入图片描述

红黑树与AVL(平衡二叉树)树比较

AVL树的平衡标准比较严格:每个左右子树的高度差不超过1。
红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的两倍。
相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,总体性能要优于 AVL树。
红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树。

红黑树与B树B+树比较

红黑树的分叉少,适合在内存中使用,如果在硬盘中使用的话,当数据量大的时候,红黑树的层高比较高,会带来比较多的磁盘IO,影响查询性能,比如说100万的数据量,用红黑树的话,大概是20层的层高,会有20次磁盘IO。
B树和B+树的分叉比较多,B树分叉一般能到两三百,B+树分叉多的能到上千,所以B树和B+树适合磁盘存储,100万的数据量,一般3层树高即可搞定,只有3次磁盘IO,实际中数据库一般把根节点存放在内存中,所以其实只有两次IO。

完整的Java测试代码

RedBlackTree

package redblacktree;

public class RedBlackTree implements BinaryTreeInfo {
    //红黑直接用布尔变量定义
    private static final boolean RED = false;
    private static final boolean BLACK = true;

    //初始化一个唯一的叶结点
    private final RBNode nil = new RBNode();

    //根结点初始化为nil
    private RBNode root = nil;

    public RBNode getRoot() {
        return root;
    }

    public void setRoot(RBNode root) {
        this.root = root;
    }

    public RedBlackTree() {
        this.root = nil;
    }

    public RedBlackTree(RBNode root) {
        this.root = root;
    }

    //前序遍历二叉树
    public void preorderTreeWalk(RBNode x) {
        if(x != nil) {
            System.out.print(x.key + " ");
            preorderTreeWalk(x.left);
            preorderTreeWalk(x.right);
        }
    }

    //中序遍历二叉树
    public void inorderTreeWalk(RBNode x) {
        if(x != nil) {
            inorderTreeWalk(x.left);
            System.out.print(x.key + " ");
            inorderTreeWalk(x.right);
        }
    }

    //后序遍历二叉树
    public void postorderTreeWalk(RBNode x) {
        if(x != nil) {
            postorderTreeWalk(x.left);
            postorderTreeWalk(x.right);
            System.out.print(x.key + " ");
        }
    }

    //在二叉搜索树中查询某个值,递归版本
    public RBNode treeSearch(RBNode x, Integer k) {
        if(x == nil || k == x.key) {
            return x;
        }

        if(k < x.key) {
            return treeSearch(x.left, k);
        } else {
            return treeSearch(x.right, k);
        }
    }

    //在二叉搜索树中查询某个值,循环版本
    public RBNode iterativeTreeSearch(RBNode x, Integer k) {
        while(x != nil && k != x.key) {
            if(k < x.key) {
                x = x.left;
            } else {
                x = x.right;
            }
        }

        return x;
    }

    //在二叉搜索树中查找包含最小值的节点
    public RBNode treeMinimum(RBNode x) {
        while (x.left != nil) {
            x = x.left;
        }

        return x;
    }

    //在二叉搜索树中查找包含最大值的节点
    public RBNode treeMaximum(RBNode x) {
        while (x.right != nil) {
            x = x.right;
        }

        return x;
    }

    //查找节点x的后继节点
    public RBNode treeSuccessor(RBNode x) {
        if(x.right != nil) {  //x的右子树不为空,找右子树的最小值
            return treeMinimum(x.right);
        }

        RBNode y = x.parent;
        while(y != nil && x == y.right) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    //查找节点x的前驱节点
    public RBNode treePredeceessor(RBNode x) {
        if(x.left != nil) {
            return treeMaximum(x.left);
        }

        RBNode y = x.parent;
        while(y != nil && x == y.left) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    /**
     * 围绕x左旋
     *       xp                    xp
     *      /                     /
     *     x                     xr
     *    / \          ==>      / \
     *  xl  xr                 x   rr
     *     / \                / \
     *    rl  rr             xl  rl
     *
     * @param t,x
     */
    void leftRotate(RedBlackTree t, RBNode x) {
        RBNode y = x.right;   //让y等于x的右子节点
        x.right = y.left;    //将y的左子树转成x的右子树
        if(y.left != t.nil) {  //假如y的左孩子不为空,将y的左孩子的父亲设置为x
            y.left.parent = x;
        }
        y.parent = x.parent;  //将y的父亲设置为x的父亲
        if(x.parent == t.nil) {
            t.root = y;  //考虑x原来为根节点的情况
        } else if (x.parent.left == x) {
            x.parent.left = y;
        } else {
            x.parent.right = y;
        }

        y.left = x;
        x.parent = y;
    }

    /**
     * 围绕x右旋
     *    xp                xp
     *     \                 \
     *      x                xl
     *     / \      =>      /  \
     *   xl  xr            ll   x
     *   / \                   / \
     *  ll lr                 lr  xr
     *
     * @param t,x
     */
    void rightRotate(RedBlackTree t, RBNode x) {
        RBNode y = x.left;   //让y等于x的左子节点
        x.left = y.right;    //将y的右子树转成x的左子树
        if(y.right != t.nil) {  //假如y的右孩子不为空,将y的右孩子的父亲设置为x
            y.right.parent = x;
        }
        y.parent = x.parent;  //将y的父亲设置为x的父亲
        if(x.parent == t.nil) {
            t.root = y;  //考虑x原来为根节点的情况
        } else if (x.parent.left == x) {
            x.parent.left = y;
        } else {
            x.parent.right = y;
        }

        y.right = x;
        x.parent = y;
    }


    //红黑树的插入操作
    public void RBInsert(RedBlackTree t, RBNode z) {
        RBNode y = t.nil;
        RBNode x = t.root;
        while(x != t.nil) {
            y = x;
            if(z.key < x.key) {
                x = x.left;
            } else {
                x = x.right;
            }
        }

        z.parent = y;
        if(y == t.nil) {  //说明现在是棵空树
            t.root = z;
        } else if (z.key < y.key) {
            y.left = z;
        } else {
            y.right = z;
        }

        z.left = t.nil;
        z.right = t.nil;
        z.color = RED;
        rbInsertFixup(t, z);
    }

    //插入节点后维护红黑树性质的过程
    public void rbInsertFixup(RedBlackTree t, RBNode z) {
        while (z.parent.color == RED) {
            if(z.parent == z.parent.parent.left) {  //z的父节点是z的祖父节点的左孩子
                RBNode y = z.parent.parent.right;   //让y成为z的叔叔节点
                if (y.color == RED) {
                    /*
                     * z的父亲和叔叔节点都是红色,
                     * 根据红黑树性质,z的祖父一定是黑色
                     * 所以这时候,可以把z的祖父设为红色,
                     * z的父亲和叔叔都设为黑色,但这可能
                     * 导致z的祖父产生双红节点的问题,这
                     * 时候把z设置成z的祖父,继续向上递归
                     */
                    z.parent.color = BLACK;
                    y.color = BLACK;
                    z.parent.parent.color = RED;
                    z = z.parent.parent;
                } else {
                    if (z == z.parent.right) {
                        /*
                         * z的父亲是左节点,z是右节点,
                         * 满足LR不平衡,需要对z的父亲进行左旋,
                         * 转变成LL不平衡
                         */
                        z = z.parent;
                        leftRotate(t, z);
                    }
                    /*
                     * z的父亲是左节点,z也是左节点,
                     * 满足LL不平衡,把z的父亲设为黑色,
                     * z的祖父设为红色,对z进行右旋,
                     * 即可满足平衡
                     */
                    z.parent.color = BLACK;
                    z.parent.parent.color = RED;
                    rightRotate(t, z.parent.parent);
                }
            } else {
                RBNode y = z.parent.parent.left;   //让y成为z的叔叔节点
                if (y.color == RED) {
                    /*
                     * z的父亲和叔叔节点都是红色,
                     * 根据红黑树性质,z的祖父一定是黑色
                     * 所以这时候,可以把z的祖父设为红色,
                     * z的父亲和叔叔都设为黑色,但这可能
                     * 导致z的祖父产生双红节点的问题,这
                     * 时候把z设置成z的祖父,继续向上递归
                     */
                    z.parent.color = BLACK;
                    y.color = BLACK;
                    z.parent.parent.color = RED;
                    z = z.parent.parent;
                } else {
                    if (z == z.parent.left) {
                        /*
                         * z的父亲是右节点,z是左节点,
                         * 满足RL不平衡,需要对z的父亲进行右旋,
                         * 转变成RR不平衡
                         */
                        z = z.parent;
                        rightRotate(t, z);
                    }
                    /*
                     * z的父亲是右节点,z也是右节点,
                     * 满足RR不平衡,把z的父亲设为黑色,
                     * z的祖父设为红色,对z进行左旋,
                     * 即可满足平衡
                     */
                    z.parent.color = BLACK;
                    z.parent.parent.color = RED;
                    leftRotate(t, z.parent.parent);
                }
            }
        }

        t.root.color = BLACK;
    }

    public void rbTransplant(RedBlackTree t, RBNode u, RBNode v) {
        if(u.parent == t.nil) {
            t.root = v;
        } else if(u == u.parent.left) {
            u.parent.left = v;
        } else {
            u.parent.right = v;
        }

        v.parent = u.parent;
    }

    //删除节点操作
    public void rbDelete(RedBlackTree t, RBNode z) {
        RBNode x;
        RBNode y = z;
        boolean yOriginalColor = y.color;
        if (z.left == t.nil) {
            x = z.right;
            //z的左孩子为空,用z的右孩子替换z,此时z的右孩子可以为空,也可以不为空
            rbTransplant(t, z, z.right);
        } else if (z.right == t.nil) {
            x = z.left;
            //z仅有一个孩子且其为左孩子,用z的左孩子替换z
            rbTransplant(t, z , z.left);
        } else {
            //z有两个孩子,找z的后继节点
            y = treeMinimum(z.right);

            yOriginalColor = y.color;
            x = y.right;
            if(y.parent == z) {
                x.parent = y;
            } else {
                //用以y的右孩子为根的树代替以y为根的树
                rbTransplant(t, y, y.right);
                y.right = z.right;
                y.right.parent = y;
            }

            //用以y为根的树代替以z为根的树
            rbTransplant(t, z, y);
            y.left = z.left;
            y.left.parent = y;
        }

        //如果删除的为黑节点,需要修复平衡
        if (yOriginalColor == BLACK) {
//            rbDeleteFixup(t, x);
            fixAfterDelete(t, x);
        }
    }

    //删除修复算法导论版代码,用红黑树解释
    public void rbDeleteFixup(RedBlackTree t, RBNode x) {
        while(x != t.root && x.color == BLACK) {
            //x是左孩子
            if(x == x.parent.left) {
                //w为兄弟节点
                RBNode w = x.parent.right;
                //w为红色,要旋转成黑色,方便后续操作
                if(w.color == RED) {
                    w.color = BLACK;
                    x.parent.color = RED;
                    leftRotate(t, x.parent);
                    //此时w为黑色
                    w = x.parent.right;
                }

                /*
                 * w是黑色,w的两个孩子都是黑色,
                 * 没办法通过旋转恢复平衡,只能把w拉下水,设为红色,
                 * x变成x的父节点,向上递归
                 */
                if(w.left.color == BLACK && w.right.color == BLACK) {
                    w.color = RED;
                    x = x.parent;
                } else {
                    /*
                     * w是黑色,w的右孩子是黑色,
                     * w的左孩子是红色,为RL场景
                     * 此时把w的左孩子设为黑色,
                     * w设为红色,对w进行右旋,
                     * 确保w的右孩子为红色,此时为RR场景
                     */
                    if(w.right.color == BLACK) {
                        w.left.color = BLACK;
                        w.color = RED;
                        rightRotate(t, w);
                        w = x.parent.right;
                    }

                    /*
                     * 此时一定是w为黑色,w的右孩子为红色,
                     * w设置为x父节点的颜色,
                     * x父节点设为黑色,
                     * w的右孩子设置为黑色,
                     * 这样w路径上多出来一个黑节点,
                     * 对x的父节点进行左旋,
                     * 相当于把原来w路径上多出来的黑节点补充到x的路径上,
                     * 这样就填补了原来删除的黑节点,恢复平衡
                     */
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.right.color = BLACK;
                    leftRotate(t, x.parent);
                    /*
                     * 这里已经恢复平衡,
                     * 所以直接把x设置为根节点结束循环
                     */
                    x = t.root;
                }
            } else {
                //x是右孩子,w是兄弟节点
                RBNode w = x.parent.left;
                //w为红色,要旋转成黑色,方便后续操作
                if(w.color == RED) {
                    w.color = BLACK;
                    x.parent.color = RED;
                    rightRotate(t, x.parent);
                    //此时w为黑色
                    w = x.parent.right;
                }

                /*
                 * w是黑色,w的两个孩子都是黑色,
                 * 没办法通过旋转恢复平衡,只能把w拉下水,设为红色,
                 * x变成x的父节点,向上递归
                 */
                if(w.right.color == BLACK && w.left.color == BLACK) {
                    w.color = RED;
                    x = x.parent;
                } else {
                    /*
                     * w是黑色,w的左孩子是黑色,
                     * w的右孩子是红色,为LR场景
                     * 此时把w的右孩子设为黑色,
                     * w设为红色,对w进行左旋,
                     * 确保w的左孩子一定为红色,此时为LL场景
                     */
                    if(w.left.color == BLACK) {
                        w.right.color = BLACK;
                        w.color = RED;
                        leftRotate(t, w);
                        w = x.parent.left;
                    }

                    /*
                     * 此时一定是w为黑色,w的左孩子为红色,
                     * w设置为x父节点的颜色,
                     * x父节点设为黑色,
                     * w的左孩子设置为黑色,
                     * 这样w路径上多出来一个黑节点,
                     * 对x的父节点进行右旋,
                     * 相当于把原来w路径上多出来的黑节点补充到x的路径上,
                     * 这样就填补了原来删除的黑节点,恢复平衡
                     */
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.left.color = BLACK;
                    rightRotate(t, x.parent);
                    /*
                     * 这里已经恢复平衡,
                     * 所以直接把x设置为根节点结束循环
                     */
                    x = t.root;
                }
            }
        }
        /*
         * 循环结束,要么x是根节点,
         * 要么x是红色节点,
         * 直接把x设置成黑色,即可恢复平衡
         */
        x.color = BLACK;
    }

    /**
     * 根据2-3-4树解释的红黑树删除
     * 删除后的调整处理
     * 1.情况1:自己能搞定,对应的叶子节点是3节点或者4节点
     * 2.情况2:自己搞不定,需要兄弟节点借,但是兄弟节点不能直接借,找父亲节点借,父亲下来,然后兄弟节点找一个人代替父亲当家
     * 3.情况3:跟兄弟节点借,兄弟也没有
     * @param t,x
     */
    public void fixAfterDelete(RedBlackTree t, RBNode x){
        while(x != t.root && x.color == BLACK){
            //x是左孩子的情况
            if(x == x.parent.left) {
                //兄弟节点
                RBNode w = x.parent.right;

                //判断此时兄弟节点是否是真正的兄弟节点,只有黑色节点才是
                if(w.color == RED){
                    w.color = BLACK;
                    x.parent.color = RED;
                    leftRotate(t, x.parent);
                    //找到真正的兄弟节点
                    w = x.parent.right;
                }
                //情况三,找兄弟借,兄弟没得借
                if(w.left.color == BLACK && w.right.color == BLACK) {
                    //把兄弟拉下水,设为红色,向上递归
                    w.color = RED;
                    x = x.parent;
                }
                //情况二,找兄弟借,兄弟有的借
                else{
                    //确保w的右孩子是红色
                    if(w.right.color == BLACK) {
                        w.left.color = BLACK;
                        w.color = RED;
                        rightRotate(t, w);
                        w = x.parent.right;
                    }

                    /*
                     * 此时一定是w为黑色,w的右孩子为红色,
                     * w设置为x父节点的颜色,
                     * x父节点设为黑色,
                     * w的右孩子设置为黑色,
                     * 这样w路径上多出来一个黑节点,
                     * 对x的父节点进行左旋,
                     * 相当于把原来w路径上多出来的黑节点补充到x的路径上,
                     * 这样就填补了原来删除的黑节点,恢复平衡
                     */
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.right.color = BLACK;
                    leftRotate(t, x.parent);

                    x = t.root;
                }
            }
            //x是右孩子的情况
            else{
                //兄弟节点
                RBNode w = x.parent.left;
                //判断此时兄弟节点是否是真正的兄弟节点,只有黑色节点才是
                if(w.color == RED) {
                    w.color = BLACK;
                    x.parent.color = RED;
                    rightRotate(t, x.parent);
                    //此时w为黑色,才是真正的兄弟节点
                    w = x.parent.right;
                }
                //情况三,找兄弟借,兄弟没得借
                if(w.left.color == BLACK && w.right.color == BLACK) {
                    //把兄弟拉下水,设为红色,向上递归
                    w.color = RED;
                    x = x.parent;
                }
                //情况二,找兄弟借,兄弟有的借
                else{
                    //确保w的左孩子是红色
                    if(w.left.color == BLACK) {
                        w.right.color = BLACK;
                        w.color = RED;
                        leftRotate(t, w);
                        w = x.parent.right;
                    }

                    /*
                     * 此时一定是w为黑色,w的左孩子为红色,
                     * w设置为x父节点的颜色,
                     * x父节点设为黑色,
                     * w的左孩子设置为黑色,
                     * 这样w路径上多出来一个黑节点,
                     * 对x的父节点进行右旋,
                     * 相当于把原来w路径上多出来的黑节点补充到x的路径上,
                     * 这样就填补了原来删除的黑节点,恢复平衡
                     */
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.left.color = BLACK;
                    rightRotate(t, x.parent);

                    x = t.root;
                }
            }
        }
        //情况一、替代节点是红色,则直接染红,补偿删除的黑色节点,这样红黑树依然保持平衡
        x.color = BLACK;
    }

    //红黑树节点类定义
    static class RBNode {
        private Integer key;  //节点值
        private RBNode parent;  //父节点
        private RBNode left;   //左孩子
        private RBNode right;   //右孩子
        private boolean color = BLACK;

        public RBNode() {

        }

        public RBNode(Integer key) {
            this.key = key;
        }

        public RBNode(Integer key, RBNode parent) {
            this.parent = parent;
            this.color = BLACK;
            this.key = key;
        }

        public RBNode(RBNode parent, RBNode left, RBNode right, Integer key, boolean color) {
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.key = key;
            this.color = color;
        }

        public Integer getKey() {
            return key;
        }

        public void setKey(Integer key) {
            this.key = key;
        }

        public RBNode getParent() {
            return parent;
        }

        public void setParent(RBNode parent) {
            this.parent = parent;
        }

        public RBNode getLeft() {
            return left;
        }

        public void setLeft(RBNode left) {
            this.left = left;
        }

        public RBNode getRight() {
            return right;
        }

        public void setRight(RBNode right) {
            this.right = right;
        }
    }

    @Override
    public Object root() {
        return root;
    }

    @Override
    public Object left(Object RBNode) {
        return ((RBNode)RBNode).left;
    }

    @Override
    public Object right(Object RBNode) {
        return ((RBNode)RBNode).right;
    }

    @Override
    public Object string(Object RBNode) {
        RBNode myRBNode = (RBNode)RBNode;

        String color = myRBNode.color == RED ? "RED" : "BLACK";

        return myRBNode.key + "(" + color + ")";
    }

    public static void main(String[] args) {
        RedBlackTree bst = new RedBlackTree();

        bst.RBInsert(bst, new RBNode(12));
        bst.RBInsert(bst, new RBNode(5));
        bst.RBInsert(bst, new RBNode(2));
        bst.RBInsert(bst, new RBNode(9));
        bst.RBInsert(bst, new RBNode(18));
        bst.RBInsert(bst, new RBNode(15));
        bst.RBInsert(bst, new RBNode(19));
        bst.RBInsert(bst, new RBNode(17));

        bst.inorderTreeWalk(bst.root);

        System.out.println();

        BinaryTrees.print(bst);

        bst.rbDelete(bst, bst.treeSearch(bst.root, 12));

        System.out.println();

        BinaryTrees.print(bst);
    }
}

BinaryTreeInfo

package redblacktree;

public interface BinaryTreeInfo {
	/**
	 * who is the root node
	 */
	Object root();
	/**
	 * how to get the left child of the node
	 */
	Object left(Object node);
	/**
	 * how to get the right child of the node
	 */
	Object right(Object node);
	/**
	 * how to print the node
	 */
	Object string(Object node);
}

BinaryTrees

package redblacktree;


public final class BinaryTrees {

	private BinaryTrees() {
	}

	public static void print(BinaryTreeInfo tree) {
		print(tree, null);
	}

	public static void println(BinaryTreeInfo tree) {
		println(tree, null);
	}

	public static void print(BinaryTreeInfo tree, PrintStyle style) {
		if (tree == null || tree.root() == null) return;
		printer(tree, style).print();
	}

	public static void println(BinaryTreeInfo tree, PrintStyle style) {
		if (tree == null || tree.root() == null) return;
		printer(tree, style).println();
	}

	public static String printString(BinaryTreeInfo tree) {
		return printString(tree, null);
	}

	public static String printString(BinaryTreeInfo tree, PrintStyle style) {
		if (tree == null || tree.root() == null) return null;
		return printer(tree, style).printString();
	}

	private static Printer printer(BinaryTreeInfo tree, PrintStyle style) {
		if (style == PrintStyle.INORDER) return new InorderPrinter(tree);
		return new LevelOrderPrinter(tree);
	}

	public enum PrintStyle {
		LEVEL_ORDER, INORDER
	}
}

InorderPrinter

package redblacktree;

/**

             ┌──800
         ┌──760
         │   └──600
     ┌──540
     │   └──476
     │       └──445
 ┌──410
 │   └──394
381
 │     ┌──190
 │     │   └──146
 │  ┌──40
 │  │  └──35
 └──12
    └──9
 */
public class InorderPrinter extends Printer {
	private static String rightAppend;
	private static String leftAppend;
	private static String blankAppend;
	private static String lineAppend;
	static {
		int length = 2;
		rightAppend = "┌" + Strings.repeat("─", length);
		leftAppend = "└" + Strings.repeat("─", length);
		blankAppend = Strings.blank(length + 1);
		lineAppend = "│" + Strings.blank(length);
	}

	public InorderPrinter(BinaryTreeInfo tree) {
		super(tree);
	}

	@Override
	public String printString() {
		StringBuilder string = new StringBuilder(
				printString(tree.root(), "", "", ""));
		string.deleteCharAt(string.length() - 1);
		return string.toString();
	}

	/**
	 * 生成node节点的字符串
	 * @param nodePrefix node那一行的前缀字符串
	 * @param leftPrefix node整棵左子树的前缀字符串
	 * @param rightPrefix node整棵右子树的前缀字符串
	 * @return
	 */
	private String printString(
			Object node,
			String nodePrefix,
			String leftPrefix,
			String rightPrefix) {
		Object left = tree.left(node);
		Object right = tree.right(node);
		String string = tree.string(node).toString();

		int length = string.length();
		if (length % 2 == 0) {
			length--;
		}
		length >>= 1;

		String nodeString = "";
		if (right != null) {
			rightPrefix += Strings.blank(length);
			nodeString += printString(right, 
					rightPrefix + rightAppend, 
					rightPrefix + lineAppend, 
					rightPrefix + blankAppend);
		}
		nodeString += nodePrefix + string + "\n";
		if (left != null) {
			leftPrefix += Strings.blank(length);
			nodeString += printString(left, 
					leftPrefix + leftAppend, 
					leftPrefix + blankAppend, 
					leftPrefix + lineAppend);
		}
		return nodeString;
	}
}

LevelOrderPrinter

package redblacktree;

import java.util.*;

/**

   ┌───381────┐
   │          │
┌─12─┐     ┌─410─┐
│    │     │     │
9  ┌─40─┐ 394 ┌─540─┐
   │    │     │     │
  35 ┌─190 ┌─476 ┌─760─┐
     │     │     │     │
    146   445   600   800
 */
public class LevelOrderPrinter extends Printer {
	/**
	 * 节点之间允许的最小间距(最小只能填1)
	 */
	private static final int MIN_SPACE = 1;
	private Node root;
	private int minX;
	private int maxWidth;
	private List<List<Node>> nodes;

	public LevelOrderPrinter(BinaryTreeInfo tree) {
		super(tree);
		
		root = new Node(tree.root(), tree);
		maxWidth = root.width;
	}

	@Override
	public String printString() {
		// nodes用来存放所有的节点
		nodes = new ArrayList<>();
		
		fillNodes();
		cleanNodes();
		compressNodes();
		addLineNodes();
		
		int rowCount = nodes.size();

		// 构建字符串
		StringBuilder string = new StringBuilder();
		for (int i = 0; i < rowCount; i++) {
			if (i != 0) {
				string.append("\n");
			}

			List<Node> rowNodes = nodes.get(i);
			StringBuilder rowSb = new StringBuilder();
			for (Node node : rowNodes) {
				int leftSpace = node.x - rowSb.length() - minX;
				rowSb.append(Strings.blank(leftSpace));
				rowSb.append(node.string);
			}

			string.append(rowSb);
		}

		return string.toString();
	}

	/**
	 * 添加一个元素节点
	 */
	private Node addNode(List<Node> nodes, Object btNode) {
		Node node = null;
		if (btNode != null) {
			node = new Node(btNode, tree);
			maxWidth = Math.max(maxWidth, node.width);
			nodes.add(node);
		} else {
			nodes.add(null);
		}
		return node;
	}

	/**
	 * 以满二叉树的形式填充节点
	 */
	private void fillNodes() {
		// 第一行
		List<Node> firstRowNodes = new ArrayList<>();
		firstRowNodes.add(root);
		nodes.add(firstRowNodes);

		// 其他行
		while (true) {
			List<Node> preRowNodes = nodes.get(nodes.size() - 1);
			List<Node> rowNodes = new ArrayList<>();

			boolean notNull = false;
			for (Node node : preRowNodes) {
				if (node == null) {
					rowNodes.add(null);
					rowNodes.add(null);
				} else {
					Node left = addNode(rowNodes, tree.left(node.btNode));
					if (left != null) {
						node.left = left;
						left.parent = node;
						notNull = true;
					}

					Node right = addNode(rowNodes, tree.right(node.btNode));
					if (right != null) {
						node.right = right;
						right.parent = node;
						notNull = true;
					}
				}
			}

			// 全是null,就退出
			if (!notNull) break;
			nodes.add(rowNodes);
		}
	}

	/**
	 * 删除全部null、更新节点的坐标
	 */
	private void cleanNodes() {
		int rowCount = nodes.size();
		if (rowCount < 2) return;

		// 最后一行的节点数量
		int lastRowNodeCount = nodes.get(rowCount - 1).size();

		// 每个节点之间的间距
		int nodeSpace = maxWidth + 2;

		// 最后一行的长度
		int lastRowLength = lastRowNodeCount * maxWidth 
				+ nodeSpace * (lastRowNodeCount - 1);

		// 空集合
		Collection<Object> nullSet = Collections.singleton(null);

		for (int i = 0; i < rowCount; i++) {
			List<Node> rowNodes = nodes.get(i);

			int rowNodeCount = rowNodes.size();
			// 节点左右两边的间距
			int allSpace = lastRowLength - (rowNodeCount - 1) * nodeSpace;
			int cornerSpace = allSpace / rowNodeCount - maxWidth;
			cornerSpace >>= 1;

			int rowLength = 0;
			for (int j = 0; j < rowNodeCount; j++) {
				if (j != 0) {
					// 每个节点之间的间距
					rowLength += nodeSpace;
				}
				rowLength += cornerSpace;
				Node node = rowNodes.get(j);
				if (node != null) {
					// 居中(由于奇偶数的问题,可能有1个符号的误差)
					int deltaX = (maxWidth - node.width) >> 1;
					node.x = rowLength + deltaX;
					node.y = i;
				}
				rowLength += maxWidth;
				rowLength += cornerSpace;
			}
			// 删除所有的null
			rowNodes.removeAll(nullSet);
		}
	}

	/**
	 * 压缩空格
	 */
	private void compressNodes() {
		int rowCount = nodes.size();
		if (rowCount < 2) return;

		for (int i = rowCount - 2; i >= 0; i--) {
			List<Node> rowNodes = nodes.get(i);
			for (Node node : rowNodes) {
				Node left = node.left;
				Node right = node.right;
				if (left == null && right == null) continue;
				if (left != null && right != null) {
					// 让左右节点对称
					node.balance(left, right);

					// left和right之间可以挪动的最小间距
					int leftEmpty = node.leftBoundEmptyLength();
					int rightEmpty = node.rightBoundEmptyLength();
					int empty = Math.min(leftEmpty, rightEmpty);
					empty = Math.min(empty, (right.x - left.rightX()) >> 1);

					// left、right的子节点之间可以挪动的最小间距
					int space = left.minLevelSpaceToRight(right) - MIN_SPACE;
					space = Math.min(space >> 1, empty);

					// left、right往中间挪动
					if (space > 0) {
						left.translateX(space);
						right.translateX(-space);
					}

					// 继续挪动
					space = left.minLevelSpaceToRight(right) - MIN_SPACE;
					if (space < 1) continue;

					// 可以继续挪动的间距
					leftEmpty = node.leftBoundEmptyLength();
					rightEmpty = node.rightBoundEmptyLength();
					if (leftEmpty < 1 && rightEmpty < 1) continue;

					if (leftEmpty > rightEmpty) {
						left.translateX(Math.min(leftEmpty, space));
					} else {
						right.translateX(-Math.min(rightEmpty, space));
					}
				} else if (left != null) {
					left.translateX(node.leftBoundEmptyLength());
				} else { // right != null
					right.translateX(-node.rightBoundEmptyLength());
				}
			}
		}
	}
	
	private void addXLineNode(List<Node> curRow, Node parent, int x) {
		Node line = new Node("─");
		line.x = x;
		line.y = parent.y;
		curRow.add(line);
	}

	private Node addLineNode(List<Node> curRow, List<Node> nextRow, Node parent, Node child) {
		if (child == null) return null;

		Node top = null;
		int topX = child.topLineX();
		if (child == parent.left) {
			top = new Node("┌");
			curRow.add(top);

			for (int x = topX + 1; x < parent.x; x++) {
				addXLineNode(curRow, parent, x);
			}
		} else {
			for (int x = parent.rightX(); x < topX; x++) {
				addXLineNode(curRow, parent, x);
			}

			top = new Node("┐");
			curRow.add(top);
		}

		// 坐标
		top.x = topX;
		top.y = parent.y;
		child.y = parent.y + 2;
		minX = Math.min(minX, child.x);

		// 竖线
		Node bottom = new Node("│");
		bottom.x = topX;
		bottom.y = parent.y + 1;
		nextRow.add(bottom);

		return top;
	}

	private void addLineNodes() {
		List<List<Node>> newNodes = new ArrayList<>();

		int rowCount = nodes.size();
		if (rowCount < 2) return;

		minX = root.x;

		for (int i = 0; i < rowCount; i++) {
			List<Node> rowNodes = nodes.get(i);
			if (i == rowCount - 1) {
				newNodes.add(rowNodes);
				continue;
			}

			List<Node> newRowNodes = new ArrayList<>();
			newNodes.add(newRowNodes);

			List<Node> lineNodes = new ArrayList<>();
			newNodes.add(lineNodes);
			for (Node node : rowNodes) {
				addLineNode(newRowNodes, lineNodes, node, node.left);
				newRowNodes.add(node);
				addLineNode(newRowNodes, lineNodes, node, node.right);
			}
		}

		nodes.clear();
		nodes.addAll(newNodes);
	}

	private static class Node {
		/**
		 * 顶部符号距离父节点的最小距离(最小能填0)
		 */
		private static final int TOP_LINE_SPACE = 1;

		Object btNode;
		Node left;
		Node right;
		Node parent;
		/**
		 * 首字符的位置
		 */
		int x;
		int y;
		int treeHeight;
		String string;
		int width;

		private void init(String string) {
			string = (string == null) ? "null" : string;
			string = string.isEmpty() ? " " : string;

			width = string.length();
			this.string = string;
		}

		public Node(String string) {
			init(string);
		}

		public Node(Object btNode, BinaryTreeInfo opetaion) {
			init(opetaion.string(btNode).toString());

			this.btNode = btNode;
		}

		/**
		 * 顶部方向字符的X(极其重要)
		 * 
		 * @return
		 */
		private int topLineX() {
			// 宽度的一半
			int delta = width;
			if (delta % 2 == 0) {
				delta--;
			}
			delta >>= 1;

			if (parent != null && this == parent.left) {
				return rightX() - 1 - delta;
			} else {
				return x + delta;
			}
		}

		/**
		 * 右边界的位置(rightX 或者 右子节点topLineX的下一个位置)(极其重要)
		 */
		private int rightBound() {
			if (right == null) return rightX();
			return right.topLineX() + 1;
		}

		/**
		 * 左边界的位置(x 或者 左子节点topLineX)(极其重要)
		 */
		private int leftBound() {
			if (left == null) return x;
			return left.topLineX();
		}

		/**
		 * x ~ 左边界之间的长度(包括左边界字符)
		 * 
		 * @return
		 */
		private int leftBoundLength() {
			return x - leftBound();
		}

		/**
		 * rightX ~ 右边界之间的长度(包括右边界字符)
		 * 
		 * @return
		 */
		private int rightBoundLength() {
			return rightBound() - rightX();
		}

		/**
		 * 左边界可以清空的长度
		 * 
		 * @return
		 */
		private int leftBoundEmptyLength() {
			return leftBoundLength() - 1 - TOP_LINE_SPACE;
		}

		/**
		 * 右边界可以清空的长度
		 * 
		 * @return
		 */
		private int rightBoundEmptyLength() {
			return rightBoundLength() - 1 - TOP_LINE_SPACE;
		}

		/**
		 * 让left和right基于this对称
		 */
		private void balance(Node left, Node right) {
			if (left == null || right == null) return;
			// 【left的尾字符】与【this的首字符】之间的间距
			int deltaLeft = x - left.rightX();
			// 【this的尾字符】与【this的首字符】之间的间距
			int deltaRight = right.x - rightX();

			int delta = Math.max(deltaLeft, deltaRight);
			int newRightX = rightX() + delta;
			right.translateX(newRightX - right.x);

			int newLeftX = x - delta - left.width;
			left.translateX(newLeftX - left.x);
		}

		private int treeHeight(Node node) {
			if (node == null) return 0;
			if (node.treeHeight != 0) return node.treeHeight;
			node.treeHeight = 1 + Math.max(
					treeHeight(node.left), treeHeight(node.right));
			return node.treeHeight;
		}

		/**
		 * 和右节点之间的最小层级距离
		 */
		private int minLevelSpaceToRight(Node right) {
			int thisHeight = treeHeight(this);
			int rightHeight = treeHeight(right);
			int minSpace = Integer.MAX_VALUE;
			for (int i = 0; i < thisHeight && i < rightHeight; i++) {
				int space = right.levelInfo(i).leftX 
						- this.levelInfo(i).rightX;
				minSpace = Math.min(minSpace, space);
			}
			return minSpace;
		}

		private LevelInfo levelInfo(int level) {
			if (level < 0) return null;
			int levelY = y + level;
			if (level >= treeHeight(this)) return null;

			List<Node> list = new ArrayList<>();
			Queue<Node> queue = new LinkedList<>();
			queue.offer(this);

			// 层序遍历找出第level行的所有节点
			while (!queue.isEmpty()) {
				Node node = queue.poll();
				if (levelY == node.y) {
					list.add(node);
				} else if (node.y > levelY) break;

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

			Node left = list.get(0);
			Node right = list.get(list.size() - 1);
			return new LevelInfo(left, right);
		}

		/**
		 * 尾字符的下一个位置
		 */
		public int rightX() {
			return x + width;
		}

		public void translateX(int deltaX) {
			if (deltaX == 0) return;
			x += deltaX;

			// 如果是LineNode
			if (btNode == null) return;

			if (left != null) {
				left.translateX(deltaX);
			}
			if (right != null) {
				right.translateX(deltaX);
			}
		}
	}

	private static class LevelInfo {
		int leftX;
		int rightX;

		public LevelInfo(Node left, Node right) {
			this.leftX = left.leftBound();
			this.rightX = right.rightBound();
		}
	}
}

Printer

package redblacktree;

public abstract class Printer {
	/**
	 * 二叉树的基本信息
	 */
	protected BinaryTreeInfo tree;
	
	public Printer(BinaryTreeInfo tree) {
		this.tree = tree;
	}
	
	/**
	 * 生成打印的字符串
	 */
	public abstract String printString();
	
	/**
	 * 打印后换行
	 */
	public void println() {
		print();
		System.out.println();
	}
	
	/**
	 * 打印
	 */
	public void print() {
		System.out.print(printString());
	}
}

Strings

package redblacktree;

public class Strings {
	public static String repeat(String string, int count) {
		if (string == null) return null;
		
		StringBuilder builder = new StringBuilder();
		while (count-- > 0) {
			builder.append(string);
		}
		return builder.toString();
	}
	
	public static String blank(int length) {
		if (length < 0) return null;
		if (length == 0) return "";
		return String.format("%" + length + "s", "");
	}
}

总结

这个实现过程比较复杂,更多的是在提现一个打印结果的实现,没有必要自己去手敲,可以全部拿下来,去运行,看具体核心实现红黑树对应的那些功能。对于红黑树更细节的去使用,我也还在探索中,之后会更加细节的去记录。

ps:计划每日更新一篇博客,今日2023-05-04,日更第十八天。
昨日更新:

B树(B-tree、B-树)理论详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/17223.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

破解马赛克有多「容易」?

刷短视频时&#xff0c;估计大家都看过下面这类视频&#xff0c;各家营销号争相曝光「一分钟解码苹果笔刷背后内容」的秘密。换汤不换药&#xff0c;自媒体们戏称其为「破解马赛克」&#xff0c;殊不知让多少不明真相的用户建立起了错误的认知&#xff0c;也让苹果笔刷第 10086…

【面试】嵌入式C语言题目整理

【面试】嵌入式C语言题目整理 描述内存四区。 内存四区分为&#xff1a;代码区、静态区、堆区、栈区 代码区就是用来存放代码的。 静态区用来存放全局变量、静态变量、常量&#xff08;字符串常量、const修饰的全局变量&#xff09;。 堆区中的内存是由程序员自己申请和释放的&…

九、MyBatis动态SQL

文章目录 九、动态SQL9.1 if9.2 where9.3 trim9.4 choose、when、otherwise9.5 foreach9.6 SQL片段 本人其他相关文章链接 九、动态SQL 9.1 if 总结&#xff1a;根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到SQL中。 User getUserByParamsWithIf(User user…

Packet Tracer - 在思科路由器上配置 AAA 认证

Packet Tracer - 在思科路由器上配置 AAA 认证 拓扑图 地址分配表 设备 接口 IP 地址 子网掩码 默认网关 交换机端口 R1 G0/1 192.168.1.1 255.255.255.0 不适用 S1 F0/1 S0/0/0 (DCE) 10.1.1.2 255.255.255.252 不适用 不适用 R2 G0/0 192.168.2.1 255.2…

(四)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 3 ] 1 部署work node1.1 创建工作目录并拷贝二进制文件1.2 部署kubelet1.2.1 创建配置文件1.2.2 配置文件1.2.3 生成kubelet初次加入集群引导kubeconfig文件1.2.4 systemd管理kubelet1.2.5 启动并设置开机启动1.2.6 允许kubelet证书申请并加入集群 1.3…

JAVA-异常

文章目录 1.异常的体系1.3异常的分类 2.异常的处理2.2异常的抛出throw2.3异常的捕获2.3.1异常声明throws2.3.2 try-catch捕获并处理2.3.3 finally 2.4 异常的处理流程 3.自定义异常类 1.异常的体系 Throwable&#xff1a;是异常体系的顶层类&#xff0c;其派生出两个重要的子类…

人员拥挤检测系统 yolov5

人员拥挤检测系统通过YOLOv5网络模型算法技术&#xff0c;人员拥挤检测系统算法模型对校园/厂区车间/街道等场景的异常的人群聚集&#xff08;出现拥挤情况&#xff09;时&#xff0c;立刻抓拍存档并通知相关人员及时处理。在介绍Yolo算法之前&#xff0c;首先先介绍一下滑动窗…

ES是如何解决高可用

https://www.cnblogs.com/crazymakercircle/p/15433680.html ES是一个分布式全文检索框架&#xff0c;隐藏了复杂的处理机制&#xff0c;核心数据分片机制、集群发现、分片负载均衡请求路由。 ES的高可用架构&#xff0c;总体如下图&#xff1a; 说明&#xff1a;本文会以pdf…

Java 基础入门篇(一)—— Java 概述

文章目录 一、Java 概述二、Java 的产品 JDK2.1 JDK 安装2.2 Java与 Javac 介绍2.3 Java 程序的开发步骤 三、Java 程序的执行原理四、JDK 的组成五、Java 的跨平台工作原理 一、Java 概述 Java 是 sun 公司在 1995 年推出的一门计算机高级编程语言&#xff0c;其语言风格接近人…

深度学习卷积神经网络学习小结2

简介 经过大约两周左右的学习&#xff0c;对深度学习有了一个初步的了解&#xff0c;最近的任务主要是精读深度学习方向的文献&#xff0c;由于搭建caffe平台失败而且比较耗费时间就没有再尝试&#xff0c;所以并没有做实践方面的工作&#xff0c;本文只介绍了阅读文献学到的知…

外卖项目优化-02-mysql主从复制、读写分离(shardingJdbc)、Nginx(反向代理,负载均衡)

文章目录 瑞吉外卖项目优化-Day02课程内容前言1. MySQL主从复制1.1 介绍1.2 搭建1.2.1 准备工作1.2.2 主库配置1.2.3 从库配置 1.3 测试 2. 读写分离案例 (shardingJdbc)2.1 背景介绍2.2 ShardingJDBC介绍2.3 数据库环境2.4 初始工程导入2.5 读写分离配置2.6 测试 3. 项目实现读…

基于ATECLOUD的航电系统可灵活扩展自动化测试平台

随着电子技术的发展&#xff0c;航电系统在飞机整机中的重要性飞速提升。据统计&#xff0c;近年来航电系统在飞机出厂成本中的比例直线上升&#xff0c;航电系统研发成本已占飞机研制总成本的近30%&#xff0c;并保持着持续扩大的趋势。测试保障作为航电产业链至关重要的一环&…

基于JavaWeb实现的寻码网文章资讯管理系统

一、技术结构 前端&#xff1a;html ajax 后端&#xff1a;SpringBootMybatis-plus 环境&#xff1a;JDK1.8 | Mysql | Maven | Redis 二、功能简介 数据库与代码截图 后端管理-登录页 后端管理-首页 后端管理-文章管理-发布文章 后端管理-文章管理-文章列表 后端管理-文…

【iOS KVO(下) KVO的内部结构和源码】

前言 学习KVO的过程&#xff0c;我分为了KVO的实现过程分析和内部结构的学习&#xff0c;学习了实现过程&#xff0c;接下来看KVO是通过何种内部结构实现如此通知&#x1f4e2;和监听。 1 KVO的存储结构 KVO的实现过程离不开合理的存储结构&#xff0c;用到了如下几个类 GS…

智能安防系统-视频监控系统

一、智能安防系统 1、智能安防系统介绍 安全防范系统成为了智慧城市与物联网行业应用中的一个非常重要的子系统。 安防系统主要包括&#xff1a;视频监控系统、入侵报警系统、出入口控制系统、电子巡查系统以及智能停车场管理系统等5个子系统。 AI人工智能安防系统功能&#xf…

Java8中DateTimeFormatter真的是线程安全的吗?

文章目录 [toc] 1.背景2.解决办法2.1办法一&#xff1a;换姿势或者升级JDK的版本2.1办法二&#xff1a;更换文件名称字生成策略 Java8中DateTimeFormatter真的是线程安全的吗&#xff1f; 答案是否定的 1.背景 由于之前写了一个旷世的ocr的服务,接入了旷世的FaceID的人脸比对…

C++笔记——第十六篇 异常

目录 1.C语言传统的处理错误的方式 2. C异常概念 3. 异常的使用 3.1 异常的抛出和捕获 在函数调用链中异常栈展开匹配原则 3.2异常安全 4.异常的优缺点 1.C语言传统的处理错误的方式 传统的错误处理机制&#xff1a; 1. 终止程序&#xff0c;如assert&#xff0c;缺陷&a…

04-Vue技术栈之组件化编程

目录 1、模块与组件、模块化与组件化1.1 模块1.2 组件1.3 模块化1.4 组件化1.5 传统方式编写应用1.6 组件方式编写应用 2、非单文件组件2.1 基本使用2.2 几个注意点2.3 组件的嵌套2.4 VueComponent2.5 一个重要的内置关系2.6 总结 3、单文件组件3.1 一个.vue 文件的组成(3 个部…

【玩转Git三剑客笔记】第一章 Git基础

第一章 Git基础 1.综述2.安装Git3.使用Git之前需要做的最小配置4.创建第一个仓库并配置local用户信息1.创建Git仓库2.设置Git最小配置 5.通过几次commit来认识工作区和暂存区1.将工作区中所有已经被git追踪的文件一起添加到暂存区2.git log查看提交日志 6.给文件重命名的简便方…

权限提升:不带引号服务路径 || 不安全的服务权限.

权限提升&#xff1a;不带引号服务路径 || 不安全的服务权限. 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动…