Java 数据结构篇-实现红黑树的核心方法

news2024/12/23 7:02:48

 🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
  

 

文章目录

        1.0 红黑树的说明

        2.0 红黑树的特性

        3.0 红黑树的成员变量及其构造方法

        4.0 实现红黑树的核心方法

        4.1 红黑树内部类的核心方法

        (1)判断当前节点是否为左孩子节点 - isLeftChild()

        (2)获取叔叔节点 - uncle()

        (3)获取兄弟节点 - brother()

        4.2 红黑树外部类的核心方法

        (1)判断是否为红色节点 isRed - (TreeNode node)

        (2)判断是否为黑色节点 isBlack - (TreeNode node)

        (3)右旋 - rightRotate(TreeNode node)

        (4)左旋 - leftRotate(TreeNode node)

        (5)更新、添加节点 - put(int key,Object value)

        (6)删除节点 - remove(int key)

        5.0 实现红黑树核心方法的完整代码


        1.0 红黑树的说明

        红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。

        与 AVL 树相比之下,红黑树放宽了对平衡的要求,通过牺牲一定的平衡性能来换取更高的插入、删除和查找操作的性能。红黑树的旋转操作相对较少,因此在实际应用中,红黑树更常用于需要高效的动态数据结构,如集合、映射等。而 AVL 树则更适用于对平衡性要求较高的场景,如数据库索引等。

        总的来说,红黑树也是一种自平衡的二叉搜索树,较之 AVL 树,插入和删除时旋转次数更少。

        2.0 红黑树的特性

        - 所有节点都有两种颜色:红与黑

        - 所有 null 视为黑色

        - 红色节点不能相邻

        - 根节点是黑色

        - 从根节点到任意一个叶子节点,路径中的黑色节点数一样(黑色完美平衡)

        前四个规则都很容易理解,接下来详细说明一下最后一个规则,到底什么是从根节点到叶子节点的所有路径都要保证黑色节点数一样?

如图 1:

        从根节点出发到叶子节点,首先从左子树方向开始,6 -> 2 -> 1 -> null ,这一条路径的黑色节点一共有 3 个;现在从左子树方向出发,6 -> 2 -> null ,这一条路径的黑色节点一共有 3 个;现在从右子树方向开始,6 -> 8 -> null ,这一条路径的黑色节点一共也是有 3 个黑色节点;这几条路径的黑色节点都为 3 。因此满足红黑树的最后一条规则。

如图 2:

        同理,从根节点出发到叶子节点,先从左子树方向开始,6 -> 2 -> 1 -> null ,这一条路径的黑色节点一共有三个;再从 6 -> 2 -> null ,但是这一条路径的黑色节点只有 2 个黑色节点,不满足红黑树的最后一个规则。

        在构建红黑树需要满足以上规则,无论插入、删除等都要满足红黑树的特性。

        3.0 红黑树的成员变量及其构造方法

        节点类 TreeNode 作为内部类,该内部类的成员变量有:

        int key : 关键字,用于比较大小

        Object value : 值

        TreeNode left : 左节点

        TreeNode right : 右节点

        Color color :颜色,默认设置为红色

        TreeNode parent :该节点的父亲节点

        该内部类的构造方法:

        节点类的构造方法主要是参数为 (int key, Object value) 的构造方法

        红黑树的外部类的成员变量主要为:

        TreeNode root :根节点,一般默认为 null  

        红黑树的外部类的构造方法:

        主要采取默认的参数为 null 的构造方法

代码如下:

import static TreeNode.RedBlackTree.Color.BLACK;
import static TreeNode.RedBlackTree.Color.RED;

public class RedBlackTree {

    enum Color {
        RED,BLACK;
    }

    private TreeNode root;

    private static class TreeNode {
        int key;
        Object value;
        TreeNode left;
        TreeNode right;
        TreeNode parent;
        Color color = RED;

        //构造方法
        public TreeNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

    }

        4.0 实现红黑树的核心方法

        为了更好的实现插入、删除等方法,需要先实现基础的方法 "打好基础"。主要分为实现内部类与外部类的核心方法。

        4.1 红黑树内部类的核心方法

        (1)判断当前节点是否为左孩子节点 - isLeftChild()

                实现思路为:先判断该父亲节点是否为 null ,若 parent == null 时,则当前节点为根节点;若 parent != null ,则需要继续判断 this == parent.left ,若满足,则说明当前节点为左孩子节点,若不满足,则说明当前节点为右孩子节点。

代码如下:

        //判断是否为左孩子
         public boolean isLeftChild() {
             return parent != null && parent.left == this;
         }

        (2)获取叔叔节点 - uncle()

                叔叔节点即为跟父亲节点的同一辈的节点,跟父亲节点的父亲是同一个父亲。

                实现思路为:需要先判断该爷爷节点是否为 null 与 父亲节点是否为 null ,因为该两种情况都不具备叔叔节点。若以上情况都不为 null 时,接着还需要判断当前节点的父亲节点的位置,若父亲节点为左孩子,则叔叔节点为右孩子;若父亲节点为右孩子,则叔叔节点为左孩子。

代码如下:

         //获取叔叔节点
         public TreeNode uncle() {
             if (this.parent == null || this.parent.parent == null) {
                 return null;
             }
             if (this.isLeftChild()) {
                 return this.parent.parent.right;
             } else {
                 return this.parent.parent.left;
             }
         }

        (3)获取兄弟节点 - brother()

                跟当前节点是同一辈的节点,同一个父亲节点。

                实现思路为:先判断当前节点的父亲节点是否为 null ,若 parent == null 时,说明该节点不存在兄弟节点;若 parent != null 时,说明该节点存在兄弟节点,然后继续判断当前节点的位置,若当前节点为左孩子,则兄弟节点为:parent.right ;若当前节点为右孩子,则兄弟节点为:parent.left 。

代码如下:

