当前位置: 首页 > news >正文

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

 🔥博客主页: 【小扳_-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:叔叔为黑色//该父亲为左孩子,该插入点也为左孩子,则触发 llif (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()) {//插入节点为右孩子、父亲节点也为右孩子 rrparent.color = BLACK;grandparent.color = RED;leftRotate(grandparent);}else {//插入节点为左孩子、父亲节点为右孩子 rlrightRotate(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;}}//若没有找到则返回nullreturn 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 {//其中某一个侄子不为黑色//兄弟为左孩子、侄子为左孩子,触发 llif (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)) {//兄弟为右孩子,侄子为右孩子,触发 rrleftRotate(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:叔叔为黑色//该父亲为左孩子,该插入点也为左孩子,则触发 llif (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()) {//插入节点为右孩子、父亲节点也为右孩子 rrparent.color = BLACK;grandparent.color = RED;leftRotate(grandparent);}else {//插入节点为左孩子、父亲节点为右孩子 rlrightRotate(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;}}//若没有找到则返回nullreturn 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 {//其中某一个侄子不为黑色//兄弟为左孩子、侄子为左孩子,触发 llif (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)) {//兄弟为右孩子,侄子为右孩子,触发 rrleftRotate(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;}}}}}

                                        

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

相关文章:

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

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 红黑树的说明 2.0 红黑树的特性 3.0 红黑树的成员变量及其构造方法 4.0 实现红黑树的核心方法 4.1 红黑树内部类的核心方法 &#xff08;1&#xff09;判断当前…...

【实战】一、Jest 前端自动化测试框架基础入门(中) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(二)

文章目录 一、Jest 前端自动化测试框架基础入门5.Jest 中的匹配器toBe 匹配器toEqual匹配器toBeNull匹配器toBeUndefined匹配器和toBeDefined匹配器toBeTruthy匹配器toBeFalsy匹配器数字相关的匹配器字符串相关的匹配器数组相关的匹配器异常情况的匹配器 6.Jest 命令行工具的使…...

【C语言 - 力扣 - 反转链表】

反转链表题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 题解1-迭代 假设链表为 1→2→3→∅&#xff0c;我们想要把它改成 ∅←1←2←3。 在遍历链表时&#xff0c;将当前节点的 next 指针改为指向前一个节点。由于节点没…...

ctfshow-php特性(web102-web115)

目录 web102 web103 web104 web105 web106 web107 web108 web109 web110 web111 web112 web113 web114 web115 实践是检验真理的 要多多尝试 web102 <?php highlight_file(__FILE__); $v1$_POST[V1]; $v2$_GET[v2]; $v3$_GET[v3]; $v4is_numeric($v2)and is…...

python系统学习Day1

section1 python introduction 文中tips只做拓展&#xff0c;可跳过。 PartOne introduction 首先要对于python这门语言有一个宏观的认识&#xff0c;包括特点和应用场景。 特点分析&#xff1a; 优势 提供了完善的基础代码库&#xff0c;许多功能不必从零编写简单优雅 劣势 运…...

Idea里自定义封装数据警告解决 Spring Boot Configuration Annotation Processor not configured

我们自定对象封装指定数据&#xff0c;封装类上面一个红色警告&#xff0c;虽然不影响我们的执行&#xff0c;但是有强迫症看着不舒服&#xff0c; 去除方式&#xff1a; 在pom文件加上坐标刷新 <dependency><groupId>org.springframework.boot</groupId><…...

【流程图——讲解】

流程图介绍 流程图介绍 流程图介绍 流程图是一种图表&#xff0c;它展示了工作流程或过程中的步骤顺序&#xff0c;它通常由不同的符号表示&#xff0c;每个符号都代表一个步骤或过程中的一个元素&#xff0c;流程图非常有用&#xff0c;因为它们可以提供清晰、视觉化的过程表…...

「计算机网络」物理层

物理层的基本概念 物理层的作用&#xff1a;尽可能屏蔽掉不同传输媒体和通信手段的差异物理层规程&#xff1a;用于物理层的协议主要任务&#xff1a;确定与传输媒体的接口有关的一些特性 机械特性电器特性功能特性过程特性 数据通信的基础知识 数据通信系统的模型 划分为…...

ARM与X86架构的区别与联系

文章目录 1.什么是CPU2.复杂指令集和精简指令集3.ARM架构与X86架构的比较3.1.制造工艺3.2 64位计算3.3 异构计算3.4 功耗 4.ARM和X86的发展现状Reference 1.什么是CPU 中央处理单元&#xff08;CPU&#xff09;主要由运算器、控制器、寄存器三部分组成&#xff0c;从字面意思看…...

