RBTree(红黑树)模拟实现(插入)

目录

红黑树的性质

红黑树的模拟插入

叔叔存在且为红色

叔叔不存在

旋转情况​​​​​​​

叔叔存在且为黑色

总结

插入实现

节点

插入逻辑

左单旋

右单旋


红黑树是一颗平衡搜索二叉树,但是红黑树并不像 AVL 树一样是高度平衡二叉树,任意一颗红黑树,它的子树不会超出它任意一个子树高度的二倍。

红黑树的性质

  • 每个节点不是红色就是黑色
  • 根节点是黑色的
  • 每个叶子节点(nil 节点/空节点)都是黑色的
  • 如果一个叶子节点是红色的,那么它的孩子节点都必须是黑色的
  • 任意一条路径上包含的黑色节点的数量都是相等的

其中,红黑树的平衡,就是由上面五条决定的。但是这里看到上面的五条并没有提到高度等字眼,红黑树也并不靠高度来维持平衡。

所以通过上面的条件,红黑树的高度虽然比 AVL 树的高度要高,但是红黑树的旋转次数是要比 AVL 树的少的,对于计算机而言,虽然高度可能比 AVL 树高,但是就搜索树的搜索时间复杂度为 O(log n) 来说,即使是红黑树的高度要相差二倍,那么时间复杂度最差也就是 O(log 2n) ,而对于计算机来说,这点时间并不算什么。

红黑树的模拟插入

红黑树的插入也是有几种情况,这几种情况分别需要不同的对待,所以下面我们看一下。

但是在插入之前,我们先想个问题,我们在插入的时候是插入红色节点呢?还是黑色节点?

  • 如果我们插入黑色节点,那么单条路径上的黑色节点的个数就变化了,所以我们还需要去修改其他路径的黑色节点,所以插入黑色节点的话,需要修改的节点数比较多
  • 插入红色节点,插入红色节点的话,我们的红黑树可能就不满足了,因为如果插入节点的父亲节点是红色的,那么就不满足红色节点的孩子节点必须是黑色的,所以这时候我们就需要对它的父亲节点进行修改颜色,或者是它的叔叔,并不会像插入黑色节点那样,需要对其他的路径都做修改
  • 所以,如果我们插入黑色节点的话,需要调整的工作量就比较大,但是如果我们插入红色节点的话,是有可能是不需要调整的,因为可能插入的节点的父亲节点就是黑色的,就算插入的父亲节点是红色的,那么也只需要修改它的父亲节点,或者是叔叔节点

叔叔存在且为红色

这里有一颗红黑树:

 假设我们现在要插入的节点是 21:

这时候,我们看到 cur 位置已经插入了节点 21,但是插入之后,该红黑树违反了,“红色节点的孩子节点必须是黑色的” 这一条规定,所以为例维持红黑树,我们需要对其进行变色,或者是旋转等操作来使其依旧是红黑树。

既然上面违反了 “红色几点的孩子节点必须是黑色的”,那么我们在看一下,cur 有没有叔叔节点,如果有的话,那么我们在看一下叔叔节点的颜色是不是红色的,如果都满足的话,那么下面我们就把父亲节点和叔叔节点都变为黑色的,然后把祖父节点变为红色,这样的话,这两条路径的黑色节点数量就不变了:

但是这里变色结束后,我们看到祖父节点是红色的,祖父节点的父亲节点也是红色的,所以我们可以继续刚才的步骤,知道祖父节点的颜色变为黑色,或者是祖父节点没有父亲节点,或者是其他情况的时候就结束,所以这时候,我们将祖父节点赋值给 cur 节点,然后继续啊上面的循环:

这时候,我们的祖父节点没有父亲节点,也就表示祖父节点就是根了,那么也就可以跳出循环,不过跳出循环后,我们的根节点不满足“根节点是黑色的”,这一条规定,所以出了循环之后,我们可以把根节点置为黑色:

在这样变色之后,该红黑树还是红黑树。

上面就是叔叔存在且为红色的情况。

叔叔不存在

如果是这种情况的话,那么就是叔叔不存在的情况,我们已经插入了 cur 节点,而这时候就是叔叔不存在的情况,那么这时候就不能靠改变父亲的颜色来维持红黑树了,因为这里只把父亲的颜色改变后和把祖父的颜色改变后,那么最右边的路径上黑色节点的数量就变少了,与其他路径的黑色节点的数量不同,所以不能光改变父亲和祖父的颜色,这时候就需要旋转加变色,那么怎么样旋转?这里我们可以对 grandparent 节点进行右单旋,然后将 parent 节点变为黑色,祖父节点变为红色:

 下一步就是将祖父节点变为红色,父亲节点变为黑色:

经过这样的旋转和变色之后,该树还是红黑树,不过刚才使用的是右单旋,那么怎么样判断是左单旋,还是右单旋,亦或者是右左双旋,或者是左右双旋?

旋转情况

在红黑树的旋转的判断条件并不是高度,而是看 cur parent 以及 grandparent 三个节点的位置。

1. 如果 parent 是 grandparent 的 left ,并且 cur 也是 parent 的 left ,那么就使用的是右单旋

2. 如果 parent 是 grandparent 的 right,并且 cur 也是 parent 的 right,那么就使用的是左单旋

3. 如果 parent 是 grandparent 的 right,并且 cur 也是 parent 的 left ,那么就使用的是右左双旋

4. 如果 parent 是 grandparent 的 left ,并且 cur 也是 parent 的 right,那么就使用的是左右双旋

所以下面我们看一下叔叔不存在,双旋的情况:

这时候,我们插入的节点是 24 这个节点,然后我们发现叔叔不存在,所以我们需要使用旋转加变色的方案,我们发现 parent 是 grandparent 的左,而 cur 是 parent 的右,所以我们需要使用左右双旋:

先对 parent 进行左单旋

 在对 grandparent 进行右单旋

旋转结束后,我们发现颜色不正确,所以我们在这种情况下,需要将grandparent 变为 红色,然后将 cur 变为 黑色。

叔叔存在且为黑色

上面就是叔叔存在且为黑色,我们到 cur 位置插入,但是这里我们先进行叔叔存在且为红色,所以我们向上跟新:

到这时候,就变成叔叔存在且为黑色了,所以这时候我们也不能单靠变色来解决问题,我们需要旋转加变色。

这时候的旋转也是按照上面的规则来的,我们看到parent 是 grandparent 的 右边,cur 是 parent 的右边所以这时候我们使用的是左单旋:

旋转结束后就是这个样子,但是颜色并不符合红黑树,所以我们还需要变色来处理,这种情况下,我们只需要把 grandparent 变为红色,父亲变为黑色:

其实叔叔不存在和叔叔存在且为黑色的情况是相同的,如果是单旋的话,那么就是旋转结束后,将付清的颜色变为黑色,然后将祖父的颜色变为红色,如果是双旋的话,那么就是将 cur 的颜色变为黑色,祖父的颜色变为红色。

下面看一下双旋的情况

这时候 cur 还是插入的节点,这时候我们跟新一次,然后就可以达到叔叔存在且为黑色,并且还是双旋的情况:

 此时就是叔叔存在且为黑色,并且我们发现 parent 是grandparent 的右边,cur 是 parent 的左边,所以这里我们使用的是右左双旋:

对 parent 进行右单旋

在对 grandparent 使用左单旋转

 这时候,就是将 grandparent 变为红色,然后将 cur 变为 黑色,所以我们在模拟插入后,发现叔叔不存在和存在且为黑的情况是一样的,所以我们后面就可以将叔叔存在且为红色分为一类,和叔叔不存在,或者存在且为黑色,分为一类,将这两类分开处理。

总结

旋转规则上面以及说过了,下面说一下变色:

1. 单旋情况下,将 grandparent 变为红色, parent 变为黑色

2. 双旋情况下,将 grandparent 变为红色, cur 变为黑色

插入实现

节点

  • 红黑树同样是kv结构,所以需要一个存储kv的变量
  • 既然是红黑树,那么除了三叉链,当然还需要一个颜色来控制
enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED){}
};