         //获取兄弟节点
         public TreeNode brother() {
             if (this.parent == null) {
                 return null;
             }
             if (this.isLeftChild()) {
                 return this.parent.right;
             }else {
                 return this.parent.left;
             }
         }

        4.2 红黑树外部类的核心方法

        (1)判断是否为红色节点 isRed - (TreeNode node)

                实现思路为:根据红黑的规则可以知道,除了根节点与 null 之外, 当前节点的 color == RED 时,则该节点为红色节点。

代码如下:

    //判断是否为红色节点
    private boolean isRed(TreeNode node) {
        return node != null && node.color == RED;
    }

        (2)判断是否为黑色节点 isBlack - (TreeNode node)

                实现思路为:有两种情况下: null 或者 color == BLACK 的节点为黑色节点。

代码如下

    //判断是否为黑色节点
    private  boolean isBlack(TreeNode node) {
        return node == null || node.color == BLACK;
    }

           (3)右旋 - rightRotate(TreeNode node)

                实现思路为:跟 AVL 树的右旋会有一定的区别,因为新添加了 parent 成员,所以正常右旋完之后,需要维护 parent 变量。

                1. parent 的处理 2. 旋转后新根的父子关系

如图:

        现需要将节点 8 进行右旋,假设节点 8 为 node ,那么先记录节点5、节点6,nodeLeft = node.left、nodeLeftRight = nodeLeft.right 。接着更新节点 5 的右孩子,将其更新为节点 8 作为右孩子。还需要更新节点 8 的左孩子 ,将其更新为节点 6 作为左孩子。从表面上已经完成右旋,但还需要维护节点的父亲节点 parent 。对于节点 6 来说,之前的父亲节点为节点 5 ,现在需要更新父亲节点为节点8,不过需要先判断节点 6 是否为 null ,若节点 6 为 null ,则不需要更新父亲节点的操作。若节点 6 不为 null,则需要更新父亲节点这一操作。对于节点 5 来说,节点 5 的父亲节点之前为节点 8,先父亲节点更新为节点 8 的父亲节点,因此还需要记录节点 8 的父亲节点。对于节点 8 来说,将其父亲节点更新为节点 5 。最后,由于现在的节点与节点之间时双互的,所以还需要维护新根的父子关系,不过需要判断节点 8 的父亲节点是否为 null ,若为 parent == null 时,则说明节点 5 为根节点,因此需要进行 root = node.left 调整;若 parent != null ,需要判断 node 的位置,若 node 是左孩子,那么 parent.left = node.left 进行链接;若 node 是右孩子,那么 parent.right = node.left 进行链接。那么现在就可以根据这个逻辑写代码了。

最后调整完之后的图:

代码如下:

    //右旋
    //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系
    private void rightRotate(TreeNode node) {
        TreeNode parent = node.parent;
        TreeNode nodeLeft = node.left;
        TreeNode nodeLeftRight = nodeLeft.right;
        if (nodeLeftRight != null) {
            nodeLeftRight.parent = node;
        }
        nodeLeft.right = node;
        nodeLeft.parent = parent;
        node.left = nodeLeftRight;
        node.parent = nodeLeft;
        if (parent == null) {
            root = nodeLeft;
        } else if (parent.left == node) {
            parent.left = nodeLeft;
        }else {
            parent.right = nodeLeft;
        }

    }

        (4)左旋 - leftRotate(TreeNode node)

                跟右旋的逻辑是一摸一样的,这里就不多赘述了。

代码如下:

    //左旋
    //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系
    private void leftRotate(TreeNode node) {
        TreeNode parent = node.parent;
        TreeNode nodeRight = node.right;
        TreeNode nodeRightLeft = nodeRight.left;
        if (nodeRightLeft != null) {
            nodeRightLeft.parent = node;
        }
        nodeRight.left = node;
        nodeRight.parent = parent;
        node.right = nodeRightLeft;
        node.parent = nodeRight;
        //2.重新与上一个节点建立联系
        if (parent == null) {
            root = nodeRight;
        } else if (parent.left == node) {
            parent.left = nodeRight;
        }else {
            parent.right = nodeRight;
        }

    }

        (5)更新、添加节点 - put(int key,Object value)

                实现思路为:正常删除节点、更新节点,若遇到红红节点不平衡,则需要进行调整

                对于正常删除、更新节点的逻辑为:根据 key 来寻找需要更新或者删除的节点、若找到了 key 的节点,那么直接更新该节点的 value 即可;若没有找到 key 的节点,需要进行添加节点。需要用到 parent 变量,记录每一次的当前节点,一旦当前节点为 node == null 时,找到了相应的空位,接着判断 key 与 parent.key 的大小。若 parent.key > key ,则 parent.left = node;若 parent.key < key 则 parent.right = node;若 parent == null 时,说明该红黑树为空树,所以 root = node 。最后对于更新来说,不会改变红黑树的平衡关系,对于添加完节点,很有可能会改变红黑树的平衡关系,所以需要进行红红修复。

                接下来详细聊一下红红节点不平衡后进行的调整:

                插入节点均视为红色

                - case 1:插入节点为根节点,将根节点变为黑色

                - case 2:插入节点的父亲若为黑色,树的红黑性质不变,无需调整

                插入节点的父亲节点为红色,触发红红相邻

                - case3:叔叔为红色

                将父亲节点变为黑色,为了保证黑色平衡,连带的叔叔也变为黑色,祖父如果是黑色不变色,会造成这颗子树黑色过多,因此祖父节点变为红色。但是祖父变为红色,可能也会继续触发红红相邻,因此对讲祖父进行递归调整。

如图 :对于 case 3 详细说明

                

        插入节点为节点 1 设为 node ,该父亲节点为节点 2 设为 parent 。对于 node 节点来说,遇到了红红不平衡的情况,且该节点 4 即为叔叔节点为红色。