蓝桥杯每日一题------背包问题(二)

前言 本次讲解背包问题的一些延申问题&#xff0c;新的知识点主要涉及到二进制优化&#xff0c;单调队列优化DP&#xff0c;树形DP等。 多重背包 原始做法 多重背包的题意处在01背包和完全背包之间&#xff0c;因为对于每一个物品它规定了可选的个数&#xff0c;那么可以考虑…...

牛客错题整理——C语言(实时更新)

1.以下程序的运行结果是&#xff08;&#xff09; #include <stdio.h> int main() { int sum, pad,pAd; sum pad 5; pAd sum, pAd, pad; printf("%d\n",pAd); }答案为7 由于赋值运算符的优先级高于逗号表达式&#xff0c;因此pAd sum, pAd, pad;等价于(…...

CIFAR-10数据集详析:使用卷积神经网络训练图像分类模型

1.数据集介绍 CIFAR-10 数据集由 10 个类的 60000 张 32x32 彩色图像组成&#xff0c;每类 6000 张图像。有 50000 张训练图像和 10000 张测试图像。 数据集分为5个训练批次和1个测试批次&#xff0c;每个批次有10000张图像。测试批次正好包含从每个类中随机选择的 1000 张图像…...

《傲剑狂刀》中的人物性格——龙吟风

在《傲剑狂刀》这款经典武侠题材的格斗游戏中,龙吟风作为一位具有传奇色彩的角色,其性格特征复杂且引人入胜。以下是对龙吟风这一角色的性格特点进行深度剖析: 一、孤高独立的剑客气质 龙吟风的名字本身就流露出一种独特的江湖气息,"吟风"象征着他的飘逸与淡泊名…...

KVM和JVM的虚拟化技术有何区别?

随着虚拟化技术的不断发展&#xff0c;KVM和JVM已成为两种主流的虚拟化技术。尽管它们都提供了虚拟化的解决方案&#xff0c;但它们在实现方式、功能和性能方面存在一些重要的差异。本文将深入探讨KVM和JVM的虚拟化技术之间的区别。 KVM&#xff08;Kernel-based Virtual Mac…...

LeetCode力扣 面试经典150题 详细题解 (1~5) 持续更新中

目录 1.合并两个有序数组 2.移动元素 3.删除有序数组中的重复项 4.删除排序数组中的重复项 II 5.多数元素 暂时更新到这里&#xff0c;博主会持续更新的 1.合并两个有序数组 题目&#xff08;难度&#xff1a;简单&#xff09;&#xff1a; 给你两个按 非递减顺序 排列的…...

如何解决利用cron定时任务自动更新SSL证书后Nginx重启问题

利用cron定时任务自动更新SSL证书后&#xff0c;用浏览器访问网站&#xff0c;获取到的证书仍然是之前的。原因在于没有对Nginx进行重启。 据说certbot更新完成证书后会自动重启Nginx,但显然经我检测不是这回事儿。 所以我们需要创建一bash脚本&#xff0c;然后定时调用这个脚…...

第一个 Angular 项目 - 静态页面

第一个 Angular 项目 - 静态页面 之前的笔记&#xff1a; [Angular 基础] - Angular 渲染过程 & 组件的创建 [Angular 基础] - 数据绑定(databinding) [Angular 基础] - 指令(directives) 这是在学完了上面这三个内容后能够完成的项目&#xff0c;目前因为还没有学到数…...

网络协议与攻击模拟_17HTTPS 协议

HTTPShttpssl/tls 1、加密算法 2、PKI&#xff08;公钥基础设施&#xff09; 3、证书 4、部署HTTPS服务器 部署CA证书服务器 5、分析HTTPS流量 分析TLS的交互过程 一、HTTPS协议 在http的通道上增加了安全性&#xff0c;传输过程通过加密和身份认证来确保传输安全性 1、TLS …...

【linux系统体验】-ubuntu简易折腾

ubuntu 一、终端美化二、桌面美化2.1 插件安装2.2 主题和图标2.3 美化配置 三、常用命令 以后看不看不重要&#xff0c;咱就是想记点儿东西。一、终端美化 安装oh my posh&#xff0c;参考链接&#xff1a;Linux 终端美化 1、安装字体 oh my posh美化工具可以使用合适的字体&a…...

Android 判断通知是进度条通知

