【数据结构】红黑树详解

目录

前言:

红黑树的概念:

红黑树的性质:

红黑树节点的定义:

红黑树的插入:

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

情况2:cur为红,p为红,g为黑,u不存在或者u为黑(p和cur都在其父亲节点同一侧)

情况3:cur为红,p为红,g为黑,u不存在或者u为黑(p和cur在其父亲节点的不同侧)

红黑树验证:

AVL树和红黑树的比较:

红黑树应用:

结语:


前言:

如果有需要文章源码的友友请前往:红黑树源码

找到RBTree即红黑树源码。

红黑树的概念:

红黑树(RBTree),是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

下图就是一颗红黑树。

红黑树的性质:

(1)最长路径最多是最短路径的2倍。

(2)每个结点不是红色就是黑色。

(3)根节点是黑色的 。

(4) 如果一个节点是红色的,则它的两个孩子结点是黑色的(没有2个连续的红色节点) 

(5)对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

(6)每个叶子结点都是黑色的(此处的叶子结点指的是空结点)。

红黑树节点的定义:

红黑树采用孩子双亲表示法,多了一个表示颜色的枚举类(只有BLACK和RED).

public class RBTreeNode {public int val;public RBTreeNode left;public RBTreeNode right;public RBTreeNode parent;public COLOR color;public RBTreeNode(int val){this.val = val;color = COLOR.RED;}
}

其中COLOR是枚举类型具体代码如下: 

public enum COLOR{RED,BLACK
}

在看到上面红黑树的构造方法后,不知道友友们有没有对把新节点设置成红色感到疑惑尼? 

提问:在节点的定义中,为什么要将节点的默认颜色给成红色的?

答:这是因为根据上面给出的性质(5),红黑树从某节点到其后代叶节点的路径上的黑色节点个数相同,那么这个时候如果直接插入一个黑色节点,那么为了维护性质(5)必须要在别的叶节点后面也插入黑色节点,但是我们本意是插入一个节点,故不能插入黑色节点。至于插入红色节点可能会违反性质(4)我们只要调节一下颜色即可。

红黑树的插入:

红黑树是在二叉搜索树的基础上,加上其平衡限制条件,因此红黑树的插入可分为两步:

(1)按照二叉搜索的树规则插入新节点。

(2)检测新节点插入后,红黑树的性质是否造到破坏。

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整,但当新插入节点的双亲节点颜色为红色时,就违反了性质(4)不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

在这之前我们先来个规定🙌🙌🙌:

规定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点。

总共有三种情况具体如下:

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

这里解释的cur在p的左边如果cur是在p的右边的情况可以把三种情况都看完,相信就明白啦👍👍👍

如下图:

那么我们要怎么调整才能使它符合红黑树的性质呢?

具体调整过程如下:

注意:这里看到的树,可能是一个完整的树,也可能是一颗子树。

(1)如果g是根节点,调整完成后,需要将g改成黑色。

(2)如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整。

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

情况2:cur为红,p为红,g为黑,u不存在或者u为黑(p和cur都在其父亲节点同一侧)

说明:情况2可以根据u存不存在分为两种:

(1)如果u节点不存在,则cur一定是新插入节点(第二种情况的cur原本是黑色的,因为向上调整变成红色),因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,这样就不满足性质(5):每条路径黑色节点个数相同。

(2)如果u节点存在,则其一定是黑色(情况规定),那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色变成红色。

第1种类型如下图:

如果对旋转不太清楚的话可以看看我的上一篇博客实现的很清楚了。AVL树

先进行把g右旋,在把p和g的颜色改一下就可以弄出符合6条性质的红黑树。 

第2种类型如下图:

第2种类型是在调整的过程中才会出现的如下图。

 出现第2中类型后我们先把g右旋,接着把p变黑,g变红。

情况2的总结:

p为g的左孩子,cur为p的左孩子,则进行右单旋转。

p为g的右孩子,cur为p的右孩子,则进行左单旋转。

p、g变色--》p变黑,g变红。

情况3:cur为红,p为红,g为黑,u不存在或者u为黑(p和cur在其父亲节点的不同侧)

单看名字可能觉得和情况2差不多,其实就是多了一层旋转,如果有学过AVL树的友友不难发现这其实和AVL树的双旋差不多(没学过也没关系啦👍👍👍)。

当u不存在的情况下:

我们可以看到先将p节点左旋,再把p和cur的指向互换,这样就变成情况2,接着我们按照情况2来处理即可。

当u存在的情况下:

先根据情况1调整接着把p节点左旋,并交换p和cur的指向。最后按照情况2的处理方式即可。