插入逻辑

	bool insert(const pair<K, V>& kv){if (_root == nullptr){// 根节点为空,插入到根节点_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root, *parent = nullptr;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{// 里面右重复值,插入失败return false;}}// 找到插入位置了cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;// 维护红黑树//当父亲不为空,并且父亲的颜色是红色就继续调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){// parent 是 grandfather 的左边//       g//    pNode* uncle = grandfather->_right;// 如果叔叔存在,且叔叔的颜色为红色// 那么就将叔叔和父亲的颜色全都改为黑色,然后将祖父的颜色改为红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}else{// 叔叔不存在或者叔叔存在但是叔叔的颜色为黑色if (cur == parent->_left){//         g//     p//  c//右单旋RotateR(grandfather);//将父亲的节点变为黑色,祖父的节点变为红色parent->_col = BLACK;grandfather->_col = RED;break;}else{//     g//  p//     c//左右双旋RotateL(parent);RotateR(grandfather);//将cur 的颜色变为黑色,祖父的节点变为红色cur->_col = BLACK;grandfather->_col = RED;break;}}}else{// parent 是 grandfather 的右边//     g//        pNode* uncle = grandfather->_left;// 如果叔叔存在,且叔叔的颜色为红色// 那么就将叔叔和父亲的颜色全都改为黑色,然后将祖父的颜色改为红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上迭代cur = grandfather;parent = cur->_parent;}else{// 叔叔不存在或者叔叔存在但是叔叔的颜色为黑色if (cur == parent->_right){//    g//       p//          c//左单旋RotateL(grandfather);//将父亲的节点变为黑色,祖父的节点变为红色parent->_col = BLACK;grandfather->_col = RED;break;}else{//       g//           p//       c//右左双旋RotateR(parent);RotateL(grandfather);//将cur 的颜色变为黑色,祖父的节点变为红色cur->_col = BLACK;grandfather->_col = RED;break;}}}}_root->_col = BLACK;return true;}

而旋转,我们以及在 AVL 就以及说过了,树的旋转是搜索树的旋转规则,并不是AVL 树或者红黑树特有的,所以旋转是通用的,只是红黑树的旋转不需要维持平衡因子,只需要旋转即可。

左单旋

	// 左单旋void RotateL(Node* parent){Node* cur = parent->_right;Node* curLeft = cur->_left;parent->_right = curLeft;if (curLeft){curLeft->_parent = parent;}cur->_left = parent;Node* pparent = parent->_parent;parent->_parent = cur;if (pparent == nullptr){// parent 就是根节点_root = cur;cur->_parent = nullptr;}else{if (parent == pparent->_left){pparent->_left = cur;}else{pparent->_right = cur;}cur->_parent = pparent;}}

右单旋

	//右单旋void RotateR(Node* parent){Node* cur = parent->_left;Node* curRight = cur->_right;parent->_left = curRight;if (curRight){curRight->_parent = parent;}cur->_right = parent;Node* pparent = parent->_parent;parent->_parent = cur;if (pparent == nullptr){// 说明 parent 是根节点_root = cur;cur->_parent = nullptr;}else{if (parent == pparent->_left){pparent->_left = cur;}else{pparent->_right = cur;}cur->_parent = pparent;}}

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

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

相关文章

自动化运维——ansible (五十二) (01)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、概述 1.1 为什么要用自动化运维软件 1.2 自动化运维 1.3 自动化运维要注意的方面 1.4 自动化运维主要关注的方面 1.5 常见的开源自动化运维软件 1.6 自动化运维软件…

华为OD机试 - 单词接龙 - 数据结构map、list (Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、输入示例1、输入&#xff1a;2、输出3、说明 五、解题思路1、核心思想&#xff1a;2、核心算法是构建一个map&#xff1a; 六、Java算法源码七、效果展示1、输入2、输出3、说明4、没有移除后再次拼接的情况&#xff0c;改…

Vue中实现3D得球自动旋转

具体实现 安装echarts 在终端下安装echarts npm install -D echarts 安装echarts-gl 在终端下安装echarts-gl npm install -D echarts-gl earth3D组件 earth3D.vue <template><div class"globe3d-earth-container" ><div class"globe3d-earth&qu…

CK_Label-V23货架标签(电池版本)接口文档

查询标签信息接口 接口类型&#xff1a;POST, 参数格式&#xff1a;json array 链接形式&#xff1a; http://localhost/wms/associate/getTagsMsg 代码形式&#xff1a; { url : http://localhost/wms/associate/getTagsMsg, requestMethed : GET, requestParameter :…

AKF拆分原则

在分布式软件环境下&#xff0c;为了保障分布式架构的可靠性、可扩展、高性能&#xff0c;通常会通过集群、扩容、数据分治等思想来实现&#xff0c;比如很多中间件的使用Redis、ZK、Kafka等&#xff0c;都可以通过这种设计思想来提高系统架构吞吐量。AKF是一个系统化的拓展思想…

苹果电脑快捷键集合

苹果电脑Windows系统下的ALT键是组合键。苹果电脑键盘左下角的Fnoption是Windows的alt键。同时按下两个键是ALT键的功能。在非组合状态下&#xff0c;单独按Option键。 补充&#xff1a; 1. 按controlalt&#xff08;选项&#xff09;delete 启动任务管理器。 2. Option-Del…

Mysql数据库之常用SQL语句及事务学习总结

数据库介绍 几个常见的缩写&#xff1a; DB&#xff1a;数据库。全称&#xff1a;DataBase。DBMS&#xff1a;数据库管理系统。全称&#xff1a;DataBase Management System。DBS&#xff1a;数据库系统。全称&#xff1a;DataBase System。DBA&#xff1a;数据库管理员。全称…

初识Nacos

前言 Nacos是一个用于微服务架构下的服务发现和配置管理以及服务管理的综合解决方案&#xff08;官网介绍&#xff09;&#xff0c;这里的服务发现其实就是注册中心&#xff0c;配置管理就是配置中心&#xff0c;而服务管理是二者的综合&#xff1b; Nacos特性 1.服务发现与…

什么是Linux

什么是Linux&#xff1f; 不知道大家是什么时候开始接触Linux&#xff0c;我记得我是大三的时候&#xff0c;那时候通过国嵌、韦东山的教学视频&#xff0c;跟着搭bootloader&#xff0c;修改内核&#xff0c;制作根文件系统&#xff0c;一步步&#xff0c;视频真的很简单&…

两性养生网站源码 生活类减肥网站源码 健康网模板源码 支持QQ登录和百度主动推送

本套模板非常适合生活类&#xff0c;两性类&#xff0c;减肥类等等类型的网站&#xff0c;这类型网站比较好做流量&#xff0c;因为客户群体众多&#xff0c; 可以自行改内容为其他类型网站模板总体非常简洁漂亮&#xff0c;配色合理&#xff0c;视觉舒服&#xff0c;并且配合…

HarmonyOS实现几种常见图片点击效果

一. 样例介绍 HarmonyOS提供了常用的图片、图片帧动画播放器组件&#xff0c;开发者可以根据实际场景和开发需求&#xff0c;实现不同的界面交互效果&#xff0c;包括&#xff1a;点击阴影效果、点击切换状态、点击动画效果、点击切换动效。 相关概念 image组件&#xff1a;图片…

IP175LLF基本参数和引脚图

特性 宽工作温度范围IP175LLF(0C至70C) IP175LLFI(-40C至85C)内置5个MAC和4个PHY 每个端口可配置为10base-t、100Base-TX 最多2K个MAC地址支持自极性10Mbps 汽车MDI-MDIX 支持1个MII/RMII端口Layer2-4多字段分类器 支持8-MultiField输入支持交通政策支持多字段过滤器 支…

静态链表处理

静态链表是指使用数组来表示节点。在C中&#xff0c;可以使用数组来创建静态列表&#xff0c;其中每个元素都有固定的位置和索引。可以通过下标寻址的方式来访问和操作列表中的元素。 单向列表&#xff1a; struct linkednode{int data;int next; }node[N]; 双向链表&#x…

K8S1.23.6版本详细安装教程以及错误解决方案(包括前置环境,使用部署工具kubeadm来引导集群)

准备工作&#xff08;来自官方文档&#xff09; 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令。每台机器 2 GB 或更多的 RAM&#xff08;如果少于这个数字将会影响你应用的运行内存&#xf…

HJ23 删除字符串中出现次数最少的字符

描述 实现删除字符串中出现次数最少的字符&#xff0c;若出现次数最少的字符有多个&#xff0c;则把出现次数最少的字符都删除。输出删除这些单词后的字符串&#xff0c;字符串中其它字符保持原来的顺序。 数据范围&#xff1a;输入的字符串长度满足 1≤n≤20 &#xff0c;保…

vue使用wangEditor

vue版本2.0&#xff1b;editor5.1.23版本&#xff1b;editor-for-vue&#xff1a;1.0.2版本 api文档入口 效果图 点击查看如何封装 安装步骤入口 npm install wangeditor/editor --savenpm install wangeditor/editor-for-vue --save代码&#xff08;未封装过的&#xff09;…

Layui快速入门之第三节栅格布局

目录 一&#xff1a;栅格布局的基本概念 二&#xff1a;栅格布局规则 三&#xff1a;始终等比例水平排列案例 四&#xff1a;响应式规则 五&#xff1a;移动设备、桌面端的组合响应式展现案例 六&#xff1a;移动设备、平板、桌面端的复杂组合响应式展现案例 七&#xf…

day35 线程

程序&#xff1a;是为了完成特定的任务&#xff0c;用某种语言编写的一组有序指令的集合&#xff0c;是一段静态的代码 进程&#xff1a;是程序的一次执行过程 线程&#xff1a;线程是进程中的一个执行单元 线程是调度和执行的单位 处理器和线程间的关系 创建线程(重点) 创建…

大数据课程L5——网站流量项目的实时业务系统搭建

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握网站流量项目的 Flume—>Kafka 连通; ⚪ 掌握网站流量项目的实时业务系统搭建; 一、Flume—>Kafka 连通 1. 实现步骤 1. 启动三台服务器。 2. 启动 Zookeeper 集群。 执行指…

2023国赛 B题论文 基于多波束测深技术的海洋探测建模与分析

因为一些不可抗力&#xff0c;下面仅展示小部分论文&#xff0c;其余看文末 一、问题重述 1.1 问题背景 海洋测深是测定水体深度与海底地形的重要任务&#xff0c;有两种主要技术&#xff1a;单波束测深与多波束测深。单波束适用于简单任务&#xff0c;但多波束可提供更精确…