红黑树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.xdnf.cn/news/140142.html

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

相关文章

Spring面试题15:Spring支持几种bean的作用域?singleton、prototype、request的区别是什么?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring支持几种bean的作用域? Spring支持以下几种Bean的作用域: Singleton(单例):这是Spring默认的作用域。使用@Scope(“singleton”)注解或…

pyspark常用算子总结

欢迎关注微信公众号&#xff0c;更多优质内容会在微信公众号首发 1. pyspark中时间格式的数据转换为字符串格式的时间&#xff0c;示例代码 from datetime import datetimedate_obj datetime(2023, 7, 2) formatted_date date_obj.strftime("%Y-%m-%d %H:%M:%S")p…

MySQL基础—从零开始学习MySQL

01.MySQL课程介绍_哔哩哔哩_bilibili 1、MySQL安装 以管理员身份运行cmd net start mysql80net stop mysql80 客户端连接 1). 方式一&#xff1a;使用MySQL提供的客户端命令行工具 2). 方式二&#xff1a;使用系统自带的命令行工具执行指令 mysql [-h 127.0.0.1] [-P 3…

C++真的是 C加加

&#x1f4dd;个人主页&#xff1a;夏目浅石. &#x1f4cc;博客专栏&#xff1a;C的故事 &#x1f3e0;学习社区&#xff1a;夏目友人帐. 文章目录 前言Ⅰ. 函数重载0x00 重载规则0x01 函数重载的原理名字修饰 Ⅱ. 引用0x00 引用的概念0x01 引用和指针区分0x03 引用的本质0x04…

SpringBoot-JWT生成

一、理论 1.配置pom.xml <!-- JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency> 2.加密方式 说明:官网JSON Web Tokens - jwt…

Java实验案例(一)

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

Python项目Flask ipv6双栈支持改造

一、背景 Flask 是一个微型的(轻量)使用Python 语言开发的 WSGI Web 框架(一组库和模块),基于Werkzeug WSGI工具箱/库和Jinja2 模板引擎,当然,Python的WEB框架还有:Django、Tornado、Webpy,这暂且不提。 Flask使用BSD授权。 Flask也被称为microframework(微框架),F…

【UE 粒子练习】02——使用一些常用的模块来创建粒子

目录 效果 步骤 一、创建材质 二、创建粒子 2.1 必需模块 2.2 初始大小模块 2.3 初始位置模块 2.4 初始速度模块 2.5 生命周期模块 2.6 加速-》恒加速度模块 2.7 生成模块 2.8 生命内颜色模块 2.9 尺寸-》大小随速度模块 2.10 碰撞-》Actor碰撞模块 2.1…

stack与queue的简单封装

前言&#xff1a; stack与queue即栈和队列&#xff0c;先进后出/先进先出的特性我们早已了然于心&#xff0c; 在学习数据结构时&#xff0c;我们利用c语言实现栈与队列&#xff0c;从结构体写起&#xff0c;利用数组或指针表示他们的数据成员&#xff0c;之后再一个个实现他们…

(避开网上复制操作)最详细的树莓派刷机配置(含IP固定、更改国内源的避坑操作、SSH网络登录、VNC远程桌面登录)

一、准备工作 SD卡格式化 二、 树莓派系统环境搭建&#xff08;官方&#xff09; 官方镜像 1.1、 必备的配件 读卡器&#xff0c; 内存卡&#xff08;强烈推荐 32GB 内存卡&#xff0c; #lite 命令行界面版本至少需要 8G&#xff0c; 图形化带桌面版镜像需要 16GB&#xf…

笔试强训Day(一)

T1&#xff1a;组队竞赛 链接&#xff1a;组队竞赛__牛客网 牛牛举办了一次编程比赛,参加比赛的有3*n个选手,每个选手都有一个水平值a_i.现在要将这些选手进行组队,一共组成n个队伍,即每个队伍3人.牛牛发现队伍的水平值等于该队伍队员中第二高水平值。 例如: 一个队伍三个队员…

自己写过比较蠢的代码:从失败中学习的经验

文章目录 引言1. 代码没有注释2. 长函数和复杂逻辑3. 不恰当的变量名4. 重复的代码5. 不适当的异常处理6. 硬编码的敏感信息7. 没有单元测试结论 &#x1f389; 自己写过比较蠢的代码&#xff1a;从失败中学习的经验 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&a…

Postgresql事务测试

参考一个事务中 可以查询自己未提交的数据吗_最详细MySQL事务隔离级别及原理讲解&#xff01;&#xff08;二&#xff09;-CSDN博客 一个事务中 可以查询自己未提交的数据吗_趣说数据库事务隔离级别与原理_weixin_39747293的博客-CSDN博客 【MySql&#xff1a;当前读与快照读…

eNSP基础网络学习-v02

一、eNSP 1.什么是eNSP eNSP(Enterprise Network Simulation Platform)是一款由华为提供的免费的、可扩展的、图形化操作的网络仿真工具平台&#xff0c;主要对企业网络路由器、交换机进行软件仿真&#xff0c;完美呈现真实设备实景&#xff0c;支持大型网络模拟&#xff0c;让…

stm32之看门狗

STM32 有两个看门狗&#xff0c;独立看门狗和窗口看门狗&#xff0c;独立看门狗又称宠物狗&#xff0c;窗 口看门狗又称警犬。可用来检测和解决由软件错误引起的故障。两个看门狗的原理都是当计数器达到给定的超时值时&#xff0c;产生系统复位&#xff0c;对于窗口型看门狗同…

VMware中安装Ubuntu(2023年)

Ubuntu安装 前言 安装过程中电脑发热时正常的&#xff0c;这个还是稍微有点点大&#xff1b;下载的版本根据自己的喜好来&#xff0c;新版本肯定要比旧版本占用的空间更大&#xff0c;大家自行选择&#xff1b;仅供学习使用的话可以下载成熟稳定的版本&#xff0c;例如16、18…

《Kubernetes部署篇:Ubuntu20.04基于containerd部署kubernetes1.25.14集群(多主多从)》

一、架构图 如下图所示: 二、环境信息 1、资源下载基于containerd部署容器版kubernetes1.25.14集群资源合集 2、部署规划主机名K8S版本系统版本内核版本IP地址备注k8s-master-121.25.14Ubuntu 20.04.5 LTS5.15.0-69-generic192.168.1.12master节点 + etcd节点k8s-master-131.…

摸鱼也摸鱼之在线数独自动求解

背景 在发现被老板CPU之后&#xff0c;大家想做的基本上都是摸鱼&#xff0c;像我这种没什么手法的人不可能摸鱼打MOBA游戏&#xff0c;所以只能选择数独这种对时间要求不怎么急促的小游戏。然而&#xff0c;有时候搞半天才发现从一开始就错了&#xff0c;这让我很苦恼&#x…

java多线程学习笔记一

一、线程的概述 1.1 线程的相关概念 1.1.1 进程&#xff08;Process&#xff09; 进程&#xff08;Process&#xff09;是计算机的程序关于某数据集合上的一次运行活动&#xff0c;是操作系统进行资源分配与调度的基本单位。 可以把进程简单的理解为操作系统中正在有运行的一…