        调整红黑树平衡:需要将父亲节点、叔叔节点的颜色都要置为黑色,parent.color = BLACK,uncle.color = BALCK ,将节点 3 即祖父节点的颜色置为红色,parent.parent.color = RED 。可以发现节点 3 与节点 5 又再一次出现了触发了红红相邻了,那么利用递归来解决这一情况,一旦遇到 node == root 时,即可停止递归了。

最后调整后之后的图:

                - case 4:叔叔为黑色

                1. 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡

                2. 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡

                3. 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡

                4. 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡

如图:对于 case 4 中  LL 不平衡的详细说明

        

                插入节点为节点 2 设为 node,父亲节点为节点 3 设为 parent ,该叔叔节点为 null 该节点为黑色,又满足 node 为左孩子,parent 也为左孩子, LL 不平衡。该调整需要将 parent.color = BLACK,parent.parent.color = RED,把爷爷节点 rightRotate(parent.parent) 右旋一次即可。

调整完之后的图为:

如图:对于 case 4 中 LR 不平衡的详细说明

               插入节点为节点 4 设为 node,父亲节点为节点 3 设为 parent,node 的叔叔节点为 null 所以该颜色为黑色。又 parent 是左孩子,node 是右孩子,满足 LR 不平衡。

需要做的第一步,将 LR 转变为 LL ,只需要将 node 节点左旋。

        接着,将 node.color = BLACK,parent.parent.color = RED 处理,再将爷爷节点右旋,rightRotate(parent.parent) 即可。

调整完之后的图:

         其余的 case 4 的情况大致与以上的情况相同,所以这里不再过多赘述了。  

代码如下:

    //更新、增添节点
    //正常更新、删除,遇到红红不平衡则需要进行调整
    public void put (int key, Object value) {
        TreeNode p = root;
        TreeNode parent = null;
        while (p != null) {
            parent = p;
            if (p.key > key) {
                p = p.left;
            }else if (p.key < key) {
                p = p.right;
            }else {
                p.value = value;
                return;
            }
        }
        TreeNode node = new TreeNode(key,value);
        if (parent == null) {
            root = node;
        }else {
            if (node.key > parent.key) {
                parent.right = node;

            }else {
                parent.left = node;

            }
            node.parent = parent;
        }
        //可能会发生红红不平衡,则需要调整
        fixRedRed(node);

    }
    //调整红红不平衡

    private void fixRedRed(TreeNode node) {
        //case1: 插入节点为根节点,将根节点变黑
        if(node == root) {
            node.color = BLACK;
            return;
        }
        if (isBlack(node.parent)) {
            //case2:插入节点的父亲若为黑,树的红黑性质不变,无需调整
            //无需调整
            return;
        }
        // 插入节点的父亲为红色,触发红红相邻
        //case3:叔叔为红色
        TreeNode parent = node.parent;
        TreeNode grandparent = parent.parent;
        TreeNode uncle = node.uncle();
        if (isRed(uncle)) {
            //进行变色处理即可
            //将其父亲、叔叔变为黑色,爷爷变为红色
            //若爷爷触发了红红,则继续递归调用该函数
            parent.color = BLACK;
            uncle.color = BLACK;
            grandparent.color = RED;
            fixRedRed(grandparent);
            return;
        }
        
        //case4:叔叔为黑色
        //该父亲为左孩子,该插入点也为左孩子,则触发 ll
        if (parent.isLeftChild() && node.isLeftChild()) {
            //先将父亲变为黑色、爷爷变为红色,再右旋转
            parent.color = BLACK;
            grandparent.color = RED;
            rightRotate(grandparent);
        }else if (parent.isLeftChild()) {
            //该插入节点为右孩子、该父亲为左孩子,则触发 lr
            //先左旋变为 ll 情况
            leftRotate(parent);
            node.color = BLACK;
            grandparent.color = RED;
            rightRotate(grandparent);
        } else if (!node.isLeftChild()) {
            //插入节点为右孩子、父亲节点也为右孩子 rr
            parent.color = BLACK;
            grandparent.color = RED;
            leftRotate(grandparent);
        }else {
            //插入节点为左孩子、父亲节点为右孩子 rl
            rightRotate(parent);
            node.color = BLACK;
            grandparent.color = RED;
            leftRotate(grandparent);

        }
    }

        (6)删除节点 - remove(int key)

                实现思路:正常删除节点,若遇到黑黑不平衡,则需要进行调整

                正常删除节点的逻辑:先找到需要删除的节点,为了后续的代码简洁,因此单独实现寻找删除节点的方法,寻找代码的逻辑这里就不过多展开了。又单独实现寻找删除节点的替代节点的方法,这个方法的简单逻辑为:若删除节点没有左右孩子,则替代的节点为 null ;若删除节点只有一个孩子,则返回不为 null 的孩子;若删除节点有两个节点,则找到右子树的最小节点返回即可。

        现在找到了删除节点,判断该删除节点是否为 null ,若 delete == null ,则说明没有找到删除节点,结束删除过程;若 delete != null ,则说明可以找到删除节点,则继续通过根据删除节点查找替换节点的方法找到替换节点,最后就可以删除节点了。对于删除节点是一个复杂的操作,所以单独为一个方法 doRemove(TreeNode deleted) 。

        对于实现 doRemove(TreeNode deleted) 方法,需要考虑以下情况:

         - case0: 对于删除节点有两个孩子来说,需要将其转换为只有一个孩子或者没有孩子情况。这里用到了李代桃僵技巧,例如,需要删除节点 deleted ,替代节点 replace ,将该两个节点的关键字 key 与值 value 交换,那么现在需要删除的节点不再是 deleted 了,而是 replace 。递归调用 doRemove(replace) 。这样子就可以将两个孩子的情况转变为一个孩子或者没有孩子的情况了。