到这里我们插入的三种情况终于全部讲解完毕🎉🎉🎉 

注意:我们上面分析的三种情况都是parent在grandFather的左侧,由于文章篇幅有限且parent在grandFather的右侧分析和在左侧是一模一样的,故这里就不再过多赘述,画完图后我们就会发现parent在grandFather的右侧就是和在左侧相反的情况,即把代码中出现left的改为right,right改为left即可(旋转方法也是)。 

插入的具体源码如下:

旋转源码因为和上一章AVL树差不多如果有需要源码的友友在文章的开头有给出😎😎😎。

public boolean insert(int val) {//1.按照二叉搜索树的方式插入节点RBTreeNode node = new RBTreeNode(val);if (root == null) {root = node;root.color = COLOR.BLACK;return true;}RBTreeNode parent = null;RBTreeNode cur = root;//记得要及时更新parent节点while (cur != null) {if (val > cur.val) {parent = cur;cur = cur.right;} else if (val < cur.val) {parent = cur;cur = cur.left;} else {System.out.println("存在重复节点");return false;}}if (val < parent.val) {parent.left = node;} else {parent.right = node;}//千万要记得我们还要把node的parent指回去。node.parent = parent;cur = node;//进行红黑树的调整while (parent != null && parent.color == COLOR.RED) {//注意进入这个while循环这里的grandFather是必定存在的,//因为parent为红色,但是我们的根节点只能是黑色RBTreeNode grandFather = parent.parent;if (grandFather.left == parent) {RBTreeNode uncle = grandFather.right;if (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为黑色//经过分别画图uncle不存在和uncle为黑色的情况分析发现处理方式是一样的//只要区分cur在parent的左侧还是右侧即可//和AVL树的思路差不多先旋转预处理一下,再来一次旋转搞定。//即转化为cur和parent在同一侧的情况。if (cur == parent.right) {rotateLeft(parent);RBTreeNode tmp = parent;parent = cur;cur = tmp;}//转换成一侧的情况rotateRight(grandFather);grandFather.color = COLOR.RED;parent.color = COLOR.BLACK;}} else {//grandFather.right == parent//这里就只要把上面的代码对应反着来就行//就是把上面代码里面出现right变成left,left变成right,方法也要变RBTreeNode uncle = grandFather.left;if (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为黑色//经过分别画图uncle不存在和uncle为黑色的情况分析发现处理方式是一样的//只要区分cur在parent的左侧还是右侧即可//和AVL树的思路差不多先旋转预处理一下,再来一次旋转搞定。//即转化为cur和parent在同一侧的情况。if (cur == parent.left) {rotateRight(parent);RBTreeNode tmp = parent;parent = cur;cur = tmp;}//转换成一侧的情况rotateLeft(grandFather);grandFather.color = COLOR.RED;parent.color = COLOR.BLACK;}}}root.color = COLOR.BLACK;return true;}

红黑树验证:

写完代码那就肯定要验证一下下啦❤️❤️❤️

红黑树的检测分为两步:

(1)检测其是否满足二叉搜索树 (中序遍历是否为有序序列)。

(2)检测其是否满足红黑树的性质。

中序遍历代码简单我就不贴了。

空树也是红黑树。

根节点的颜色是黑色。

判断没有两个连续的红色节点(checkRedNode)和每条路径上黑色节点数相同(checkBlackNum)

public boolean isValidRBTree(){//1.红的节点后面只能是黑色节点//2.所有节点到其后代叶子节点的路径上的黑色节点个数相同//3.根节点必须是黑色节点if(root == null){return true;}if(root.color != COLOR.BLACK){System.out.println("违反了根节点必须是黑色的特性");return false;}int tempBlackNum = 0;RBTreeNode cur = root;while(cur != null){if(cur.color == COLOR.BLACK){tempBlackNum++;}cur = cur.left;}return checkRedNode(root) && checkBlackNum(root,0,tempBlackNum);}

要想确定每一条路径上黑色节点个数相同那么我们就要先找一条路径来作参照,至于正不正确我们不关心只要保证黑色节点数相同即可。

找参照黑色节点数在外面找这里看上面代码我找的是最左边的那条路。

checkBlackNum源码如下:

private boolean checkBlackNum(RBTreeNode root,int path,int tempBlackNum){if(root == null){return true;}//要先加在判断,根据实际情况来if(root.color == COLOR.BLACK){path++;}if(root.left == null && root.right == null){if(path != tempBlackNum){System.out.println("违反了每条路径上的黑色节点数是一样的");return false;}}return checkBlackNum(root.left,path,tempBlackNum) && checkBlackNum(root.right,path,tempBlackNum);}

checkRedNode源码如下:

这里root的颜色如果是红色的话那么其父亲节点必定存在,因为根节点是黑色的所以不用判断是否为空。可以看到在树的各类方法中递归是非常参见的,写递归时我们关注某一个子问题做什么就好了。

private boolean checkRedNode(RBTreeNode root){if(root == null){return true;}if(root.color == COLOR.RED){if(root.parent.color == COLOR.RED){System.out.println("违反了两个红色节点不能连续出现");return false;}}return checkRedNode(root.left) && checkRedNode(root.right);}

这里也建议大家拿这个这个测试用例去跑一跑。

不仅要返回true还要调试一遍看看和自己手动画的图是不是一样的,下面我给出测试用例的红黑树的图,也希望友友们能自己画出来。 

红黑树的删除本文章不做讲解,有兴趣的同学可参考:《算法导论》

作者准备学了(希望不要入门到入土😭😭😭),后续有需要的话可能会单独出一篇👌👌👌。

AVL树和红黑树的比较:

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

红黑树应用:

1. java集合框架中的:TreeMap、TreeSet底层使用的就是红黑树。

2. C++ STL库 -- map/set、mutil_map/mutil_set。

3. linux内核:进程调度中使用红黑树管理进程控制块,epoll在内核中实现时使用红黑树管理事件块。

4. 其他一些库:比如nginx中用红黑树管理timer等。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

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

相关文章

C++从入门到精通——类的作用域及类的实例化

类的作用域及类的实例化 前言一、类的作用域二、类的实例化引例类是对对象进行描述的示例 一个类可以实例化出多个对象示例 示例 前言 类的作用域是指类中定义的变量和方法的可见性和可访问性范围。在类的内部&#xff0c;所有成员&#xff08;包括属性和方法&#xff09;都具…

GIT版本管理使用示例

一、创建好远程代码仓库后&#xff0c;复制远程仓库的地址 二、新建一个文件夹&#xff0c;在文件夹里打开Git Bash Here 三、输入git clone 远程仓库地址&#xff0c;仓库就初始化完成了 四、新建一个文本文件&#xff0c;假设这是我们开发的代码 五、打开文本&#xff0c;假设…

小秦网站部署上线

叮&#xff5e;您有一份消息&#xff0c;请注意查收&#xff01; 小秦个人网站终于上线了&#xff1a; 网站主要有&#xff1a;免费听歌、看小姐姐、AI女友、留言板等功能&#xff0c;建议收藏、收藏、再收藏、重要事情说三遍&#xff01;&#xff01;&#xff01; 网站&#x…

java实现运行脚本文件

在最近的项目中&#xff0c;有一个需求是前端传给我一个脚本文件&#xff0c;然后我需要运行脚本文件后将结果进行返回&#xff0c;那接下来就让我们看看是怎么做的吧&#xff01; public R runScripts(Integer id) {ScriptsInfo scriptsInfo this.baseMapper.selectById(id);…

工业4g路由器联网后迅速掉线是什么原因?

工业4G路由器连接上网后迅速掉线可能是由多种因素造成的。以下是一些建议的检查和解决步骤&#xff1a; 1、信号问题&#xff1a; 信号强度&#xff1a;检查工业路由器信号强度指示灯&#xff0c;如果信号弱&#xff0c;尝试移动路由器位置或添加外部天线来增强信号。 网络拥…

求m和n的最大公约数(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int remainder 1;int m 0;int n 0;int middle 0;//提示用户&#xff1b;printf("请输入整数m和n的值&#xff…

基于 OpenHarmony compress 三方件使用说明

关于 提供了一个轻量级的图像压缩库。将允许您将大照片压缩成小 尺寸的照片&#xff0c;图像质量损失或可以忽略不计 compress 的依赖添加 为你的应用添加 compress-debug.har。将 compress-debug.har 复制到 entry\libs 目录下即可&#xff08;由于 build.gradle 中已经依赖…

TopOn x Google Admob 自动创建广告源功能上线,支持一键添加广告单元 | TopOn产品速递

合作官宣 TopOn 正式成为亚太地区首家支持自动创建Admob bidding广告源的聚合平台。目前&#xff0c;在TopOn后台添加Admob广告平台&#xff0c;您只需要重新授权Google账号&#xff0c;即可开通自动创建功能。此前&#xff0c;TopOn 已在24年2月6日官方聚合支持Google biddin…

飞书文档如何在不同账号间迁移

今天由于个人需要新建了一个飞书账号&#xff0c;遇到个需求就是需要把老帐号里面的文档迁移到新的账号里面。在网上搜了一通&#xff0c;发现关于此的内容似乎不多&#xff0c;只好自己动手解决&#xff0c;记录一下过程以便分享&#xff0c;主要有以下几个步骤。 1. 添加新账…

python打印杨辉三角形

杨辉三角形&#xff0c;这个在国外被叫做帕斯卡三角形&#xff0c;中华文明为何立于世界之颠&#xff0c;这个就是最好的证明&#xff0c;古人的杨辉至少是这个帕斯卡的鼻祖辈&#xff0c;比帕某早了393年发现&#xff0c;那时候可没有知识产权概率&#xff0c;不然就是妥妥的侵…

JavaEE 初阶篇-深入了解线程池(线程池创建、线程池如何处理任务)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 线程池概述 1.1 线程池的优点 1.2 不使用线程池的问题 1.3 线程池的工作原理图 1.4 如何创建线程池&#xff1f; 2.0 通过 ThreadPoolExecutor 类自定义创建线程…

撸代码时,有哪些习惯一定要坚持?

我从2011年开始做单片机开发&#xff0c;一直保持以下撸代码的习惯。 1.做好代码版本管理 有些人&#xff0c;喜欢一个程序干到底&#xff0c;直到实现全部的产品功能&#xff0c;我以前做51单片机的项目就是这样。 如果功能比较多的产品&#xff0c;我不建议这样做&#xff0…

如何搭建APP分发平台分发平台搭建教程

搭建一个APP分发平台可以帮助开发者更好地分发和管理他们的应用程序。下面是一个简要的教程&#xff0c;介绍如何搭建一个APP分发平台。 1.确定需求和功能&#xff1a;首先&#xff0c;确定你的APP分发平台的需求和功能。考虑以下几个方面&#xff1a; 用户注册和登录&#xff…

Anritsu MS9740B与MS9740A 光谱分析仪 的区别?

MS9740B与MS9740A的主要区别在于测量处理时间的缩短和对高速信号处理技术的应用。MS9740B在保持原有功能和性能的同时&#xff0c;将测量处理时间缩短了一半。这一点通过提高生产效率和减少测量及检查时间来实现&#xff0c;从而提高了光有源设备制造商的生产力。此外&#xff…

mid转MP3怎么转?一分钟搞定~

MIDI&#xff08;Musical Instrument Digital Interface&#xff09;文件格式的诞生可以追溯到上世纪80年代&#xff0c;音频技术迅速崛起。为了让不同音乐设备之间能够互相通信&#xff0c;MIDI格式成为了音乐的标准。它不同于常见的音频文件格式&#xff0c;不包含实际的声音…

浮点数在内存中的存储【详解】

浮点数在内存中的存储 浮点数存储规则小数点后数值的二进制转换float和double存储图示优化存储方案E不全为0或不全为1E全为0E全为1 浮点数存储规则 大家都知道整型数据是以补码的方式存放在内存中。以下几个概念是需要知道的&#xff1a; 原码&#xff0c;补码&#xff0c;反…

汇编语言:寻址方式在结构化数据访问中的应用——计算人均收入

有一年多没有在CSDN上发博文了。人的工作重心总是有转移的&#xff0c;庆幸一直在做着有意义的事。   今天的内容&#xff0c;是为汇编语言课程更新一个实验项目。      本方案修改自王爽编《汇编语言》第&#xff14;版P172“实验7寻址方式在结构化数据访问中的应用” …

探索未来游戏:生成式人工智能AI如何重塑你的游戏世界?

生成式人工智能&#xff08;Generative AI&#xff09;正以前所未有的速度改变着各行各业的运作模式。其中&#xff0c;游戏产业作为科技应用的前沿阵地&#xff0c;正经历着前所未有的变革。本文将探讨生成式人工智能如何重塑游戏产业&#xff0c;以及这一变革背后的深远影响。…

一、Docker部署GitLab(详细步骤)

Docker部署GitLab&#xff08;详细步骤&#xff09; 一、拉取镜像二、启动容器三、修改配置四、修改密码五、浏览器访问 一、拉取镜像 docker安装教程&#xff1a;https://qingsi.blog.csdn.net/article/details/131270071 docker pull gitlab/gitlab-ce:latest二、启动容器 …

邮件服务器:Postfix

文章目录 邮件服务器的功能与工作原理电子邮件的问题Mail server与DNS 之间的关系邮件传输所需要的组件(MTA、MUA、MDA)以及相关协议用户收信时服务器端所提供的相关协议&#xff1a;MRA电子邮件的数据内容 使用Postfix与Dovecot部署邮件系统部署基础的电子邮件系统配置Postfix…