1.需求: 应用监听安卓系统中的通知,需要区分出带进度条的通知. 当使用NotificationCompat.Builder构建一个通知时&#xff0c;可以通过调用setProgress(max, progress, indeterminate)方法来添加一个进度条。这里的max参数表示最大进度值&#xff0c;progress表示当前进度值&a…...

使用homeassistant 插件将tasmota 接入到米家

我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能&#xff0c;利用了巴法接入小爱的功能&#xff0c;将本地mqtt转发给巴法以实现小爱控制的功能&#xff0c;前提条件。1需要tasmota 设备&#xff0c; 2.在本地搭建了mqtt服务可&#xff0c; 3.搭建了ha 4.在h…...

qt 双缓冲案例对比

双缓冲 1.双缓冲原理 单缓冲&#xff1a;在paintEvent中直接绘制到屏幕&#xff0c;绘制过程被用户看到 双缓冲&#xff1a;先在redrawBuffer绘制到缓冲区&#xff0c;然后一次性显示完整结果 代码结构 单缓冲&#xff1a;所有绘制逻辑在paintEvent中 双缓冲&#xff1a;绘制…...

【图片转AR场景】Tripo + Blender + Kivicube 实现图片转 AR 建模

总览 1.将 2D 图片转为立体建模 2. 3. 一、将 2D 图片转为立体建模 1.工具介绍 Tripo 网站 2.找图片 找的图片必须是看起来能够让 AI 有能力识别和推理的&#xff0c;因为现在的AI虽然可以补全但是能力还没有像人的想象力那么丰富。 比如上面这张图片&#xff0c;看起来虽…...

JVM——对象模型:JVM对象的内部机制和存在方式是怎样的?

引入 在Java的编程宇宙中&#xff0c;“Everything is object”是最核心的哲学纲领。当我们写下new Book()这样简单的代码时&#xff0c;JVM正在幕后构建一个复杂而精妙的“数据实体”——对象。这个看似普通的对象&#xff0c;实则是JVM内存管理、类型系统和多态机制的基石。…...

基于 Transformer robert的情感分类任务实践总结之二——R-Drop

基于 Transformer robert的情感分类任务实践总结之一 核心改进点 1. R-Drop正则化 原理&#xff1a;通过在同一个输入上两次前向传播&#xff08;利用Dropout的随机性&#xff09;&#xff0c;强制模型对相同输入生成相似的输出分布&#xff0c;避免过拟合。实现&#xff1a…...

2. Web网络基础 - 协议端口

深入解析协议端口与netstat命令&#xff1a;网络工程师的实战指南 在网络通信中&#xff0c;协议端口是服务访问的门户。本文将全面解析端口概念&#xff0c;并通过netstat命令实战演示如何监控网络连接状态。 一、协议端口核心知识解析 1. 端口号的本质与分类 端口范围类型说…...

【Zephyr 系列 8】构建完整 BLE 产品架构:状态机 + AT 命令 + 双通道通信实战

🧠关键词:Zephyr、BLE、状态机、双向透传、AT 命令、Buffer、主从共存、系统架构 📌适合人群:希望开发 BLE 产品(模块/标签/终端)具备可控、可测、可维护架构的开发者 🧭 引言:从“点功能”到“系统架构” 前面几篇我们已经逐步构建了 BLE 广播、连接、数据透传系统…...

前端对WebSocket进行封装,并建立心跳监测

WebSocket的介绍&#xff1a; WebSocket 是一种在客户端和服务器之间进行全双工、双向通信的协议。它是基于 HTTP 协议&#xff0c;但通过升级&#xff08;HTTP 升级请求&#xff09;将连接转换为 WebSocket 协议&#xff0c;从而提供更高效的实时数据交换。 WebSocket 的特点…...

window安装docker\docker-compose

安装前配置 打开控制面板,参照下图打开“启动或关闭windows功能”,Hyper-V 和容器需要启用 程序和功能 启动或关闭windows功能 勾选Hyper-V 安装路径配置 Docker在Windows上的默认安装路径为C:\Program Files\Docker。 以管理员身份运行CMD在D盘,dev文件夹下创建Docker文…...

打通印染车间“神经末梢”:DeviceNet转Ethernet/IP连接机器人的高效方案

在印染行业自动化升级中&#xff0c;设备联网需求迫切。老旧印染设备多采用Devicenet协议&#xff0c;而新型工业机器人普遍支持Ethernet/IP协议&#xff0c;协议不兼容导致数据交互困难&#xff0c;设备协同效率低、生产监控滞后&#xff0c;成了行业数字化转型的阻碍。本文将…...