红黑树刨析(删除部分)

news2024/12/24 21:45:35

文章目录

    • 红黑树删除节点情景分析
      • 情景1:删除节点左右子树都为空
        • 情景1.1:删除节点为红色
        • 情景1.2:删除节点为黑色
          • 情况1.2.1:删除节点的兄弟节点是红色
          • 情景1.2.2:删除节点的兄弟节点是黑色
            • 情景1.2.2.1:删除节点的兄弟节点是黑色,兄弟节点的右节点是红色,兄弟节点的左节点是空或者红色
            • 情景1.2.2.2:删除节点的兄弟节点是黑色,兄弟节点的左节点是红色,兄弟节点的右节点是空(右节点是红色按照情况1.2.2.1讨论就可以了)
            • 情景1.2.2.3:删除节点的兄弟节点是黑色,兄弟节点无孩子
      • 情景2:删除节点有一个子树(左子树或者右子树)
      • 情景3:删除节点有两个子树

红黑树删除节点情景分析

情景1:删除节点左右子树都为空

情景1.1:删除节点为红色

那么可以直接将p删除,不影响平衡性

暂时无法在飞书文档外展示此内容

以下这两种情况都符合

在这里插入图片描述
在这里插入图片描述

情景1.2:删除节点为黑色

删除节点为黑色的话就不太平衡了,此时我们就需要看情况讨论了

情况1.2.1:删除节点的兄弟节点是红色

将父亲节点和兄弟节点颜色互换,然后再将父亲节点左旋,此时这就变成了情景1.2.2

在这里插入图片描述

情景1.2.2:删除节点的兄弟节点是黑色

如果兄弟节点为黑色,那么只有两种情况

情景1.2.2.1:删除节点的兄弟节点是黑色,兄弟节点的右节点是红色,兄弟节点的左节点是空或者红色

此时如果我们删除黑色节点p,那么就不平衡了,我们看一下pp - ppr - pprr三个节点,这三个点在一条线上,我们可以借助pprr这个红色节点变成黑色节点来保持平衡。首先让ppppr互换颜色,然后再将pp左旋,那么删除p之后,右边没有黑色节点,直接让pprr变成黑色就行了。在这里插入图片描述

情景1.2.2.2:删除节点的兄弟节点是黑色,兄弟节点的左节点是红色,兄弟节点的右节点是空(右节点是红色按照情况1.2.2.1讨论就可以了)

直接让兄弟右旋,然后将兄弟和侄子互换颜色,就变成了情景1.2.2.1
在这里插入图片描述

情景1.2.2.3:删除节点的兄弟节点是黑色,兄弟节点无孩子

情景1.2.2.3.1:删除节点的兄弟节点是黑色,兄弟节点无孩子,父亲节点是红色

删除节点P之后,左右两边不平衡了,可以直接将父节节点变成黑色,兄弟节点变成红色,这样就平衡了。
在这里插入图片描述
情景1.2.2.3.2:删除节点的兄弟节点是黑色,兄弟节点无孩子,父亲节点是黑色

直接将兄弟节点变成红色,这样就平衡了。但是经过pp的路径上的黑色节点数会少1,这个时候在以pp作为起始点,继续平衡操作,这里可以把pp和ppr当作一个节点pp这样一直向上,直到新的起始点为根节点。
在这里插入图片描述

情景2:删除节点有一个子树(左子树或者右子树)

以下情景不满足红黑树性质不可能出现:
在这里插入图片描述
只有下面两种情况可能出现:

删除节点是黑色,子节点是红色

那么可以直接让子孩子替换p,颜色变成黑色就可以了。
在这里插入图片描述

情景3:删除节点有两个子树

首先找到删除节点的后继节点,再将后继节点和删除节点替换,问题就变成删除替换节点的问题,而且替换节点要么无子树,要么有一个节点,问题就变回了情景1或者情景2。