        - case1: 删除节点为根节点的情况。先考虑根节点没有左右孩子的情况,则直接将 root == null 。再考虑根节点有一个孩子的情况,则利用到李代桃僵的技巧,将孩子节点的关键字 key 与值 value 跟root 节点的关键字 key 与值 value 分别进行交换,这样删除的节点就是孩子节点了,将根节点的左右孩子置为 null 即可。最后要不要考虑根节点有两个孩子的情况呢?

        其实是不用的,因为已经通过李代桃僵的技巧将有两个孩子节点转换为一个孩子或者没有孩子的节点。

        - case2: 删除节点不为根节点的情况。同样,先考虑删除的节点没有左右孩子的情况,也说明删除的节点就是叶子节点,所以先判断该删除节点的位置,若删除节点是左孩子,那么父亲节点的左孩子置为 null ,parent.left = null;若删除节点是右孩子,那么父亲节点的右孩子置为 null ,parent.right = null 。最后还需要将删除节点的父亲节点置为 null ,方便后续的内存自动回收。

        接着再来考虑删除节点有一个孩子的情况,删除节点设为 deleted,替换节点设为 repalce 。 先判断 deleted 的位置,若 deleted 是左孩子,则 parent.left = repalce 进行链接;若 deleted 是右孩子,则 parent.right = replace 进行链接。由于红黑树的节点与节点之间链接是相互的,所以,replace 节点的父亲节点需要链接到 parent 上, replace.parent = parent 。最后需要将删除节点删除干净,方便内存自动回收,所以 deleted.left = deleted.right = deleted.parent = null 。同样,对于删除节点有两个孩子的情况不需要考虑,已经转换为一个孩子或者没有孩子的情况了。

        删除节点之后,很有可能会导致红黑树的性质改变,所以删除节点之前或者删除节点之后,需要进行调整,使其红黑树确保性质不会发生改变。主要有以下情况:

        对于删除节点没有左右孩子来说:如果删除节点的颜色是红色,且没有左右孩子,则直接删除完之后,不会发生红黑树性质改变;如果删除节点的颜色是黑色,且没有左右孩子,则会触发黑黑相邻,直接删除会导致红黑树性质发生改变,所以需要进行复杂调整。

        对于删除节点有一个节点来说:如果删除节点的颜色为黑色,替换节点为红色,则删除节点之后,需要将替换节点的颜色变为黑色即可;如果删除节点的颜色为黑色,替换节点也为黑色,则触发了黑黑相邻,需要进行复杂的调整。

        进行复杂的调整,修复黑黑不平衡的方法 fixBlackBlack(TreeNode node)

        删除节点与剩下节点颜色都是黑色,触发双黑,双黑意思是,少了一个黑。

        - case3: 删除节点或者剩余节点的兄弟为红色,此时两个侄子定位黑色。

如图:

        删除节点为节点 4 设为 deletde ,兄弟节点为节点 8 设为 brother,父亲节点为节点 6 设为 parent 。现在删除节点 4 的颜色为黑色,且剩余节点为 null 该颜色为黑色,触发双黑,又兄弟节点为红色。满足以上情况时,先判断兄弟节点的置为,若兄弟节点为右孩子,则需要将该父亲节点进行左旋;若兄弟节点为左孩子,则需要将该父亲节点进行右旋。旋转完之后,需要变色处理,将父亲节点的颜色变为红色,兄弟节点的颜色变为黑色。最后,继续将删除节点递归调用该函数。该方法的目的就是将删除节点的兄弟节点的颜色由红色变为黑色,好对以下两种情况处理。简单说明,这个方法就是一个过度的方法。

        - case4: 被调整节点的兄弟为黑色,两个侄子都为黑色

                步骤一:将兄弟节点的颜色变为红,目的是将删除节点和兄弟那边的黑色高度同时减少1

                步骤二:如果父亲是红,则需要将父亲变为黑色,避免红红相邻,此时路径黑色节点数目不变。

                步骤三:如果父亲是黑色,说明这条路径则少了一个黑,再次让父亲节点触发双黑。

如图:

        调整节点为节点 1 设为 node ,兄弟节点为节点 2 设为 brother,父亲节点为节点 2 设为 parent 。该情况满足了删除节点与剩余节点都是黑色,触发双黑,且兄弟节点的颜色也为黑色,父亲节点的颜色为红色。调整的方法为:将兄弟节点的颜色变为红色,将父亲节点的颜色变为黑色即可。

调整完之后的图:

        

如图:

        调整节点为节点 1 设为 node ,兄弟节点为节点 3 设为 brother ,父亲节点为节点 2 设为 parent 。该情况满足调整节点与剩余节点的颜色都为黑色,触发双黑,且兄弟节点的颜色也为黑色,父亲节点也都为黑色。调整方法:将兄弟节点的颜色变为红色,再让父亲节点递归调用该函数,继续触发双黑。

调整完之后的图:

        - case5: 被调整节点的兄弟为黑色,至少有一个红色节点的侄子节点。

                如果兄弟是左孩子,左侄子是红色,触发 LL 不平衡

                如果兄弟是左孩子,右侄子是红色,触发 LR 不平衡

                如果兄弟是右孩子,右侄子是红色,触发 RR 不平衡

                如果兄弟是右孩子,左侄子是红色,触发 RL 不平衡

如图: 触发 LL 不平衡情况

        调整节点为节点 4 设为 node ,兄弟节点为节点 2 设为 brother ,父亲节点为节点 3 设为 parent ,侄子节点为节点 1 。兄弟节点与侄子节点都是左孩子,且侄子节点为红色。调整方法:将父亲节点进行右旋,再把侄子节点的颜色变为黑色,兄弟节点的颜色变为原来父亲节点的颜色,父亲节点的颜色变为黑色。

调整完之后的图:

如图:触发 LR 不平衡情况

