【C++第二十章】红黑树
【C++第二十章】红黑树
红黑树介绍🧐
红黑树是一种自平衡的二叉搜索树,通过颜色标记和特定规则保持树的平衡性,从而在动态插入、删除等操作中维持较高的效率。它的最长路径不会超过最短路径的两倍,它的查找效率比AVL树更慢(对于CPU来说可以忽略不计),但是它不会像AVL树那样花费更大的代价去实现严格平衡(旋转)。
1.红黑树与AVL树🔎
特性 红黑树 AVL树 平衡标准 通过颜色规则约束,允许一定不平衡 严格平衡(左右子树高度差≤1) 插入/删除效率 旋转次数少,调整频率低 可能需要多次旋转,调整频率高 查询效率 平均稍慢(高度≤2log(n+1)) 更快(高度严格为O(log n)) 适用场景 频繁插入/删除(如内存数据库、STL Map) 查询密集(如字典、静态数据集) 实现复杂度 较复杂(需处理颜色标记和多种情况) 相对简单(仅维护平衡因子) 2.设计思想🔎
- 平衡与效率的折衷:
红黑树通过放宽平衡条件(允许局部不平衡),减少插入/删除时的调整次数,适合写多读少的场景。AVL树追求绝对平衡,适合读多写少的场景。- 模拟B树结构:
红黑树可视为一种“二叉化”的B树(如2-3-4树),每个节点隐含多个键值,通过颜色标记合并逻辑,减少树的高度。- 工业界应用:
- 红黑树:Java的
TreeMap、C++的std::map、Linux内核调度器。- AVL树:Windows内核对象管理、需要快速查找的静态数据库。
所以,若需要高频插入/删除,我们可以选择红黑树,若需要快速查询,不怎么变动数据,选择AVL树。
3.红黑树的性质🔎
- 每个节点不是红色就是黑色。
- 根节点一定是黑色的。
- 如果一个节点是红色的,则它的两个孩子节点是黑色的。(不能出现连续的红色节点)。
- 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。(每条路径黑色节点数量相同)。
- 每个叶子节点都是黑色的(这里的叶子节点指的是空指针节点,也称NIL节点)
其中,路径的条数是从根节点到NIL节点来计算的,并且NIL节点的黑色也要统计到每条路径的黑色节点数量中。红黑树的最长和最短路径不一定存在。
红黑树插入实现🧐
红黑树的平衡方式为:直接变色、旋转变色,所以我们还是要用到AVL树中的旋转代码。
1.红黑树的节点定义🔎
enum Color //定义颜色枚举,增加可读性 {RED,BLACK };template<class K, class V> struct RBTreeNode {//一样的使用三叉链,方便旋转RBTreeNode<K, V>* _left; //左孩子RBTreeNode<K, V>* _right; //右孩子RBTreeNode<K, V>* _parent; //父节点pair<K, V> _kv; //这里使用的是KV模型Color _col; //颜色RBTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED) //默认设置为红色,如果默认为黑色,则每次插入必定违反性质4{} };2.左单旋、右单旋🔎
与AVL树中一摸一样的方法。
//左单旋 void RotateL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent; //记录传来结点的父节点,后面判断是否为整个树的根parent->_parent = subR;if (subRL) //subRL不为空才处理subRL->_parent = parent;if (_root == parent) //当parent就是整个树的根,直接改变{_root = subR;subR->_parent = nullptr;}else{//如果不是,结点在父左边就将左边置为subR,否则右边if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent; //父节点也要改变} }//右单旋 void RotateR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR) //subLR不为空才处理subLR->_parent = parent;//放在subL下面,不然parentParent可能为空Node* parentParent = parent->_parent; //记录传来结点的父节点,后面判断是否为整个树的根subL->_right = parent;parent->_parent = subL;if (_root == parent) //当parent就是整个树的根,直接改变{_root = subL;subL->_parent = nullptr;}else{//如果不是,结点在父左边就将左边置为subL,否则右边if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent; //subL父节点也要改变} }3.插入🔎
我们在节点定义时就将默认颜色设置为红色,是因为将默认颜色设置为黑色的话,那么一个正常的红黑树突然插入一个黑色必定违反性质4,则每次都需要调整。
而插入红色时我们进行分析得:
1.当插入节点的父亲节点为黑色时,没有违反任何性质,不需要处理。
2.当插入节点的父亲节点为红色时,违反性质3,需要处理。
由此再次对红黑树分情况讨论:
我们首先约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
情况一:cur为红,p为红,g为黑,u存在且为红
解决方法:
将p,u改为黑,g改为红,如果g是根节点,那么再改为黑,如果不是根节点且违反性质4,那么将g看为cur,再次进行调整。
情况二:cur为红,p为红,g为黑,u不存在或者u存在且为黑
解决方法:
如果p是g的左孩子,cur为p的左孩子,则进行右单旋;如果p是g的右孩子,cur为p的右孩子,则进行左单旋。然后进行p、g变色,p变黑,g变红。
情况三:cur为红,p为红,g为黑,u不存在或者存在且为黑。
解决方法:
如果p是g的左孩子,cur为p的右孩子,则进行左单旋;如果p是g的右孩子,cur为p的左孩子,则进行右单旋。然后转换为情况二处理。
bool Insert(const pair<K, V>& kv) { // 空树初始化:创建根节点并强制置黑 if (_root == nullptr) //首次插入必定为黑色 {_root = new Node(kv);_root->_col = BLACK;return true; }Node* parent = nullptr; Node* cur = _root;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->_col = RED;// 链接新节点到树中 if (parent->_kv.first < kv.first) {parent->_right = cur;cur->_parent = parent; } else {parent->_left = cur;cur->_parent = parent; }// 父亲为红色,需要处理 while (parent && parent->_col == RED) {Node* grandfather = parent->_parent; if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔节点存在且为红(颜色翻转)if (uncle && uncle->_col == RED)、{// g// p u// c//变色parent->_col = uncle->_col = BLACK; // 父叔变黑grandfather->_col = RED; // 祖父变红//继续往上处理cur = grandfather;parent = cur->_parent;}// 叔节点不存在或为黑(需要旋转)else{// 当前节点是父的左孩子if (cur == parent->_left){// g// p// cRotateR(grandfather);parent->_col = BLACK; // 原父节点变黑grandfather->_col = RED; //祖父变红}// 当前节点是父的右孩子else{// g// p// cRotateL(parent); // 先左旋父节点转换为情况二RotateR(grandfather);cur->_col = BLACK; //当前节点变黑grandfather->_col = RED; //祖父变红}break; // 旋转后子树平衡,退出循环}}// 父节点是祖父的右孩子else{// g// u p// cNode* 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// cRotateL(grandfather); // 左旋祖父节点parent->_col = BLACK;grandfather->_col = RED;}// 当前节点是父的左孩子else{// g// p// cRotateR(parent); // 先右旋父节点转换为情况二RotateL(grandfather); // 再左旋祖父节点grandfather->_col = RED;cur->_col = BLACK;}break;}} } _root->_col = BLACK; //直接根变黑,不在需要考虑根的颜色问题return true; }4.平衡检测🔎
//根节点到当前结点的黑色数量 bool Check(Node* root, int blacknum, const int refVal) {if (root == nullptr){if (blacknum != refVal){cout << "存在黑色结点数量不相等的路径!" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){//当两个结点都为红,就出问题了cout << "有连续的红色" << endl;return false;}if (root->_col == BLACK){++blacknum;}return Check(root->_left, blacknum, refVal) && Check(root->_right, blacknum, refVal); }bool IsBalance() {if (_root == nullptr)return true;if (_root->_col == RED)return false;int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refVal;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal); }
结尾👍
以上便是红黑树的介绍和插入分析,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹
相关文章:
【C++第二十章】红黑树
【C第二十章】红黑树 红黑树介绍🧐 红黑树是一种自平衡的二叉搜索树,通过颜色标记和特定规则保持树的平衡性,从而在动态插入、删除等操作中维持较高的效率。它的最长路径不会超过最短路径的两倍,它的查找效率比AVL树更慢(对于CPU…...
如何修改Windows系统Ollama模型存储位置
默认情况下,Ollama 模型会存储在 C 盘用户目录下的 .ollama/models 文件夹中,这会占用大量 C 盘空间,增加C盘“爆红”的几率。所以,我们就需要修改Ollama的模型存储位置 Ollama提供了一个环境变量参数可以修改Ollama的默认存在位…...
OpenAI ChatGPT在心理治疗领域展现超凡同理心,通过图灵测试挑战人类专家
近期,一项关于OpenAI ChatGPT在心理治疗领域的研究更是引起了广泛关注。据报道,ChatGPT已经成功通过了治疗师领域的图灵测试,其表现甚至在某些方面超越了人类治疗师,尤其是在展现同理心方面,这一发现无疑为AI在心理健康…...
Netflix Ribbon:云端负载均衡利器
Netflix Ribbon:云端负载均衡利器 ribbon Ribbon is a Inter Process Communication (remote procedure calls) library with built in software load balancers. The primary usage model involves REST calls with various serialization scheme support. 项目地…...
MAVSDK - Custom Mavlink处理
编译命令中开启 Custom Mavlink 编译 cmake -DCMAKE_BUILD_TYPERelease -DMAVLINK_DIALECTcustom -DBUILD_CUSTOM_MAVLINKON -DCUSTOM_MAVLINK_PATH"G:/Custom_Mavlink" -DBUILD_CUSTOM_PLUGINSON -DENABLED_CUSTOM_PLUGINS"speaker" -DENABLED_PLUGINS&qu…...
【Android】Android 悬浮窗开发 ( 动态权限请求 | 前台服务和通知 | 悬浮窗创建 )
文章目录 一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后返回处理 二、悬浮窗 前台服务和通知1、前台服务 启动 悬浮窗 的必要性① 保持悬浮窗存活② 悬浮窗的要求③ 悬浮窗版本兼容 2、其它类型服务简介① 前台服务…...
Python高级语法之jsonpathBeautifulSoup解析器
目录: 1、jsonPath的使用2、使用jsonpath解析淘票票网页3、BeautifulSoup解析器的使用4、BeautifulSoup层级选择器的使用 1、jsonPath的使用 2、使用jsonpath解析淘票票网页 3、BeautifulSoup解析器的使用 4、BeautifulSoup层级选择器的使用...
工业安卓主板在智慧粮仓设备中发挥着至关重要的作用
工业安卓主板在智慧粮仓设备中发挥着至关重要的作用。以下是关于其作用的具体分析: 一、提供稳定可靠的运行平台 智慧粮仓设备需要长时间稳定运行,以实现对粮食储存环境的实时监测和精准控制。工业安卓主板采用高性能的处理器和大容量的存储空间&#…...
ECMAScript6----var、let、const
ECMAScript6----var、let、const 1.var2.let3.const 1.var (1)在相同作用域下可重复声明 var a 20 var a 30 console.log(a) // 30(2)存在变量提升 console.log(a) // undefined var a 20(3)可修改声…...
【ST-LINK未能被keil识别STM32 ST-LINK Utility出现“Can not connect to target】
针对各种品牌32MCU boot0拉高,boot1拉低进入系统存储器,对Flash先擦除在下载 针对STM32f103 通过32复位和stlink Utilit解决 https://blog.csdn.net/Donglutao/article/details/129086960 https://www.bilibili.com/video/BV1F94y1g7be/?spm_id_…...
Android Http-server 本地 web 服务
时间:2025年2月16日 地点:深圳.前海湾 需求 我们都知道 webview 可加载 URI,他有自己的协议 scheme: content:// 标识数据由 Content Provider 管理file:// 本地文件 http:// 网络资源 特别的,如果你想直接…...
用deepseek学大模型05逻辑回归
deepseek.com:逻辑回归的目标函数,损失函数,梯度下降 标量和矩阵形式的数学推导,pytorch真实能跑的代码案例以及模型,数据,预测结果的可视化展示, 模型应用场景和优缺点,及如何改进解决及改进方法数据推导。…...
python实践-实现实时语音转文字本地部署版(二)
一、技术栈 python 3.10.6 vosk 需下载对应模型(vosk-model-cn-0.22)模型下载慢的同学看最后的资源链接。 pyaudio keyboard 二、实现功能 本地化实现麦克风语音录入,实时生成文字,并保存至本地文档。 三、实现代码 fro…...
tortoiseSVN 如何克隆项目到本地
导入项目成功,如下图:...
解决“QString的split()函数分割中文“报错
在使用Qt平台的QString类里的split()函数,分割.txt文件里中文的字符串时,发现中文会乱码。 问题原因:中文使用UTF-16编码。 解决方法:将.txt文件保存为UTF-16编码,然后使用split()去分割对应的字符串即可。…...
云平台结合DeepSeek的AI模型优化实践:技术突破与应用革新
目录 前言 一、技术架构:算力与算法的协同基石 1. 蓝耘平台的核心优势 2. DeepSeek的模型创新 二、应用场景:垂直领域的智能化落地 1. 商业领域:智能推荐与客服 2. 工业领域:质检与流程优化 3. 智慧城市与医…...
蓝桥杯(B组)-每日一题(1093字符逆序)
c中函数: reverse(首位置,尾位置) reverse(s.begin(),s.end()) 头文件:<algorithm> #include<iostream> #include<algorithm>//运用reverse函数的头文件 using namespace std; int main() {string s;//定义一…...
jsherp importItemExcel接口存在SQL注入
一、漏洞简介 很多人说管伊佳ERP(原名:华夏ERP,英文名:jshERP)是目前人气领先的国产ERP系统虽然目前只有进销存财务生产的功能,但后面将会推出ERP的全部功能,有兴趣请帮点一下 二、漏洞影响 …...
基于ffmpeg+openGL ES实现的视频编辑工具-字幕添加(六)
在视频编辑领域,字幕的添加是一项极为重要的功能,它能够极大地丰富视频内容,提升观众的观看体验。当我们深入探究如何实现这一功能时,FreeType 开源库成为了强大助力。本文将详细阐述借助 FreeType 库生成字幕数据的过程,以及如何实现字幕的缩放、移动、旋转、颜色修改、对…...
一文讲清 AIO BIO NIO的区别
引言 在 Java 编程中,BIO(Blocking I/O)、NIO(Non-blocking I/O)和 AIO(Asynchronous I/O)是三种不同的 I/O 模型,它们在处理输入输出操作时有着不同的机制和特点,但是市…...
Qt 中使用 ffmpeg 获取采集卡数据录制视频
作者:billy 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 前言 之前做了一个功能,从采集卡获取数据然后录制成视频,结果发现录制的视频内存占用非常大,1分钟的…...
用HTML5+CSS+JavaScript实现新奇挂钟动画
用HTML5+CSS+JavaScript实现新奇挂钟动画 引言 在技术博客中,如何吸引粉丝并保持他们的关注?除了干货内容,独特的视觉效果也是关键。今天,我们将通过HTML5、CSS和JavaScript实现一个新奇挂钟动画,并将其嵌入到你的网站中。这个动画不仅能让你的网站脱颖而出,还能展示你的…...
一周学会Flask3 Python Web开发-redirect重定向
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 前面我们学过渲染到模板页面,这个其实是一种内部的转发,浏览器地址栏地址没有变化。如果我们想重定向…...
如何在 ConstraintLayout 中将 ViewPager 始终置于 ll_title 的下方
关于如何在 ConstraintLayout 中将 ViewPager 始终置于 ll_title标题栏 的下方。。 如何将 ViewPager 始终置于 ll_title 下方 在 ConstraintLayout 中,可以通过约束来实现 ViewPager 始终位于 ll_title 标题栏的下方。以下是修改后的布局代码: <?…...
文心一言大模型的“三级跳”:从收费到免费再到开源,一场AI生态的重构实验
2025年2月,百度文心大模型接连抛出两枚“重磅炸弹”:4月1日起全面免费,6月30日正式开源文心大模型4.5系列。这一系列动作不仅颠覆了李彦宏此前坚持的“闭源优势论”13,更标志着中国AI大模型竞争进入了一个全新的阶段——从技术壁垒…...
IPv6报头40字节具体怎么分配的?
目录 IPv6报头结构 字段详解 示例代码:IPv6报头的Python实现 输出示例 IPv6协议是为了解决IPv4地址耗尽问题而设计的下一代互联网协议。与IPv4相比,IPv6不仅提供了更大的地址空间,还简化了报头结构,提高了网络设备的处理效率。…...
使用 Spark NLP 实现中文实体抽取与关系提取
在自然语言处理(NLP)领域,实体抽取和关系提取是两个重要的任务。实体抽取用于从文本中识别出具有特定意义的实体(如人名、地名、组织名等),而关系提取则用于识别实体之间的关系。本文将通过一个基于 Apache Spark 和 Spark NLP 的示例,展示如何实现中文文本的实体抽取和…...
大数据治理之solr的体现
大数据治理之solr的体现 一,大数据治理下Solr的作用 在大数据治理的背景下,Solr作为一个高性能的搜索平台,发挥这重要的作用,下面是Solr在大数据治理中的几个关键作用和体现: 数据索引与检索: 高效检索&a…...
[笔记.AI]如何判断模型是否通过剪枝、量化、蒸馏生成?
以下摘自与DeepSeek-R1在线联网版的对话 一、基础判断维度 技术类型核心特征验证方法剪枝模型参数减少、结构稀疏化1. 检查模型参数量是否显著小于同类标准模型1 2. 分析权重矩阵稀疏性(如非零参数占比<30%)4量化权重/激活值精度降低、推理速度提升1…...
Uniapp 从入门到精通:基础篇 - 搭建开发环境
Uniapp 从入门到精通:基础篇 - 搭建开发环境 前言一、Uniapp 简介1.1 什么是 Uniapp1.2 Uniapp 的优势二、搭建开发环境前的准备2.1 安装 Node.js2.2 安装 HBuilderX三、创建第一个 Uniapp 项目3.1 打开 HBuilderX 并创建项目3.2 项目结构介绍3.3 运行项目四、配置项目4.1 配置…...