JDK1.8中hashMap中的删除红黑树节点的源码,我做了一部分的改进,方便阅读。

 final void removeTreeNode(MyHashMap<K, V> map, Node<K, V>[] tab,
                              boolean movable) {
        /**
         * 链表的处理
         */
        int n;
        // 如果当前哈希表为空直接返回
        if (tab == null || (n = tab.length) == 0)
            return;

        // 计算当前节点在hash表的索引位置
        int index = (n - 1) & hash;
        // fisrt : t头节点
        TreeNode<K, V> first = (TreeNode<K, V>) tab[index];
        // 如果索引位置的红黑树为空
        if (first == null) {
            return;
        }
        // root:根节点
        TreeNode<K, V> root = first;
        // rl : root的左节点
        TreeNode<K, V> rl;
        // succ:节点的后继节点
        TreeNode<K, V> succ = (TreeNode<K, V>) next;
        // pred:节点的前驱节点
        TreeNode<K, V> pred = prev;
        // 如果根节点为空,则当前节点就是头节点,直接删除
        if (pred == null) {
            first = succ;
            tab[index] = succ;
            // 根节点不为空,当前节点为中间某个节点,删除中间节点
        } else {
            // 前驱的后继
            pred.next = succ;
        }
        // 后继的前驱
        if (succ != null) {
            succ.prev = pred;
        }
        // 如果root的父节点不为空,说明该节点并不是真正的红黑树根节点,需要重新查找根节点
        if (root.parent != null) {
            root = root.parent;
        }
        // 通过root节点来判断此红黑树是否太小, 如果是太小了则调用untreeify方法转为链表节点并返回
        // (转链表后就无需再进行下面的红黑树处理)
        // 太小的判定依据:根节点为null,或者根的右节点为null,或者根的左节点为null,或者根的左节点的左节点为null
        // 这里并没有遍历整个红黑树去统计节点数是否小于等于阈值6,而是直接判断这几种情况,
        // 来决定要不要转换为链表,因为这几种情况一般就涵盖了节点数小于6的情况,这样执行效率也会变高
        if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
            tab[index] = first.untreeify(map);  // too small
            return;
        }

        /**
         * 红黑树的处理
         */
        TreeNode<K, V> p = this;
        TreeNode<K, V> pl = left;
        TreeNode<K, V> pr = right;
        // replacement:替换节点
        TreeNode<K, V> replacement;
        if (pl != null && pr != null) {
            // 找到当前节点的后继
            TreeNode<K, V> s = pr;
            TreeNode<K, V> sl = s.left;
            while (sl != null) {
                s = sl;
                sl = s.left;
            }
            // 交换p和s的颜色
            boolean c = s.red;
            s.red = p.red;
            p.red = c;
            TreeNode<K, V> sr = s.right;
            TreeNode<K, V> pp = p.parent;
            // 如果p的后继节点s恰好是p的右节点,那说明pr没有左节点
            // 那么就可以直接将pr替换为p
            if (s == pr) {
                // 先处理p
                p.parent = s;
                p.left = null;
                p.right = sr;
                if (sr != null) {
                    sr.parent = p;
                }
                // 处理s
                s.right = p;
                s.left = pl;
                pl.parent = s;
                s.parent = pp;
                if (pp == null) {
                    root = s;
                } else if (p == pp.left) {
                    pp.left = s;
                } else {
                    pp.right = s;
                }
            } else {
                // 将p和s互换
                TreeNode<K, V> sp = s.parent;
                p.parent = sp;
                if (s == sp.left) {
                    sp.left = p;
                } else {
                    sp.right = p;
                }
                p.left = null;
                p.right = sr;
                if (sr != null) {
                    sr.parent = p;
                }
                s.parent = pp;
                if (pp == null) {
                    root = s;
                } else if (p == pp.left) {
                    pp.left = s;
                } else {
                    pp.right = s;
                }
                s.left = pl;
                s.right = pr;
                pr.parent = s;
            }


            // 如果sr不等于null,那需要p和sr替换掉
            if (sr != null) {
                replacement = sr;
                // 如果sr等于null,此时p无子树,直接删掉就可以
            } else {
                replacement = p;
            }

            // 走到这里说明pr为null,pl不为null
        } else if (pl != null) {
            replacement = pl;
            // 走到这里说明pl为null,pr不为null
        } else if (pr != null) {
            replacement = pr;
        }
        // 到这里,说明p的左右节点都为null
        else {
            replacement = p;
        }

        // 删掉当前节点p
        if (replacement != p) {
            TreeNode<K, V> pp = replacement.parent = p.parent;
            // 当p只有一个子树的时候,p的父节点可能为null
            if (pp == null) {
                root = replacement;
            } else if (p == pp.left) {
                pp.left = replacement;
            } else {
                pp.right = replacement;
            }
            // 删掉p节点
            p.left = p.right = p.parent = null;
        }
        // 如果p节点是红色,那不影响树的结构
        TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);

        if (replacement == p) {
            TreeNode<K, V> pp = p.parent;
            p.parent = null;
            if (pp != null) {
                if (p == pp.left) {
                    pp.left = null;
                } else {
                    pp.right = null;
                }
            }
        }
    }

    static <K, V> TreeNode<K, V> balanceDeletion(TreeNode<K, V> root,
                                                 TreeNode<K, V> x) {
        TreeNode<K, V> xp, xpl, xpr;
        while (true) {
            // 如果x为null或者是根节点,说明已经删除完了
            if (x == null || x == root) {
                return root;
                // 父节点为null,说明是根节点
            } else if ((xp = x.parent) == null) {
                x.red = false;
                return x;
                // 如果x是红色的,那么直接让它变成黑色的就行了
                // 因为父节点是黑色的,x节点直接代替他成为黑色的就行了
                // 这对应情景1.1或情景2
            } else if (x.red) {
                x.red = false;
                return root;
                // x既不是根节点,也不是红色
                // x是父亲的左节点
            } else if ((xpl = xp.left) == x) {
                // 此时对应于情景1.2.1,父兄换色,然后对x在进行一次平衡
                if ((xpr = xp.right) != null && xpr.red) {
                    xpr.red = false;
                    xp.red = true;
                    root = rotateLeft(root, xp);
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                if (xpr == null) {
                    // TODO: 这里应该不可能出现
                    System.out.println("..........");
                    x = xp;
                } else {
                    TreeNode<K, V> sl = xpr.left, sr = xpr.right;
                    // 此时xpr只能是黑色
                    // 这里if判断成功的可能条件:
                    // 1.sl == null,sr == null (对应情景1.2.2.3)
                    // 2.sl == null,sr == black (不可能)
                    // 3.sl == black,sr == null (不可能)
                    // 4.sl == black,sr == black (不可能)
                    if ((sr == null || !sr.red) &&
                            (sl == null || !sl.red)) {
                        xpr.red = true;
                        x = xp;
                    } else {
                        // 进入这里的可能条件
                        // 1.sl == null,sr == red (对应情景1.2.2.1)
                        // 2.sl == red,sr == null (对应情景1.2.2.2)
                        // 3.sl == red,sr == red (对应情景1.2.2.1)
                        // 4.sl == black,sr == red (不存在)
                        // 4.sl == red,sr == black (不存在)

                        // 条件2
                        if (sr == null) {
                            // 情景1.2.2.2
                            sl.red = false;
                            xpr.red = true;
                            root = rotateRight(root, xpr);
                            xpr = (xp = x.parent) == null ? null : xp.right;
                        }
                        // 此时就变成了场景1.2.2.1
                        if (xpr != null) {
                            // 父兄换色
                            xpr.red = xp.red;
                            if ((sr = xpr.right) != null) {
                                sr.red = false;
                            }
                        }
                        if (xp != null) {
                            xp.red = false;
                            root = rotateLeft(root, xp);
                        }
                        x = root;
                    }
                }
            } else {
                // 如果xpl为红色,那xp和xpl的孩子肯定为黑色
                if (xpl != null && xpl.red) {
                    xpl.red = false;
                    xp.red = true;
                    root = rotateRight(root, xp);
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }

                if (xpl == null) {
                    x = xp;
                } else {
                    TreeNode<K, V> sl = xpl.left, sr = xpl.right;
                    if ((sl == null || !sl.red) && (sr == null || !sr.red)) {
                        xpl.red = true;
                        x = xp;
                    } else {
                        if (sl == null) {
                            sr.red = false;
                            xpl.red = true;
                            root = rotateLeft(root, xpl);
                            xpl = (xp = x.parent) == null ?
                                    null : xp.left;
                        }
                        if (xpl != null) {
                            xpl.red = xp.red;
                            if ((sl = xpl.left) != null)
                                sl.red = false;
                        }
                        if (xp != null) {
                            xp.red = false;
                            root = rotateRight(root, xp);
                        }
                        x = root;
                    }
                }
            }
        }
    }
}

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

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