        调整节点为节点 4 设为 node ,父亲节点为节点 3 设为 parent ,兄弟节点为节点 1 设为 brother ,侄子节点为节点 2 。兄弟节点是左孩子,侄子为右孩子,触发了 LR 不平衡。调整方法:先将兄弟节点左旋,变成了 LL 不平衡情况。

        再把侄子节点的颜色变为原来父亲节点的颜色,再到父亲节点的颜色变为黑色,最后右旋父亲节点即可。

调整完之后的图:

        

        还有 RR 、RL 的情况与以上的情况大致是一样的,所以这里就不过多赘述了。

删除节点的代码如下:

    //查找删除节点
    private TreeNode findDelete(int key) {
        TreeNode p = root;
        while(p != null) {
            if (p.key > key) {
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            }else {
                return p;
            }
        }
        //若没有找到则返回null
        return null;
    }

    //查找剩余节点
    private TreeNode findReplaced(TreeNode deleted) {
        //没有孩子的情况:
        if (deleted.left == null && deleted.right == null) {
            return null;
        }
        if (deleted.left == null) {
            return deleted.right;
        }
        if (deleted.right == null) {
            return deleted.left;
        }

        //有两个孩子的情况,找后继节点即可
        TreeNode p = deleted.right;
        while(p.left != null) {
            p = p.left;
        }
        return p;
    }

    //删除节点
    //正常删除节点,遇到黑黑不平衡则需要进行调整
    public void remove(int key) {
        TreeNode delete = findDelete(key);
        if (delete == null) {
            return;
        }
        doRemove(delete);

    }

    private void doRemove(TreeNode deleted) {
        TreeNode replaced = findReplaced(deleted);
        TreeNode parent = deleted.parent;
        //没有孩子的情况:
        if (replaced == null) {
            //删除的节点为根节点情况下:
            if (deleted == root) {
                root = null;
                return;
            }else {
                if (isRed(deleted)) {
                    //无需任何操作
                }else {
                    //触发黑黑不平衡,需要进行复杂的操作
                    fixBlackBlack(deleted);
                }
                if (deleted.isLeftChild()) {
                    parent.left = null;
                }else {
                    parent.right = null;
                }
                deleted.parent = null;
            }
            return;
        }
        //有一个孩子的情况
        if (deleted.left == null || deleted.right == null) {
            if (deleted == root) {
                root.key = replaced.key;
                root.value = replaced.value;
                root.left = root.right = null;
            }else {
                if (deleted.isLeftChild()) {
                    parent.left = replaced;
                } else {
                    parent.right = replaced;
                }
                replaced.parent = parent;
                deleted.left = deleted.right = deleted.parent = null;

                if (isRed(replaced) && isBlack(deleted)) {
                    //却少一个黑色,则将替换的节点换为红色即可
                    replaced.color = BLACK;
                }else {
                    //遇到黑黑不平衡情况,则需要进行复杂调整
                    fixBlackBlack(replaced);
                }
            }
            return;
        }
        //有两个孩子的情况,需要将用到李代桃僵技巧
        int key = deleted.key;
        deleted.key = replaced.key;
        replaced.key = key;

        Object value = deleted.value;
        deleted.value = replaced.value;
        replaced.value = value;
        doRemove(replaced);

    }
    private void fixBlackBlack(TreeNode node) {
        if (node == root) {
            return;
        }
        TreeNode parent = node.parent;
        TreeNode brother = node.brother();

        if (isRed(node.brother())) {
            //先进行旋转调整,再换色暂时达到平衡
            if (brother.isLeftChild()) {
                rightRotate(parent);
            }else {
                leftRotate(parent);
            }
            parent.color = RED;
            brother.color = BLACK;
            fixBlackBlack(node);
            return;
        }
        //两个侄子都为黑色
        if (brother == null) {
            fixBlackBlack(parent);
        }else {
            //case 4 兄弟是黑色,两个侄子也是黑色
            if (isBlack(brother.left) && isBlack(brother.right)) {
                brother.color = RED;
                if (isRed(parent)) {
                    parent.color = BLACK;
                }else {
                    fixBlackBlack(parent);
                }
            }

            //case 5 兄弟是黑色,侄子有红色
            else {
                //其中某一个侄子不为黑色
                //兄弟为左孩子、侄子为左孩子,触发 ll
                if (brother.isLeftChild() && isRed(brother.left)) {
                    rightRotate(parent);
                    brother.left.color = BLACK;
                    brother.color = parent.color;
                    parent.color = BLACK;
                } else if (brother.isLeftChild() && isRed(brother.right)) {
                    //兄弟为左孩子、侄子为右孩子,先触发 lr
                    //需要将 lr 转变为 ll 情况再处理
                    brother.right.color = parent.color;
                    leftRotate(brother);
                    rightRotate(parent);
                    parent.color = BLACK;
                } else if ( !brother.isLeftChild() && isRed(brother.right)) {
                    //兄弟为右孩子,侄子为右孩子,触发 rr
                    leftRotate(parent);
                    brother.right.color = BLACK;
                    brother.color = parent.color;
                    parent.color = BLACK;
                }else {
                    //最后一种情况兄弟为右孩子、侄子为左孩子,触发 rl
                    //需要将 rl 转变为 rr 情况再处理
                    brother.left.color = parent.color;
                    rightRotate(brother);
                    leftRotate(parent);
                    parent.color = BLACK;

                }
            }
        }

    }

        5.0 实现红黑树核心方法的完整代码

import static TreeNode.RedBlackTree.Color.BLACK;
import static TreeNode.RedBlackTree.Color.RED;

public class RedBlackTree {

    enum Color {
        RED,BLACK;
    }

    private TreeNode root;

    private static class TreeNode {
        int key;
        Object value;
        TreeNode left;
        TreeNode right;
        TreeNode parent;
        Color color = RED;

        //构造方法
        public TreeNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public TreeNode(int key, Object value, TreeNode left, TreeNode right, TreeNode parent) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.parent = parent;
        }

