当前位置: 首页 > news >正文

C++进阶篇4---番外-红黑树

一、红黑树的概念

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

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的 
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (即不能出现两个连续的红色结点)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径(从根节点到空结点)上,均包含相同数目的黑色结点 
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,也被叫做NIL)

下图是一个红黑树

根据红黑树的性质:我们能得到这样一个结论---最长路径不会超过最短路径的两倍,为什么呢?

理由如下:因为每条路径上黑色节点的个数要相同,所以最短的路径上的点均为黑色结点,同时因为不能出现红色结点相邻的情况,所以最长路径上的结点颜色只能是黑红相间,故得出上诉结论

二、红黑树结点定义

enum Colour //这里用的枚举类型,也可以用其他的类型,只要能代表红黑两种颜色就行,如true/false
{BLACK,RED
};template<class K,class V>
struct RBTreeNode {RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V>_kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

思考:为什么要将结点颜色默认设置为红色???

理由如下:由于每条路径上的黑色节点的个数要保持相同,如果我们插入的结点的颜色为黑色,那么必然该节点所在路径的黑色节点的个数要增加,就会导致这颗树的其他所有路径都需要多一个黑色节点,影响范围太大,而如果插入的结点颜色为红色,我们只要关心它所在子树的情况就行,具体看下面的插入操作。故每个新插入结点颜色都默认为红色

三、红黑树插入结点

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1.按照二叉搜索树的规则插入新节点,如下
template <class K,class V>
class RBTree 
{typedef RBTreeNode<K, V> Node;
public:bool insert(const pair<K,V>&kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur) {if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//调整红黑树//...}
private:Node* _root = nullptr;
};

2.检查新节点插入后,红黑树的性质有没有被破坏

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

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

在这种情况下,我们只要改变结点的颜色就能保持红黑树的性质。

(注意:这种情况下,不用关心cur所在的位置)

情况二:cur为红,p为红,g为黑,u不存在或为黑色

 

这里就无法通过改变p、g、u的颜色来保持红黑树的性质(读者可以去手玩尝试一下),需要进行旋转+变色才行,那么看到这种形状的图形,我们就应该想到AVL树中的单旋和双旋,上面的情况很明显对应右单旋,旋转之后如下图

如何改变颜色使得红黑树的性质保持不变?

1.uncle为空,显而易见,将g变为红,p变为黑(可能有人会觉得将cur变成黑不是也行嘛?但如果p为红,且它是子树,那么还需要向上调整,会很麻烦,但如果p为黑色,那么它本身既符合红黑树性质,也并不会对其他的子树产生影响,直接就一步到位了)

2.uncle为黑色,(这种情况下,cur不可能是新插入的结点,只能是情况一向上调整得到的),这里就要分析a,b,c,d,e这几颗子树中黑色节点的个数,显然a,b,c的黑色结点个数相同,d和e的黑色结点比abc少一个,所以将p变为黑色,g变为红色,就能保持红黑树的性质(至于为什么不选择将g变成红色,理由同上)

具体的旋转和AVL树一样,只是红黑树旋转后需要变色,AVL树旋转后需要调整平衡因子,不了解的,可以看我写过的AVL树

这里,我们还要考虑cur所在的位置,共四种情况,第一种情况就是上面所讲的,剩下三种的旋转+变色,留给读者思考【旋转不会的可以去看我之前写的AVL树】

 

总结:红黑树的结点的插入首先看父节点是否为黑色(插入结点为根的情况要特别判断),如果为黑,不用处理,如果为红,关键看uncle结点,它决定了我们是否需要旋转,如果是红色,则只需要变色,如果是黑色/不存在,我们需要根据cur所在的位置选择合适的旋转方式,并对旋转之后的结点进行变色