相关文章

计算机毕业设计选题推荐-大学生竞赛管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

初识Arduino

什么是Arduino Arduino是一款便捷灵活、方便上手的开源电子原型平台。它包含硬件部分&#xff08;即各种型号的Arduino板&#xff09;、软件部分&#xff08;即Arduino IDE&#xff09;&#xff0c;以及其Arduino社区平台。 Arduino由一个欧洲开发团队于2005年冬季开发&#…

56基于SpringBoot+Vue+uniapp的教学资源库的详细设计和实现(源码+lw+部署文档+讲解等)

文章目录 前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus 系统测试系统测试目的系统功能测试系统测试结论 为什么选择我代码参考数据库参考源码获取源码获取 前言 &#x1f31e;博主介绍 &#xff1a;✌全网粉丝15W,CSDN特邀作者、21…

信息学奥赛初赛天天练-80-NOIP2015普及组-基础题5-错位排列、二叉树、完全二叉树、叶子节点、完全二叉树叶子节点

NOIP 2015 普及组 基础题5 21 重新排列 1234使得每一个数字都不在原来的位置上&#xff0c;一共有( )种排法 22 一棵结点数为 2015的二叉树最多有( )个叶子结点 2 相关知识点 1) 错位排列 考虑一个有n个元素的排列&#xff0c;若一个排列中所有的元素都不在自己原来的位置…

