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;}}}}}

                                        

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

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

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

相关文章

软件工程实验报告(完整)

博主介绍&#xff1a;✌全网粉丝喜爱、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦&#xff01; &#x1f345;附上相关C语言版源码讲解&#x1f345; &#x1f44…

openGauss学习笔记-205 openGauss 数据库运维-常见故障定位案例-业务运行时整数转换错

文章目录 openGauss学习笔记-205 openGauss 数据库运维-常见故障定位案例-业务运行时整数转换错205.1 业务运行时整数转换错205.1.1 问题现象205.1.2 原因分析205.1.3 处理办法 openGauss学习笔记-205 openGauss 数据库运维-常见故障定位案例-业务运行时整数转换错 205.1 业务…

Java21 + SpringBoot3集成easy-captcha实现验证码显示和登录校验

文章目录 前言相关技术简介easy-captcha 实现步骤引入maven依赖定义实体类定义登录服务类定义登录控制器前端登录页面实现测试和验证 总结附录使用Session缓存验证码前端登录页面实现代码 前言 近日心血来潮想做一个开源项目&#xff0c;目标是做一款可以适配多端、功能完备的…

虚拟机下载docker

一&#xff0c;Docker简介 百科说&#xff1a;Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化&#xff0c;容器是完全使用沙箱机制&#xff…

CentOS 7安装全解析

目录 一.centos安装1.1 下载镜像文件1.2 安装 二.远程连接&#xff0c;换源2.1 下载并且使用MobaXterm2.2 远程连接2.3 换源 一.centos安装 1.1 下载镜像文件 https://mirrors.aliyun.com/centos/7/isos/x86_64/ 下载即可 1.2 安装 二.远程连接&#xff0c;换源 2.1 下载并…

租幻兽帕鲁Palworld服务器多少钱?

使用腾讯云服务器搭建搭建幻兽帕鲁Palworld如何选择服务器配置&#xff1f;腾讯云百科txybk.com建议幻兽帕鲁选择腾讯云轻量应用服务器4核16G14M带宽&#xff0c;Ubuntu/Debian系统。如何收费&#xff1f; 腾讯云幻兽帕鲁服务器活动 https://curl.qcloud.com/oRMoSucP 轻量应用…

C#,入门教程(28)——文件夹(目录)、文件读(Read)与写(Write)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(27)——应用程序&#xff08;Application&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/125094837 C#知识比你的预期简单的多&#xff0c;但也远远超乎你的想象&#xff01; 与文件相关的知识&#xf…

记一次低级且重大的Presto运维事故

本文纯属虚构&#xff0c;旨在提醒各位别犯类似低级错误。 如有雷同&#xff0c;说的就是你&#xff01; 文章目录 前言事件回顾后续总结 前言 首先&#xff0c;要重视运维工作和离职人员的交接工作&#xff0c;这个不必多说。一将无能&#xff0c;累死三军&#xff01; 接下来…

目标检测难题 | 小目标检测策略汇总

大家好&#xff0c;在计算机视觉中&#xff0c;检测小目标是最有挑战的问题之一&#xff0c;本文给出了一些有效的策略。 从无人机上看到的小目标 为了提高模型在小目标上的性能&#xff0c;本文推荐以下技术&#xff1a; 提高图像采集的分辨率 增加模型的输入分辨率 tile你…

3DMAX初级小白班第一课:菜单栏介绍

基本介绍 这里不可能一个一个选项全部教给大家&#xff08;毕竟之后靠实操慢慢就记住了&#xff09;&#xff0c;只说一些相对需要注意的设置。 自定义-热键编辑器-热键设置 这里有你所需要的全部快捷键 自定义-自定义UI启动布局 将UI布局还原到启动的位置 自定义-通用单…

【Linux配置yum源以及基本yum指令】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、yum是什么&#xff1f; 二、什么是软件包&#xff1f; 三、三种安装软件包的方式 四、yum的相关操作 4.1、搜索软件 4.2、安装软件 4.3、卸载软件 4.4、那…