        //判断是否为左孩子
         public boolean isLeftChild() {
             return parent != null && parent.left == this;
         }

         //获取叔叔节点
         public TreeNode uncle() {
             if (this.parent == null || this.parent.parent == null) {
                 return null;
             }
             if (this.isLeftChild()) {
                 return this.parent.parent.right;
             } else {
                 return this.parent.parent.left;
             }
         }

         //获取兄弟节点
         public TreeNode brother() {
             if (this.parent == null) {
                 return null;
             }
             if (this.isLeftChild()) {
                 return this.parent.right;
             }else {
                 return this.parent.left;
             }
         }


    }

    //判断是否为红色节点
    private boolean isRed(TreeNode node) {
        return node != null && node.color == RED;
    }

    //判断是否为黑色节点
    private  boolean isBlack(TreeNode node) {
        return node == null || node.color == BLACK;
    }

    //右旋
    //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系
    private void rightRotate(TreeNode node) {
        TreeNode parent = node.parent;
        TreeNode nodeLeft = node.left;
        TreeNode nodeLeftRight = nodeLeft.right;
        if (nodeLeftRight != null) {
            nodeLeftRight.parent = node;
        }
        nodeLeft.right = node;
        nodeLeft.parent = parent;
        node.left = nodeLeftRight;
        node.parent = nodeLeft;
        if (parent == null) {
            root = nodeLeft;
        } else if (parent.left == node) {
            parent.left = nodeLeft;
        }else {
            parent.right = nodeLeft;
        }

    }

    //左旋
    //1.考虑旋转后节点的维护parent 2.重新与上一个节点建立联系
    private void leftRotate(TreeNode node) {
        TreeNode parent = node.parent;
        TreeNode nodeRight = node.right;
        TreeNode nodeRightLeft = nodeRight.left;
        if (nodeRightLeft != null) {
            nodeRightLeft.parent = node;
        }
        nodeRight.left = node;
        nodeRight.parent = parent;
        node.right = nodeRightLeft;
        node.parent = nodeRight;
        //2.重新与上一个节点建立联系
        if (parent == null) {
            root = nodeRight;
        } else if (parent.left == node) {
            parent.left = nodeRight;
        }else {
            parent.right = nodeRight;
        }

    }

    //更新、增添节点
    //正常更新、删除,遇到红红不平衡则需要进行调整
    public void put (int key, Object value) {
        TreeNode p = root;
        TreeNode parent = null;
        while (p != null) {
            parent = p;
            if (p.key > key) {
                p = p.left;
            }else if (p.key < key) {
                p = p.right;
            }else {
                p.value = value;
                return;
            }
        }
        TreeNode node = new TreeNode(key,value);
        if (parent == null) {
            root = node;
        }else {
            if (node.key > parent.key) {
                parent.right = node;

            }else {
                parent.left = node;

            }
            node.parent = parent;
        }
        //可能会发生红红不平衡,则需要调整
        fixRedRed(node);

    }
    //调整红红不平衡

    private void fixRedRed(TreeNode node) {
        //case1: 插入节点为根节点,将根节点变黑
        if(node == root) {
            node.color = BLACK;
            return;
        }
        if (isBlack(node.parent)) {
            //case2:插入节点的父亲若为黑,树的红黑性质不变,无需调整
            //无需调整
            return;
        }
        // 插入节点的父亲为红色,触发红红相邻
        //case3:叔叔为红色
        TreeNode parent = node.parent;
        TreeNode grandparent = parent.parent;
        TreeNode uncle = node.uncle();
        if (isRed(uncle)) {
            //进行变色处理即可
            //将其父亲、叔叔变为黑色,爷爷变为红色
            //若爷爷触发了红红,则继续递归调用该函数
            parent.color = BLACK;
            uncle.color = BLACK;
            grandparent.color = RED;
            fixRedRed(grandparent);
            return;
        }
        
        //case4:叔叔为黑色
        //该父亲为左孩子,该插入点也为左孩子,则触发 ll
        if (parent.isLeftChild() && node.isLeftChild()) {
            //先将父亲变为黑色、爷爷变为红色,再右旋转
            parent.color = BLACK;
            grandparent.color = RED;
            rightRotate(grandparent);
        }else if (parent.isLeftChild()) {
            //该插入节点为右孩子、该父亲为左孩子,则触发 lr
            //先左旋变为 ll 情况
            leftRotate(parent);
            node.color = BLACK;
            grandparent.color = RED;
            rightRotate(grandparent);
        } else if (!node.isLeftChild()) {
            //插入节点为右孩子、父亲节点也为右孩子 rr
            parent.color = BLACK;
            grandparent.color = RED;
            leftRotate(grandparent);
        }else {
            //插入节点为左孩子、父亲节点为右孩子 rl
            rightRotate(parent);
            node.color = BLACK;
            grandparent.color = RED;
            leftRotate(grandparent);

        }
    }

    //查找删除节点
    private TreeNode findDelete(int key) {
        TreeNode p = root;
        while(p != null) {
            if (p.key > key) {
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            }else {
                return p;
            }
        }
        //若没有找到则返回null
        return null;
    }

    //查找剩余节点
    private TreeNode findReplaced(TreeNode deleted) {
        //没有孩子的情况:
        if (deleted.left == null && deleted.right == null) {
            return null;
        }
        if (deleted.left == null) {
            return deleted.right;
        }
        if (deleted.right == null) {
            return deleted.left;
        }

        //有两个孩子的情况,找后继节点即可
        TreeNode p = deleted.right;
        while(p.left != null) {
            p = p.left;
        }
        return p;
    }

    //删除节点
    //正常删除节点,遇到黑黑不平衡则需要进行调整
    public void remove(int key) {
        TreeNode delete = findDelete(key);
        if (delete == null) {
            return;
        }
        doRemove(delete);

    }