建模杂谈系列250 Hello2Pymc

说明 pymc算是多年的老朋友了&#xff0c;中间失联了好几年。 内容 1 安装 安装更加麻烦了&#xff0c;不能很好的和其他的环境兼容。在官网上&#xff0c;也是建议用conda的方式安装。 conda create -c conda-forge -n pymc_env "pymc>5" conda activate p…

SQL基础——MySQL的优化

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 概述 在应用的的开发过程中&#xff0c;由于初期数据量小&#xff0c;开发人员写 SQL 语句时更重视功能上的实现&#xff0c;但是当应用系统正式上线后&#xff0c;随着生产数据量的急剧增长&…

安卓15发布日期确定,安卓15 谷歌GMS认证截止日期有重大变化!安卓版本GMS认证截止时间更新,谷歌GMS认证之MADA/EDLA设备认证截止时间介绍

谷歌正式公布安卓15发布日期&#xff0c;即9月3号&#xff0c;到时&#xff0c;安卓版本的认证时间将会有改变&#xff01;以下是深光标准整理的最新安卓版本的到期时间 详细讲解如何看懂这个图 第一列&#xff1a;OS version (API level) 指安卓版本 第二列&#xff1a;AOS…

软件测试工程师必备的技术能力

今年是我从事软件测试工作的第十年&#xff0c;从功能测试进阶到自动化测试&#xff0c;然后负责稳定性测试团队&#xff0c;进而兼任整个质量团队的技术专项治理&#xff0c;再到基础架构团队的测试专家角色&#xff0c;负责多个技术项目的产品/运营和质量保障工作。可以说绝大…

GNU 汇编语法基础

目录 一、引言 二、GNU 汇编基本结构 1.指令格式 2.注释 3. 段 三、寄存器和寻址方式 1.寄存器命名 2.寻址方式 四、指令集 1.数据传送指令 2.算术运算指令 3.逻辑运算指令 4.控制流指令 五、宏和函数 1.宏定义 2. 函数定义 六、总结 一、引言 在嵌入式系统…

南京观海微电子----VCC、 VDD、VSS、VEE 电压符号解释

一般在数据手册或者是说原理图中你会看到VCC、 VDD、VEE、VSS等不同的符号&#xff0c;那它们有什么区别&#xff0c;并且该怎么记住它们呢。 解释一&#xff1a; VCC&#xff1a;电源电压&#xff08;双极器件&#xff09;&#xff1b;电源电压&#xff08;74系列数字电路&a…

机会约束转化为确定性约束-- 样本均值法