操作系统-进程的概念,组成,特征(PCB 程序如何运行)

文章目录 总览进程的概念进程的组成-PCBPCB中存放的内容程序是如何运行的进程的组成-程序段&#xff0c;数据段进程的特征小结 总览 进程的概念 任务管理器&#xff1a;显示运行的进程 打开qq前后 打开三个qq&#xff0c;有三个进程 进程的组成-PCB PCB包含进程的很多信息 …

AI搜索引擎Perplexity来了,谷歌等老牌搜索引擎或许会有新的威胁?

Perplexity AI 是一家 AI 搜索初创公司&#xff0c;它通过结合内容索引技术和大型语言模型的推理能力&#xff0c;提供更便捷和高效的搜索体验。另外&#xff0c;最近很火的小兔子Rabbit R1硬件AI设备中的搜索功能正是这家公司的杰作。在短短一年半的时间里&#xff0c;一个企业…

51单片机1-6

目录 单片机介绍 点亮一个LED 流水灯参考代码 点亮流水LEDplus版本 独立按键 独立按键控制LED亮灭 静态数码管 静态数码管显示 动态数码管显示 模块化编程 调试工具 矩阵键盘 矩阵键盘显示数据 矩阵键盘密码锁 学习B站江协科技课程笔记。 安装keil&#xff0c;下…

Qt配置OpenCV

首先安装好Qt Createor&#xff0c;CMake&#xff0c;OpenCV,我本次使用的是Qt6.3.4和OpenCV4.6.0 Qt Creator清华镜像源:https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/qtcreator/OpenCV官网下载: https://opencv.org/releases/ 一. 编译OpenCV 首先使用Qt C…

three.js从入门到精通系列教程004 - three.js透视相机(PerspectiveCamera)滚动浏览全景大图

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>three.js从入门到精通系列教程004 - three.js透视相机&#xff08;PerspectiveCamera&#xff09;滚动浏览全景大图</title><script src"js/three.js"&g…

ThinkPHP5.0.0~5.0.23路由控制不严谨导致的RCE

本次我们继续以漏洞挖掘者的视角&#xff0c;来分析thinkphp的RCE 敏感函数发现 在调用入口函数&#xff1a;/ThinkPHP_full_v5.0.22/public/index.php 时 发现了框架底层调用了\thinkphp\library\think\App.php的app类中的incokeMethod方法 注意传递的参数&#xff0c;Refle…

shopee最新选品:Shopee平台上的最新选品策略和方法

在Shopee平台上进行选品是卖家们必须经历的重要步骤。通过精心选择和定位产品&#xff0c;卖家可以提高产品的市场接受度和销售业绩。然而&#xff0c;要在竞争激烈的电商市场中脱颖而出&#xff0c;并不是一件容易的事情。本文将介绍一些在Shopee平台上进行最新选品时可以采用…

打造专业级ChatGPT风格聊天界面:SpringBoot与Vue实现动态打字机效果,附完整前后端源码

大家好&#xff0c;今天用SpringBoot、vue写了一个仿ChatGPT官网聊天的打字机效果。 所有代码地址:gitee代码地址 &#xff0c;包含前端和后端&#xff0c;可以直接运行 使用本技术实现的项目&#xff1a;aicnn.cn&#xff0c;欢迎大家体验 如果文章知识点有错误的地方&#xf…

【源码】医院绩效管理系统,针对医、护、技、药、管不同岗位,可设置不同的核算方法、核算参数

医院绩效管理系统源码 医院绩效管理系统以国家医院绩效管理考核政策法规为依据&#xff0c;结合医院管理实践&#xff0c;以经济管理指标为核心&#xff0c;医疗质量、安全、效率、效益管理为重点&#xff0c;特别强调持续改进&#xff08;PDCA&#xff09;管理理念。实现医院绩…