红黑树Java实现

文章目录

  • 红黑树
    • 1. 概念性质
    • 2. 红黑树节点定义
    • 3. 红黑树的插入
      • 情况1
      • 情况2
      • 情况3
      • 其它细节问题
      • 插入代码实现
    • 4. 红黑树的验证
    • 5.性能分析


红黑树

1. 概念性质

红黑树也是一种二插搜索树,每一个节点上比普通二插搜索树都增加了一个存储位置表示节点的颜色,可以是Red或者Black.通过对任何一条从根到叶子的路径上各个节点上色的方式限制,红黑树确保没有一条路径会比其他路径长出2倍,从而得出红黑树是接近平衡的

在这里插入图片描述

红黑树的性质

  1. 每个节点不是红色就是黑色

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子节点是黑色的,红黑树不能有2个连续的红色节点

  4. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点

  5. 每个叶子节点都是黑色的(此处的叶子节点指的是是空节点,比如上图的NIL)

通过以上几条性质就能保证最长路径中的节点个数不会超过最短路径节点个数的两倍,因为不能出现两个连续的红色节点,假设有一条路径全是黑色节点,由于每条路径的黑色节点个数是相等的,假设有一种情况是红黑交替,那么全黑节点路径就是最短路径的,而红黑交替路径就是最上路径,就可以保证长路径中的节点个数不会超过最短路径节点个数的两倍

最长路径和最短路径图:

如图最短路径

在这里插入图片描述

2. 红黑树节点定义

enum COLOR {RED,BLACK;
}
private static class RBTreeNode {int val;RBTreeNode parent;RBTreeNode left;RBTreeNode right;COLOR color;public RBTreeNode(int val) {this.val = val;this.left = null;this.right = null;// 插入节点默认为红色this.color = COLOR.RED;}
}

3. 红黑树的插入

红黑树插入节点默认应该插入红色的,因为如果默认插入的节点是黑色,因为红黑树的性质4每条路径中的黑色节点数目相同,如果默认插入的节点为黑色就需要新增节点,所以默认插入节点应该为红色

红黑树的插入步骤:

  1. 以二插搜索树的插入方式进行插入新节点
  2. 插入节点后,检测红黑树的性质是否被影响

我们约定cur为插入节点、p为插入节点的父亲节点、g为插入节点的爷爷节点、u为插入节点的叔叔节点

情况1

情况1:cur为红,p为红,g为黑,u存且为红

需要注意的是这里需要分两种情况,一种是看到的是一棵完整的树,另一种是一棵树的子树,此处为抽象图:

在这里插入图片描述

当把cur插入到红黑树当中,因为插入的节点是红色的,此时就不满足红黑树的性质3,所以此时就需要把p和u改成黑色的。

在这里插入图片描述

但是这还没完,假设g不是根节点,这棵树是另外一棵树的子树,那么g还有父节点。有可能g的父节点是红色的,也有可能是黑色的。假设g的父亲节点是黑色的,它的兄弟节点也是黑的。那么此时把p和u修改成的黑色节点,那么路径上的黑色节点就增加了,此时就需要把g修改成红色。那如果g本身就是根节点呢?这个可以放在最后再来处理,处理完所有情况后,不管g是红色和是黑色都把g改成黑色。

在这里插入图片描述

还有一种情况就是如果g父亲的节点本身就是一个红色的节点,如果g的父亲节点是红色的说明其上面肯定还有节点,因为根节点是黑色的,也就是g的父亲节点可定不是根节点。此时就以同样的方式继续向上调整。所以解决方式就是:**将p,u改为黑,g改为红,然后把g当成cur,继续向上调整 **

在这里插入图片描述

情况2

情况2:cur为红、p为红、u不存在或者u为黑

情况2抽象图如下:

在这里插入图片描述

需要注意的是情况2这种抽象图是在红黑树调整过程中产生的,因为它并不遵循红黑树的性质:每条路径上的黑色节点个数一致,那么其实p的右边其实是有黑色节点的,同样cur也应该是黑色节点,cur变成红色就是因为在调整过程中改变了颜色。

具象图如下:

我们在情况1的条件下修改完对应节点颜色后进行向上调整,发现就得到了情况2,所以说情况2是在调整过程中的到的。

在这里插入图片描述

调整情况2第一步就是进行右旋,再修改颜色。