当涉及到新能源消纳的机会约束规划时&#xff0c;我们需要深入理解其背后的原理和采用的方法。以下是对上文内容的更详细且更贴切的展开解释&#xff1a; 机会约束转化为确定性约束-- 样本均值法代码获取戳此处代码获取戳此处代码获取戳此处 新能源消纳的机会约束 新能源&…

计量校准中溯源方法会有哪些不足之处?

随着新型计量器具的不断涌现&#xff0c;现有的计量检定规程或计量校准规范已不能满足计量溯源的需要。特别是一体化大型设备所配备的传感器&#xff0c;如产业生产线之上的压力传感器、流量计、在线电导率仪、在线酸度计为代表的对传感器的检测目前多依据国家检定规程或计量校…

自制深度学习推理框架之表达式层的设计与实现

文章目录 一、表达式Expression二、词法解析2.1 词法定义2.2 词法解析 三、语法解析3.1 语法树的定义3.2 语法树构建3.3 语法树的转换(逆波兰式) 四、表达式层4.1 ExpressionLayer和ExpressionParser类4.2 表达式层的注册4.3 表达式层的输入处理4.4 表达式层的计算过程 五、计算…

分布式计算架构详解以及任务调度方式

信息技术领域重要分支—分布式计算。分布式计算通过将任务分配到多个物理的计算资源上进行处理&#xff0c;以来提高计算效率和资源利用率。今天主讲分布式计算架构的关键组成以及在云服务器背景下任务调度的不同方式&#xff0c;然后再综合来看这些调度策略是怎样适应云环境的…

使用 nuxi build-module 命令构建 Nuxt 模块

title: 使用 nuxi build-module 命令构建 Nuxt 模块 date: 2024/8/31 updated: 2024/8/31 author: cmdragon excerpt: nuxi build-module 命令是构建 Nuxt 模块的核心工具,它将你的模块打包成适合生产环境的格式。通过使用 --stub 选项,你可以在开发过程中加快模块构建速度…

Linux学习——ubuntu安装qt

安装VM的教程就不过多叙述了&#xff0c;这个简单&#xff0c;大家直接下载VM然后创建虚拟机就可以了&#xff0c;那我们今天来讲讲怎么在ubuntu中安装qtcreator. 如果我们的虚拟机是连接网络的&#xff0c;我们可以直接在Ubuntu上的浏览器中直接下载Qt,我们搜索Qt.io就可以&a…

【论文阅读】:Mamba YOLO SSMs-Based YOLO For Object Detection

摘要 Mamba架构已被证明可以有效地捕获长距离的地面依赖关系。 在深度学习技术的快速发展的推动下&#xff0c;YOLO系列为实时目标探测器设定了一个新的基准。在YOLO的基础上&#xff0c;不断探索再参数化、高效层聚合网络和无锚定技术的创新应用。为了进一步提高检测性能&am…

JSP详解使用

一、JSP概述 1.1 、JSP基础 1.1.1 、JSP简介 JSP全称是Java Server Page&#xff0c;它和Servlet一样&#xff0c;也是sun公司推出的一套开发动态web资源的技术&#xff0c;称为JSP/Servlet规范。JSP的本质其实就是一个Servlet。 1.1.2 、JSP和HTML以及Servlet的适用场景 …

阿尔茨海默病症识别+图像识别Python+人工智能+深度学习+TensorFlow+机器学习+卷积神经网络算法

一、介绍 阿尔茨海默病症识别。使用Python作为主要编程语言进行开发&#xff0c;基于深度学习等技术使用TensorFlow搭建ResNet50卷积神经网络算法&#xff0c;通过对病症图片4种数据集进行训练[‘轻度痴呆’, ‘中度痴呆’, ‘非痴呆’, ‘非常轻微的痴呆’]&#xff0c;最终得…

SimpleTranslationAIAgent借助SiliconCloud API 构建自己的专属翻译助手

SimpleTranslationAIAgent介绍 SimpleTranslationAIAgent是一款基于C#与LLM通过简单对话即可实现文件到文件的翻译任务的简单应用&#xff0c;基于WPF与Semantic Kernel构建。 该软件是MIT协议完全开源免费的&#xff0c;但是调用LLM的API可能需要费用&#xff0c;但是没关系…