    private void doRemove(TreeNode deleted) {
        TreeNode replaced = findReplaced(deleted);
        TreeNode parent = deleted.parent;
        //没有孩子的情况:
        if (replaced == null) {
            //删除的节点为根节点情况下:
            if (deleted == root) {
                root = null;
                return;
            }else {
                if (isRed(deleted)) {
                    //无需任何操作
                }else {
                    //触发黑黑不平衡,需要进行复杂的操作
                    fixBlackBlack(deleted);
                }
                if (deleted.isLeftChild()) {
                    parent.left = null;
                }else {
                    parent.right = null;
                }
                deleted.parent = null;
            }
            return;
        }
        //有一个孩子的情况
        if (deleted.left == null || deleted.right == null) {
            if (deleted == root) {
                root.key = replaced.key;
                root.value = replaced.value;
                root.left = root.right = null;
            }else {
                if (deleted.isLeftChild()) {
                    parent.left = replaced;
                } else {
                    parent.right = replaced;
                }
                replaced.parent = parent;
                deleted.left = deleted.right = deleted.parent = null;

                if (isRed(replaced) && isBlack(deleted)) {
                    //却少一个黑色,则将替换的节点换为红色即可
                    replaced.color = BLACK;
                }else {
                    //遇到黑黑不平衡情况,则需要进行复杂调整
                    fixBlackBlack(replaced);
                }
            }
            return;
        }
        //有两个孩子的情况,需要将用到李代桃僵技巧
        int key = deleted.key;
        deleted.key = replaced.key;
        replaced.key = key;

        Object value = deleted.value;
        deleted.value = replaced.value;
        replaced.value = value;
        doRemove(replaced);

    }
    private void fixBlackBlack(TreeNode node) {
        if (node == root) {
            return;
        }
        TreeNode parent = node.parent;
        TreeNode brother = node.brother();

        if (isRed(node.brother())) {
            //先进行旋转调整,再换色暂时达到平衡
            if (brother.isLeftChild()) {
                rightRotate(parent);
            }else {
                leftRotate(parent);
            }
            parent.color = RED;
            brother.color = BLACK;
            fixBlackBlack(node);
            return;
        }
        //两个侄子都为黑色
        if (brother == null) {
            fixBlackBlack(parent);
        }else {
            //case 4 兄弟是黑色,两个侄子也是黑色
            if (isBlack(brother.left) && isBlack(brother.right)) {
                brother.color = RED;
                if (isRed(parent)) {
                    parent.color = BLACK;
                }else {
                    fixBlackBlack(parent);
                }
            }

            //case 5 兄弟是黑色,侄子有红色
            else {
                //其中某一个侄子不为黑色
                //兄弟为左孩子、侄子为左孩子,触发 ll
                if (brother.isLeftChild() && isRed(brother.left)) {
                    rightRotate(parent);
                    brother.left.color = BLACK;
                    brother.color = parent.color;
                    parent.color = BLACK;
                } else if (brother.isLeftChild() && isRed(brother.right)) {
                    //兄弟为左孩子、侄子为右孩子,先触发 lr
                    //需要将 lr 转变为 ll 情况再处理
                    brother.right.color = parent.color;
                    leftRotate(brother);
                    rightRotate(parent);
                    parent.color = BLACK;
                } else if ( !brother.isLeftChild() && isRed(brother.right)) {
                    //兄弟为右孩子,侄子为右孩子,触发 rr
                    leftRotate(parent);
                    brother.right.color = BLACK;
                    brother.color = parent.color;
                    parent.color = BLACK;
                }else {
                    //最后一种情况兄弟为右孩子、侄子为左孩子,触发 rl
                    //需要将 rl 转变为 rr 情况再处理
                    brother.left.color = parent.color;
                    rightRotate(brother);
                    leftRotate(parent);
                    parent.color = BLACK;

                }
            }
        }

    }

}

                                        

                                                   - - - 努力要成为自己想要称为的人

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s中服务器容器tcp连接数量优化

netty的http1服务器在运行一段时间后会无法提供服务&#xff0c;返回客户端socket hang up 使用apipost测试抓包显示三次握手后被reset 客户端使用了大量短连接&#xff0c;如果能改成长连接就会消耗更少的连接&#xff0c;但是客户端逻辑无法掌控&#xff0c;只能修改服务器。…

网络安全概述---笔记总结

网络安全概述 网络安全---Cyberspace security 2003年美国提出网络空间的概念 --- 一个由信息基础设施组成的互相依赖的网络。我国官方文件定义&#xff1a;网络空间为继海&#xff0c;陆&#xff0c;空&#xff0c;天以外的第五大人类活动领域 发展阶段&#xff1a; 通信保…

学习STM32,该用哪款开发工具?

有很多初学者在问&#xff1a;学习STM32&#xff0c;该用哪款开发工具&#xff1f; 我首先说一下我的观点&#xff1a; 1.没有最好&#xff0c;只有适不适合&#xff0c;适合自己的才是最好的。 2.开发工具很多&#xff0c;各有各的特点&#xff0c;有优点肯定也有缺点。 本文…

MySQL InnoDB 底层数据存储

InnoDB 页记录Page Directory记录迁移 页 是内存与磁盘交互的基本单位&#xff0c;16kb。 比如&#xff0c;查询的时候&#xff0c;并不是只从磁盘读取某条记录&#xff0c;而是记录所在的页 记录 记录的物理插入是随机的&#xff0c;就是在磁盘上的位置是无序的。但是在页中…

vscode连不上虚拟机,一直密码错误

最近在做毕设&#xff0c;但是vscode使用连接不上虚拟机&#xff0c;我以为是网络配置的问题&#xff0c;一顿查阅没找到原因。 后来查了一下ssh的日志&#xff0c;发现ssh有消息&#xff0c;但是也提示密码错误。 没找到密码配置格式什么的&#xff0c;经查看sshd配置文件发现…