  • 对g节点进行右单旋
  • 修改p的颜色为黑色、修改g的颜色为红色

在这里插入图片描述

此处讨论的是grandFather.left == parent,如果是grandFather.right == parent那么就要是左单旋g节点

情况3

情况3:cur为红,p为红,g为黑,u不存在或者u为黑

情况3也是和情况2类似也是在红黑树的调整过程中产生的,在调整过程中cur变成了红色。抽象图如下:

在这里插入图片描述

再来看一个对应的具象图:

在这里插入图片描述

对应情况3我们先要将p节点进行左单旋。

在这里插入图片描述

对p节点进行左单旋后,发现此时的树节点的颜色情况和情况2非常相似,只是引用不一致。我们回想一下情况2的条件:

  • 情况2:cur为红、p为红、u不存在或者u为黑
  • 对比情况2只是p和cur的指向不一样入下图所示

在这里插入图片描述

所以对于情况3我们只需要左旋后将p和cur的引用进行交换,再以情况2的方式进行处理即可。

还有一种情况grandFather.right == parent,此时的判断又不一样了,此时的条件是cur == parent.left,且是对parent进行右单旋,再修改指向。

其它细节问题

  • 注意每次插入后都要将根节点root的颜色修改为黑色,避免调整时root被修改成红色,从而导致问题
  • 情况2和情况3,要分两种情况讨论
    • grandFather.right == parentgrandFather.left == parent
    • grandFather.left == parent的时候情况2是对grandFather进行右单旋,当grandFather.right == parent的时候情况2是对grandFather进行左单旋
    • grandFather.left == parent情况3判断的是cur == parent.right并且对parent进行左单旋,如果是grandFather.right == parent情况3判断的是cur == parent.left并且对parent进行右单旋
    • 但情况1是不需要区分的

插入代码实现


/*** 插入节点* @param val*/
public void insert(int val) {RBTreeNode node = new RBTreeNode(val);if (root == null) {root = node;// 根节点是黑色的root.color = COLOR.BLACK;return;}// 以搜索树树的方式进行插入RBTreeNode cur = root;RBTreeNode parent = cur;while (cur != null) {parent = cur;if (cur.val > val) {cur = parent.left;} else if (cur.val < val) {cur = parent.right;} else {System.out.println("元素: "+val+"+已经存在,插入失败!");return;}}if (parent.val > val) {parent.left = node;} else {parent.right = node;}// 修改指向node.parent = parent;cur = node;//调整红黑树// 如果parent为红色说明,parent一定不是根节点while (parent != null && parent.color == COLOR.RED) {RBTreeNode grandFather = parent.parent;if (grandFather.left == parent) {RBTreeNode uncle = grandFather.right;// 情况1if (uncle != null && uncle.color == COLOR.RED) {parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandFather.color = COLOR.RED;// 继续向上调整cur = grandFather;parent = cur.parent;} else {//uncle不存在 或者 uncle是黑色的// 情况3:把情况3修改成情况2if (cur == parent.right) {rotateLeft(parent);// 修改引用指向RBTreeNode tmp = cur;cur = parent;parent = tmp;}// 情况2rotateRight(grandFather);parent.color = COLOR.BLACK;grandFather.color = COLOR.RED;}} else {// grandFather.right == parentRBTreeNode uncle = grandFather.left;// 情况1if (uncle != null && uncle.color == COLOR.RED) {parent.color = COLOR.BLACK;uncle.color = COLOR.BLACK;grandFather.color = COLOR.RED;// 继续向上调整cur = grandFather;parent = cur.parent;} else {//uncle不存在 或者 uncle是黑色的// 情况3:把情况3修改成情况2if (cur == parent.left) {rotateRight(parent);// 修改引用指向RBTreeNode tmp = cur;cur = parent;parent = tmp;}// 情况2:rotateLeft(grandFather);parent.color = COLOR.BLACK;grandFather.color = COLOR.RED;}}}// 关键一步,防止向上调整的时候把根节点的颜色给修改了root.color = COLOR.BLACK;
}/*** 对grandFather节点进行右单旋* @param grandFather*/
private void rotateRight(RBTreeNode grandFather) {// 定义相关节点RBTreeNode gLeft = grandFather.left;RBTreeNode gLR = gLeft.right;RBTreeNode gParent = grandFather.parent;// 修改引用grandFather.left = gLR;grandFather.parent = gLeft;gLeft.right = grandFather;if (gLR != null) {gLR.parent = gParent;}// 判断g是否是根节点if (grandFather == root) {gLeft.parent = null;root = gLeft;} else {// 如果不是根节点那么就分时gParent的左节点和右节点if (gParent.left == grandFather) {gParent.left = gLeft;} else {gParent.right = gLeft;}gLeft.parent = gParent;}}/*** 对parent节点进行左旋* @param parent*/
private void rotateLeft(RBTreeNode parent) {// 记录对应节点RBTreeNode parentR = parent.right;RBTreeNode parentRL = parentR.left;RBTreeNode pParent = parent.parent;// 修改节点parent.right = parentRL;// 如果parentRL存在if (parentRL != null) {parentRL.parent = parent;}parent.parent = parentR;parentR.left = parent;// 如果旋转的是根节点if (parent == root) {root = parentR;parentR.parent = null;} else {// 如果旋转的不是根节点就判断旋转的是pParent的左子树还是右子树if (pParent.left == parent) {pParent.left = parentR;} else {pParent.right = parentR;}parentR.parent = pParent;}
}

4. 红黑树的验证

根据红黑树的性质编写代码验证:

  1. 每个节点不是红色就是黑色

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子节点是黑色的,红黑树不能有2个连续的红色节点

  4. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点

  5. 再对红黑树进行中序遍历验证

/*** 判断当前是否红黑树* @return*/
public boolean isRBTree() {if (root == null) {return true;}// 1.根节点为黑色if (root.color != COLOR.BLACK) {System.out.println("根节点不为黑色!");return false;}// 计算某一条黑色节点个数int checkNum = 0;RBTreeNode cur = root;while (cur != null) {if (cur.color == COLOR.BLACK) {checkNum++;}cur = cur.left;}// 中序遍历inorderTraversal(root);// 红黑树不能有两个连续的红色节点&& 每条路径的黑色节点个数一致return checkRedColor(root) && checkBlackNum(root,0,checkNum);
}/*** 判断是否有两个连续红色节点* @param node* @return*/
private boolean checkRedColor(RBTreeNode node) {if (node == null) {return true;}if (node.color == COLOR.RED) {if (node.left != null && node.left.color == COLOR.RED) {return false;}if (node.right !=  null && node.right.color == COLOR.RED) {return false;}}return checkRedColor(node.left) && checkRedColor(node.right);
}/*** 判断每条路径上的黑色节点个数* @param root* @param count* @param checkNum* @return*/
private boolean checkBlackNum(RBTreeNode root,int count,int checkNum) {if (root == null) {return true;}if (root.color == COLOR.BLACK) {count++;}if (root.left == null && root.right == null && count != checkNum) {System.out.println("每条路径黑色节点个数不一致");return false;}return checkBlackNum(root.left,count,checkNum) && checkBlackNum(root.right,count,checkNum);
}
private void inorderTraversal(RBTreeNode root) {if (root == null) return;inorderTraversal(root.left);System.out.print(root.val+" ");inorderTraversal(root.right);
}

5.性能分析

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是 O ( l o g n ) O(logn) O(logn)

  • AVL树通过左旋右旋来保证树的绝对平衡(左右子树的高度差不超过1),所以旋转的次数比较多

  • 红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,通过修改颜色来降低旋转的次数。

  • 红黑树对比AVL树,其通过修改颜色大大减低了旋转的次数,在增删的场景中使用红黑树更优,而AVL树只是适合查找,所以红黑树在实际运用更多的是红黑树,比如说TreeMap和TreeSet。


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

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

相关文章

Foxit PDF SDK Windows 9.1 Crack

Foxit PDF SDK 变更日志 Windows/Linux/Mac 2023 年 8 月 新功能/增强功能 在开始签名之前设置外观。支持使用共享字典添加签名。允许在调用 Signature::StartSign() 之前增量保存文档。在签名前修改现有未签名分页印章签名的外观。支持使用共享字典添加分页签名。忽略全角…

FPGA的主流技术与市场表现方面的调研报告

撰写简单的FPGA的主流技术与市场表现方面的调研报告&#xff0c;表达自己的认知和发展展望&#xff0c;500字&#xff0c;图片&#xff0c;表格除外 FPGA简介 FPGA&#xff08;Field-Programmable Gate Array&#xff09;是一种可编程逻辑器件&#xff0c;是在PAL &#xff08…

【软件测试】测试中的风险有哪些?

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; ​那么到底都有哪些风险要注意呢?如何解决呢?另外这些风险如何在计划中写明呢&#xff0c;不会写“张三…

苏宁滑块验证

网址&#xff1a;https://passport.suning.com/ids/login总结一下&#xff0c;别被他的表面现象给骗了&#xff0c;这玩意儿&#xff0c;个人认为&#xff0c;腾讯的都没法跟他比&#xff01;&#xff01;&#xff01; 难点&#xff1a;动态混淆&#xff0c;vmp&#xff0c;图片…

Django — 配置和路由

目录 一、项目的配置二、静态文件的配置三、路由1、概述2、代码实现2.1、后台管理页面2.2、子应用自定义页面2.3、子路由 3、路由解析顺序3.1、请求到达 Django 应用程序3.2、Django 根据 URL 路径查找匹配的路由3.3、第一个匹配的 URL 模式被选中3.4、相关联的视图函数被调用3…

Java由浅入深理解线程池设计和原理

目录 1 线程1.1 什么是线程&#xff1f;什么是进程&#xff1f;1.2 java中线程的实现方式有几种?1.3 线程的生命周期是什么&#xff1f; 2 线程存在的问题2.1 一个线程只能执行一个任务2.2 线程执行完后销毁,无法复用2.3 线程过多,导致JVM宕机 3 初识线程池3.1 了解J.U.C3.2 线…

追光者的梦

追光者的梦 鸿蒙中我茫然于世&#xff0c;你是钻入我心里的那束光 我所有的梦想都是和你热烈的拥抱 没有追到你时&#xff0c;我一直在路上 追到你时&#xff0c;我的人生就被你点燃 ——致所有的追光者 合肥先进光源国家重大科技基础设施项目及配套工程启动会刚开过&…

【matlab程序】海图单位的度分格式

【matlab程序】海图单位的度分格式 海洋与大气科学 点击蓝字 关注我们 思路来源 阅读文献&#xff1a; 文献中图片的横纵坐标出现半分画法&#xff1a; 半分画法&#xff0c;甚至更为精细的坐标轴在小区域研究中更为重要。 图片 图片 01 一度间隔 图片 代码 % 01 运…

2023 第十二届中国智能产业高峰论坛 - 文档大模型的未来展望

目录 前言文档图像分析识别与理解中的技术挑战 文档图像分析识别与理解的研究主题文档图像分析与预处理文档解析与识别版面分析与还原文档信息抽取与理解AI安全知识化&存储检索和管理 多模态大模型在文档图像处理中的应用多模态的GPT-4在文档图像上的表现多模态的Google Ba…

基因组注释(Annotation)

基因组组装完成后&#xff0c;或者是完成了草图&#xff0c;就不可避免遇到一个问题&#xff0c;需要对基因组序列进行注释。注释之前首先得构建基因模型&#xff0c;有三种策略&#xff1a; 从头注释(de novo prediction)&#xff1a;通过已有的概率模型来预测基因结构&#…

Java实验案例(一)

目录 案例一&#xff1a;买飞机票 案例二&#xff1a;开发验证码 案例三&#xff1a;评委打分 案例四&#xff1a;数字加密 案例五&#xff1a;数组拷贝 案例六&#xff1a;抢红包 案例七&#xff1a;找素数的三种方法 案例八&#xff1a;打印乘法口诀表 案例九&#x…

大厂面试-16道面试题

1 java集合类有哪些&#xff1f; List是有序的Collection&#xff0c;使用此接口能够精确的控制每个元素的插入位置&#xff0c;用户能根据索引访问List中元素。常用的实现List的类有LinkedList&#xff0c;ArrayList&#xff0c;Vector&#xff0c;Stack。 ArrayList是容量…

某度sign参数逆向

文章目录 前文分析完整代码结尾 前文 本文章中所有内容仅供学习交流&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 分析 经过我们几次抓包&#xff0c;测试&#xf…

解决jupyter打开的默认路径问题

已经安装完anaconda&#xff0c;但是jupyter每一次打开的路径都不是自己想要的路径&#xff0c;可以在配置文件中修改jupyter打开的默认路径&#xff0c;具体步骤如下&#xff1a; 首先打开anaconda的命令行 如果有多个环境的&#xff0c;需要输入conda activate 环境名称以下命…

list容器排序案例

案例描述:将Perspn自定义数据类型进行排序&#xff0c;Person中属性有姓名、年龄、身高 排序规则:按照年龄进行升序&#xff0c;如果年龄相同按照身高进行降序 代码示例 #include <iostream> #include <string.h> #include <iterator> #include <vector…

【C++面向对象侯捷】12.虚函数与多态 | 13.委托相关设计【设计模式 经典做法,类与类之间关联起来,太妙了,不断的想,不断的写代码】

文章目录 12.虚函数与多态举例&#xff1a;委托 继承【观察者模式】13.委托相关设计Composite 组合模式Prototype 原型模式 12.虚函数与多态 纯虚函数 一定要 子类重新定义的 继承和复合 关系下的构造和析构 举例&#xff1a;委托 继承【观察者模式】 13.委托相关设计 问题…

云原生安全性:保护现代应用免受威胁

文章目录 引言云原生安全性的挑战云原生安全性的关键实践1. 安全的镜像构建2. 网络策略3. 漏洞扫描和漏洞管理4. 认证和授权5. 日志和监控 云原生安全工具结论 &#x1f389;欢迎来到云计算技术应用专栏~云原生安全性&#xff1a;保护现代应用免受威胁 ☆* o(≧▽≦)o *☆嗨~我…

科目二倒车入库

调整座位和后视镜 离合踩到底大腿小腿成130-140 上半身90-100 座椅高度能看到前方全部情况 后视镜调节到能看到后门把手&#xff0c;且后门把手刚好在后视镜上方边缘、离车1/3处。 保持直线&#xff1a; 前进&#xff1a; 车仪表盘中央的原点和地面上的黄线擦边&#xff…

【AI视野·今日NLP 自然语言处理论文速览 第三十八期】Thu, 21 Sep 2023

AI视野今日CS.NLP 自然语言处理论文速览 Thu, 21 Sep 2023 Totally 57 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Chain-of-Verification Reduces Hallucination in Large Language Models Authors Shehzaad Dhuliawala, Mojt…

PyTorch深度学习实战(17)——多任务学习

PyTorch深度学习实战&#xff08;17&#xff09;——多任务学习 0. 前言1. 多任务学习1.1 多任务学习基本概念1.2 多任务学习优势 2. 模型与数据集分析2.1 模型分析2.2 数据集介绍 3. 实现年龄估计和性别分类小结系列链接 0. 前言 多任务学习( Multi-Task Learning, MTL )是一…