 代码如下---附带检查检查红黑树是否正确的函数

enum Colour {BLACK,RED
};template<class K,class V>
struct RBTreeNode {RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V>_kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template <class K,class V>
class RBTree {typedef RBTreeNode<K, V> Node;
public:bool insert(const pair<K,V>&kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur) {if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}//父节点为黑,不用处理//为红需要调整while (parent && parent->_col == RED){//    g//  p   u//cNode* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED)//情况一{grandparent->_col = RED;parent->_col = uncle->_col = BLACK;cur = grandparent;parent = cur->_parent;}else//情况二{if (parent->_left == cur)//单旋{RotateR(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else//双旋{RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else{//     g//  u     p//       c  cNode* uncle = grandparent->_left;if (uncle && uncle->_col == RED){//变色grandparent->_col = RED;parent->_col = uncle->_col = BLACK;//向上走cur = grandparent;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pParent = parent->_parent;subR->_left = parent;parent->_right = subRL;parent->_parent = subR;if (subRL)//注意h==0的情况subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{subR->_parent = pParent;if (pParent->_left == parent){pParent->_left = subR;}else{pParent->_right = subR;}}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pParent = parent->_parent;subL->_right = parent;parent->_left = subLR;parent->_parent = subL;if (subLR)//注意h==0的情况subLR->_parent = parent;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{subL->_parent = pParent;if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}}}void InOrder(){_InOrder(_root);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}bool Isbalance(){return _Isbalance(_root);}//检查是否出现两个连续的红色节点+是否路径上的黑色节点的个数是否相同bool check(Node* root,int blacknum){if (root == nullptr){cout << blacknum << " ";return true;}if (root->_col == RED && root->_parent->_col == RED)return false;if (root->_col == BLACK)blacknum++;return check(root->_left, blacknum) && check(root->_right, blacknum);}bool _Isbalance(Node* root){if (root == nullptr)return true;if (root->_col == RED){cout <<"出现两个连续的红色节点:"<< root->_kv.first << endl;return false;}return check(root->_left, 1) && check(root->_right, 1);}
private:Node* _root = nullptr;
};

相关文章:

C++进阶篇4---番外-红黑树

一、红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0…...

《网络协议》05. 网络通信安全 · 密码技术

title: 《网络协议》05. 网络通信安全 密码技术 date: 2022-09-10 15:16:15 updated: 2023-11-12 07:03:52 categories: 学习记录&#xff1a;网络协议 excerpt: 网络通信安全&#xff08;ARP 欺骗&#xff0c;DoS & DDoS&#xff0c;SYN 洪水攻击&#xff0c;LAND 攻击&a…...

通信信道:无线信道中衰落的类型和分类

通信信道&#xff1a;无线信道中衰落的类型和分类 在进行通信系统仿真时&#xff0c;简单的情况下选择AWGN信道&#xff0c;但是AWGN信道和真是通信中的信道相差甚远&#xff0c;所以需要仿真各种其他类型的信道&#xff0c;为了更清楚理解仿真信道的特点&#xff0c;首先回顾…...

4.HTML网页开发的工具

4. 网页开发的工具 4.1 快捷键 4.1.1 快速复制一行 快捷键&#xff1a;shiftalt下箭头&#xff08;上箭头&#xff09; 或者ctrlc 然后 ctrlv 4.1.2 选定多个相同的单词 快捷键&#xff1a; ctrld 4.1.3 添加多个光标 快捷键&#xff1a;ctrlalt上箭头&#xff08;下箭头&…...

【Qt5 VS2019 (C++)编译报错解决】ASSERT failure in QList<T>::at: “index out of range“

Qt编译报错提示&#xff1a; ASSERT failure in QList<T>::at: "index out of range", file C:\Qt5\5.15.2\msvc2019_64\include\QtCore/qlist.h, line 571 //load 1st imageQFileInfo fileInfo1 list.at(2);原因&#xff1a; QList的索引越界&#xff0c;超…...

linux环境安装redis,以及常用的操作

1. 下载安装文件 http://download.redis.io/releases/redis-5.0.7.tar.gz 2. 把安装文件上传到 /usr/local/ 目录&#xff0c;并解压缩 tar -zvxf redis-5.0.7.tar.gz 3. 重命名文件夹 mv redis-5.0.7/ redis/ 4. 进入 /usr/local/redis/ 进行编译和安装 make make PREF…...

C++ Qt 学习(六):Qt http 编程

1. http 基础 HTTP 基础教程C Web 框架 drogonoatpp 2. C Qt 用户登录、注册功能实现 login_register.h #pragma once#include <QtWidgets/QDialog> #include "ui_login_register.h" #include <QNetworkReply>class login_register : public QDialog…...

38 路由的过滤器配置

3.3.断言工厂 我们在配置文件中写的断言规则只是字符串&#xff0c;这些字符串会被Predicate Factory读取并处理&#xff0c;转变为路由判断的条件 例如Path/user/**是按照路径匹配&#xff0c;这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoute…...

3分钟带你了解前端缓存-HTTP缓存

前情提要 前端缓存分为下面三大类&#xff0c;本文主要讲解HTTP缓存~ 1. HTTP缓存 强缓存协商缓存 2. 浏览器缓存 本地小容量缓存本地大容量缓存 3. 应用程序缓存 HTML5应用程序缓存 缓存作用 减少了冗余的数据传输减少服务器的负担提高了网站的性能加快加载网页速度 …...

【多线程 - 03、线程的生命周期】

生命周期 当线程被创建并启动以后&#xff0c;它不是一启动就进入执行状态&#xff0c;也不会一直处于执行状态&#xff0c;而是会经历五种状态。 线程状态的五个阶段&#xff1a; 新建状态&#xff08;New&#xff09;就绪状态&#xff08;Runnable&#xff09;运行状态&…...

excel表的筛选后自动求和

一般都使用subtotal函数。 通过看一个大佬的视频&#xff0c;发现可以有更简单的方法。 首先任意筛选数据(ctrlshiftl)&#xff0c; 然后选中需要求和的列的最下方的空白单元格&#xff0c;再按alt。 回车即可。 实质它还是用的subtotal函数...

2311rust特征

Rust无成本抽象 Rust中抽象基石是trait: 1,Trait是Rust中唯一的接口概念.多个类型可实现一个特征,事实上,可为现有类型提供新的特征实现.另一方面,想抽象未知类型时,找特征就行了. 2,与C模板一样,可静态分发特征. 3,可动态分发特征.有时确实需要间接,所以不必运行时"擦除…...

原型模式 rust和java的实现

文章目录 原型模式介绍优点缺点使用场景 实现java 实现rust 实现 rust代码仓库 原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当…...

阿里云ACK(Serverless)安装APISIX网关及APISIX Ingress Controller

在k8s上安装apisix全家&#xff0c;通过helm安装很简单&#xff0c;但是会遇到一些问题。 安装 首先登录阿里云控制台&#xff0c;在ACK集群详情页&#xff0c;进入CloudShell&#xff0c;执行下面helm命令安装apisix、apisix-ectd、apisix-dashboard和apisix-ingress-contro…...

vue+mongodb+nodejs实现表单增删改查

ExpressMongodbVue实现增删改查 效果图 前言 最近一直想学下node,毕竟会node的前端更有市场。但是光看不练&#xff0c;感觉还是少了点什么&#xff0c;就去github上看别人写的项目&#xff0c;收获颇丰&#xff0c;于是准备自己照葫芦画瓢写一个。 作为程序员&#xff0c;一…...

SpringBootWeb案例——Tlias智能学习辅助系统(3)——登录校验

前一节已经实现了部门管理、员工管理的基本功能。但并没有登录&#xff0c;就直接访问到了Tlias智能学习辅助系统的后台&#xff0c;这节来实现登录认证。 目录 登录功能登录校验(重点)会话技术会话跟踪方案一 Cookie&#xff08;客户端会话跟踪技术&#xff09;会话跟踪方案二…...

hive和spark-sql中 日期和时间相关函数 测试对比

测试版本&#xff1a; hive 2.3.4 spark 3.1.1 hadoop 2.7.7 1、增加月份 add_months(timestamp date, int months)add_months(timestamp date, bigint months)Return type: timestampusage:add_months(now(),1) 2、增加日期 adddate(timestamp startdate, int days)…...

Cell和RefCell

Cell和RefCell CellRefCellRefCell在运行时记录借用结合Rc和RefCell来拥有多个可变数据所有者引用循环与内存泄漏制造引用循环避免引用循环&#xff1a;将Rc变为Weak创建树形数据结构&#xff1a;带子节点的Node增加从子到父的引用可视化strong_count 和 weak_count 的改变 Rus…...

DaoWiki(基于Django)开发笔记 20231113

DaoWiki&#xff08;基于Django&#xff09;开发笔记 20231113 开发环境 操作系统 windows11python版本 3.12.0django版本 4.2.7 构建python虚拟环境 python -m venv daowiki启动python虚拟环境 cd daowiki\Scripts .\activate安装Django pip install django4.2.7创建项目…...

照片放大软件 Topaz Gigapixel AI mac中文版简介

Topaz Gigapixel AI mac是一款使用人工智能功能扩展图像的桌面应用程序&#xff0c;同时添加自然细节以获得惊人的效果。使用深度学习技术&#xff0c;A.I.Gigapixel™可以放大图像并填写其他调整大小的产品遗漏的细节&#xff0c;使用A.I.Gigapixel™&#xff0c;您可以裁剪照…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...