easyexcel导入合并单元格解析(纵向合并,横向合并都支持)

1、按照开发逻辑&#xff0c;首先定义导入接收实体 package com.wang.test.excel;import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data;/***/ Data public class ExcelData1 {ExcelProperty(index 0)private String name;ExcelProperty(index 1)private S…

[C++] external “C“的作用和使用场景(案例)

C中extern "C"的作用是什么&#xff1f; 在 C 中&#xff0c;extern "C" 的作用是告诉编译器按照 C 语言的规范来处理函数名和变量名。这是因为 C 编译器会对函数名和变量名进行名称修饰(name mangling)&#xff0c;以区分不同的函数和变量。而在 C 语言中…

企业需要的3种供应商管理解决方案

传统的 "管理和监控 "供应商管理解决方案是对时间和金钱的巨大浪费。准入、资格认证和细分等孤立的供应商管理流程无法与其他采购流程整合在一起。 此外&#xff0c;从多个来源获取和管理供应商数据的过程只会增加固有的复杂性。而且&#xff0c;人工操作往往会延误…

【Java IO】设计模式 (装饰者模式)

Java I/O 使用了装饰者模式来实现。 装饰者模式 请参考装饰者模式详解 装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component)&#xff0c;具体组件的方法实现不需要依赖于其它对象&#xff0c;而装饰者组合了一个组件&#xff0c;这样它可以装饰其它装饰者…

Linux中文件IO(open、read、write、close函数使用)

介绍 什么是文件IO&#xff1f; 是操作系统提供的API接口函数。 POSIX接口 &#xff08;了解&#xff09; 注意&#xff1a;文件IO不提供缓冲机制 文件IO的API open close read read 文件描述符概念&#xff1a; 英文&#xff1a;缩写fd&#xff08;file descriptor&#xff…

前端和后端之间的CORS 跨域和解决办法

什么是CORS&#xff08;Cross-Origin Resource Sharing&#xff0c;跨源资源共享&#xff09; 跨源资源共享&#xff08;CORS&#xff0c;或通俗地译为跨域资源共享&#xff09;是一种基于 HTTP 头的机制&#xff0c;该机制通过允许服务器标示除了它自己以外的其他源&#xff0…

go 语言中 json.Unmarshal([]byte(jsonbuff), j) 字节切片得使用场景

struct_tag的使用 在上面的例子看到&#xff0c;我们根据结构体生成的json的key都是大写的&#xff0c;因为结构体名字在go语言中不大写的话&#xff0c;又没有访问权限&#xff0c;这种问题会影响到我们对json的key的名字&#xff0c;所以go官方给出了struct_tag的方法去修改…

【C++杂货铺】三分钟彻底搞懂如何使用C++中max函数

&#x1f308;前言 欢迎收看本期【C杂货铺】&#xff0c;这期内容&#xff0c;我们将围绕C中max函数部分进行讲解&#xff0c;包含了如何查询库函数&#xff0c;max函数的使用方法灯。如果你想学习C&#xff0c;或者刚学完C语言衔接C&#xff0c;那么这篇文章将会非常有效的帮助…

Redis学习——入门篇①

Redis学习——入门篇① 1、2&#xff1a;Redis入门概述3&#xff1a;Redis安装配置10 安装Redis-cli命令 4&#xff1a;Redis——十大数据类型11 redis数据类型12 命令查阅13 key常用命令介绍14 类型大小写和帮助命令15 String 命令&#xff08;上&#xff09;16 String 命令&a…

电脑摄像头设置在哪里?这3种打开方式要记好!

“我需要开一个视频会议&#xff0c;因此可能需要用到电脑的摄像头。但是我不知道我电脑的摄像头设置在哪里&#xff0c;有没有朋友可以告诉我一下呀&#xff1f;” 在日常办公时&#xff0c;我们可能经常要用到电脑摄像头。这不仅为用户提供了实时沟通的机会&#xff0c;还帮助…

机器学习实验报告——APRIORI算法

目录 一、算法介绍 1.1算法背景 1.2算法引入 1.3算法假设 1.4算法基本概念介绍 1.4.1关联规则 1.4.2支持度 1.4.3置信度 1.4.4频繁项集 1.4.5项目 1.4.6提升度 二、算法原理 2.1算法思想 2.2Apriori算法产生频繁项集 2.3Apriori算法的基本步骤 2.4关联分析 三、算法实现 3.1 Ap…

安卓自动化 | autox.js

介绍: 不需要Root权限 的 JavaScript 自动化软件 官方文档: http://doc.autoxjs.com/ 开发准备 安装Releases kkevsekk1/AutoX (github.com)到手机上 安装vscode插件 插件介绍中有中文的使用说明, 如何连接电脑 Usage Step 1 按 CtrlShiftP 或点击"查看"->&…

司铭宇老师:房地产中介培训班:房地产中介培训课程

房地产中介培训班&#xff1a;房地产中介培训课程 在竞争激烈的房地产市场中&#xff0c;成为一名杰出的中介代表不仅需要对行业有深刻的理解&#xff0c;还要具备一系列专业技能和高效的销售策略。我们的房地产中介培训课程旨在为有志于提升个人能力和业绩的中介人员提供全面、…

C++: vector

目录 1.vector的介绍 2.vector常用的接口 1.vector构造 2.迭代器iterator的使用 3.vector空间增长 4.vector的增删改查 3.vector模拟实现 如果在reverse时使用memcpy会怎么样&#xff1f; 1.vector的介绍 C中的vector是一个动态数组容器&#xff0c;可以存储任意类型的…

element plus表格的表头和内容居中

文章目录 需求分析 需求 对于 element-plus 中的 table 进行表头和内容的居中显示 分析 单列的表头和内容居中 &#xff1a; 在对应的那一列加上align“center” 即可 <el-table-column prop"name" label"商品名称" align"center" />…