红黑树构建模拟实现

目录

一.红黑树概述

二.红黑树的性质

​编辑 三.构建红黑树模拟实现

插入新节点情况分析

情况一、cur为红色,parent为红色,grandfather为黑色,uncle存在且为红

情况二、cur为红色,parent为红色,grandfather为黑色,uncle不存在

单旋的解决方案

双旋的解决方案 

情况三、cur为红色,parent为红色,grandfather,uncle存在且为黑色

单旋的解决方案 

双旋的解决方案

完整插入节点和调整的代码

四、红黑树检测


一.红黑树概述

红黑树也是一种二叉搜索树,在二叉搜索树的基础上它也定义了一些规则来使平衡树相对平衡。AVL树是通过左右高度差(平衡因子)来控制搜索树的平衡,构建出来是非常接近完全二叉树和满二叉树的树。而红黑树是一种相对平衡,最长路径不超过最短路径的两倍就可以了,红黑树在每个结点上增加一个存储位表示节点的颜色,可以是红色也可以是黑色,通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑色树确保没有一条路径会比其他路径长出两倍,从而达到相对平衡。相同之处在于红黑树也要用到旋转来调整节点,但是旋转的次数要少一点(因为触发旋转的条件放松了)

二.红黑树的性质

(1)、每个节点不是黑色就是红色

(2)、根节点是黑色的

(3)、如果一个节点是红色的,则它的两个孩子节点必须是黑色的(不能有连续的红色节点)

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

(5)、每个叶子节点都是黑色的(此处的叶子节点指空节点)

为什么从这五条性质就可以限制了最长路径中的节点个数不会超过最短路径节点个数的两倍

首先最短路径节点个数是可以算红色节点也可以算黑色节点个数的,但是因为第四条性质限制了黑色节点数量,如果这棵树限制了黑色节点只能有两个,那么无疑两个都是黑色节点就是最短节点(此时最短路径节点个数是2)。如果非要加入红色节点,那么要么是在两个黑色节点后面加上红节点(因为要保证必须有两个黑色节点,但是此时节点个数变为3了,远不如只有两个黑色节点的情况),要么就得为了保证最短路径的情况下减去一个黑色节点,改为加上红色节点(此时该条路径的节点个数也为2,正好持平两个黑色节点的情况),但是这样就不足必须两个黑色节点的数量的限制。所以最短路径只能是整条路径都是黑色节点

最长路径只能是一黑一红间隔了,性质三限制了不能有连续的红色节点,而性质四限制了如果连续黑色节点肯定不能是最长节点,所以黑色间隔红色节点才是最合适的

 三.构建红黑树模拟实现

 首先要明确的是新插入节点的颜色一定是先是红色的,虽然父节点可能也是红色的(破坏了性质三),但是新节点是红色的只会影响到从根节点到新插入节点的这一条路径的节点,所以只需要调整这一条路径就可以了。但是如果新插入节点颜色是黑色,那么因为性质四限制了所有路径的黑色节点数量相同,而此时在这条路径上加了一个黑色节点意味着所有路径都要想办法加一个黑色节点,这样代价就非常大了

插入新节点情况分析

情况一、cur为红色,parent为红色,grandfather为黑色,uncle存在且为红

情况一

如果d、e为空,那么就意味着a、b、c一定为空,cur一定是新插入节点,因为只有这样才能黑色节点数量相等

但是此时cur和parent都为红,破坏了性质三不能有连续的红节点,解决办法就是parent和uncle都直接变黑,grandfather变为红。为什么grandfather要变为红而不是黑呢,因为当前这种解决方法实际上是grandfather的所有孩子路径都加了一个黑色节点,但是grandfather又不一定是根节点,此时是破坏了性质四,所以要往上处理把其余路径的黑色节点数量做一个调整。如果grandfather就是根节点的话,那就不需要往上更新了,因为所有的路径相当于已经都加了一个黑色节点了

 如果d、e有一个黑色节点,那么说明a、b、c、d必定也有一个黑色节点,此时cur不是新增节点,而是通过上面那种情况变换而来的grandfather节点,也就是说此时是上面那种grandfather节点不是根节点还需往上处理的情况。此时uncle还是存在而且为红色,处理方法也是parent和uncle节点都变黑,grandfather节点变红,继续往上处理

 

所以总结一下,如果cur存在且为红,parent也为红,grandfather为黑,uncle节点存在且为黑,那么解决办法都是parent和uncle变为黑色,grandfather节点变为红色,往上接着处理 

情况二、cur为红色,parent为红色,grandfather为黑色,uncle不存在

此时cur一定是新增节点,因为如果不是新增节点,要么cur是之前情况一转变而来变为红色的grandfather节点,而情况一的一个大前提是grandfather节点是黑色的,此时u不存在说明右边一个黑色节点都没有,而左边却多一个黑色节点,这样就破坏性质四了,所以cur一点是新增节点为红色。

那么怎么解决连续的红色呢,此时是一边高,一边低,直接染为黑色是无法解决问题的,所以要用到旋转。而此时判断单旋还是双旋就只是看cur是parent的那个孩子,如果parent是grandfather节点的左孩子,而cur也是parent的左孩子,那么这时候就只是左边高右边低而已,所以直接右单旋就可以了。而如果parent是grandfather的左孩子,但是cur是parent的右孩子的话,此时不是单纯的左边高,所以需要先左单旋使它变为单纯一边高,然后整体进行右单旋。反过来,如果parent是grandfather节点的右孩子,而cur也是parent的右孩子,那么这时候就只是右边高左边低而已,所以直接左单旋就可以了。而如果parent是grandfather的右孩子,但是cur是parent的左孩子的话,此时不是单纯的右边高,所以需要先右单旋使它变为单纯一边高,然后整体进行左单旋。

单旋的解决方案

单旋parent上去当根节点,而grandfather下来当孩子节点,其实就是parent和grandfather换了个位置和颜色,而此时压根就没有加黑色节点,所以不需要往上进行处理节点

双旋的解决方案 

旋转完染色只有cur和grandfather进行染色就可以了,parent可以不用动

情况三、cur为红色,parent为红色,grandfather,uncle存在且为黑色

请注意此时cur为红色是原本是黑色,但是下面的子树里插入新节点导致cur变色变为红色了,而此时parent也是红色,此时有连续的红色节点了,所以必须进行调整处理。情况三的处理方案和情况二的处理方案一样,所以很多人会把情况二三放到一块去讲

情况三

 

上图的抽象图里,如果d、e子树都为空的话,c是必须要有一个黑色节点的,a、b可以为空,也可以只为红色节点,但是不能有黑色节点(因为只有这样每条路径上的黑色节点数量相等)。a、b无论哪个新插入节点都会触发情况一,导致cur变红

单旋的解决方案 

单旋与情况二也类似,如果parent是grandfather的左孩子,cur是parent的左孩子,那么就直接右单旋就可以了。如果parent是grandfather的右孩子,而cur是parent的右孩子,那么就需要左单旋。单旋变色只需要parent变为黑色,grandfather变为红色就可以了

双旋的解决方案

 如果parent是grandfather的左孩子,cur是parent的右孩子,那么就需要左右双旋。如果parent是grandfather的右孩子,而cur是parent的左孩子,那么就需要右左单旋。旋转完,grandfather变为红色,cur变为黑色

完整插入节点和调整的代码

bool insert(const T& key){// 如果树为空,则插入新节点作为根节点  if (root == nullptr){root = new Node(key); // 创建新节点  root->col = black;    // 根节点总是黑色  root->_parent = nullptr; // 根节点没有父节点  }// 初始化当前节点和父节点  Node* cur = root;Node* parent = cur->_parent;// 查找插入位置  while (cur){if (key > cur->_key) // 如果新键大于当前节点的键,向右移动  {parent = cur;    // 更新父节点  cur = cur->right; // 切换到右子树  }else if (key < cur->_key) // 如果新键小于当前节点,向左移动  {parent = cur;     // 更新父节点  cur = cur->left; // 切换到左子树  }else // 如果新键等于当前节点的键,则不允许重复插入  {return false; // 插入失败  }}// 创建新节点并插入  cur = new Node(key); // 创建新节点  cur->col = red;      // 新节点初始为红色  // 将新节点链接到父节点  if (cur->_key > parent->_key) // 新节点键大于父节点键,插入右边  {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) // 再次检查叔叔节点颜色  {// 情况1:叔叔节点也为红色  uncle->col = parent->col = black; // 将父节点和叔叔节点设置为黑色  grandfather->col = red;           // 将祖父节点设置为红色  cur = grandfather;                // 继续向上调整  parent = cur->_parent;            // 更新父节点  }else // 叔叔节点为黑色  {if (cur == parent->left) // 情况2:新插入节点是父节点的左子节点  {RotateR(grandfather); // 旋转祖父节点向右  parent->col = black;  // 设置父节点为黑色  grandfather->col = red; // 设置祖父节点为红色  }else // 情况3:新插入节点是父节点的右子节点  {RotateL(parent);          // 先左旋转父节点  RotateR(grandfather);     // 再右旋转祖父节点  cur->col = black;         // 设置新节点为黑色  grandfather->col = red;   // 设置祖父节点为红色  }break; // 调整完成,退出循环  }}else // 如果父节点是祖父节点的右子节点  {Node* uncle = grandfather->left; // 叔叔节点  if (uncle && uncle->col == red) // 再次检查叔叔节点颜色  {// 情况1:叔叔节点也为红色  uncle->col = parent->col = black; // 将父节点和叔叔节点设置为黑色  grandfather->col = red;           // 将祖父节点设置为红色  cur = grandfather;                // 继续向上调整  parent = cur->_parent;            // 更新父节点  }else // 叔叔节点为黑色  {if (cur == parent->right) // 情况2:新插入节点是父节点的右子节点  {RotateL(grandfather); // 旋转祖父节点向左  parent->col = black;  // 设置父节点为黑色  grandfather->col = cur->col = red; // 设置祖父节点为红色,保持新节点为红色  }else // 情况3:新插入节点是父节点的左子节点  {RotateR(parent);          // 先右旋转父节点  RotateL(grandfather);     // 再左旋转祖父节点  grandfather->col = red;   // 设置祖父节点为红色  cur->col = black;         // 设置新节点为黑色  }break; // 调整完成,退出循环  }}}// 确保根节点始终为黑色  root->col = black;return true; // 插入成功  }

四、红黑树检测

红黑树检测一般是按两方面来进行检测的,一方面检测不能有连续的红节点,但是检查一个节点和自己的两个孩子节点是否是连续红色节点是很难的,所以反过来检测一个节点是否和自己的父节点是否都是红色就可以了。另一方面检测每条路径的黑色节点数量是否相同,首先是先循环走完一条路径查出这条路径的黑色节点的数量num,然后递归查找每一条路径黑色节点数量,每次都与事先存储好的num进行比较,如果有不一样的就说明黑色节点数量不相等

// 递归检查红黑树的性质  bool _isRBtree(const Node* root, int num, int size){// 如果当前节点是空,检查黑色节点数量  if (root == nullptr){// 如果当前黑色节点数量不等于树中应有的数量,则返回失败  if (num != size){cout << "黑色节点数量不对" << endl; // 输出错误信息  return false; // 返回 false,表示不符合红黑树性质  }return true; // 空节点,符合红黑树性质  }// 如果当前节点是黑色,增加黑色节点计数  if (root->col == black){size++; // 增加黑色节点的计数  }// 检查是否有连续的红色节点  if (root->col == red && root->_parent != nullptr && root->_parent->col == red){cout << "有连续的红色节点" << endl; // 输出错误信息  return false; // 返回 false,表示不符合红黑树性质  }// 递归检查左子树和右子树  return _isRBtree(root->left, num, size) && _isRBtree(root->right, num, size);}// 检查整个红黑树的性质  bool isRBtree(){// 根节点必须是黑色  if (root->col == red)return false; // 根节点为红色,返回 false  // 树为空,满足红黑树性质  if (root == nullptr)return true;// 从根节点开始  Node* cur = root;int num = 0; // 用于统计黑色节点数量  // 统计从根节点到最左叶子的路径中的黑色节点数量  while (cur){if (cur->col == black) // 如果当前节点是黑色  num++; // 增加黑色节点计数  cur = cur->left; // 移动到左子节点  }int size = 0; // 用于记录路径上统计到的黑色节点数量  return _isRBtree(root, num, size); // 递归检查树的性质  }

 随机插入一万个数据进行检测一下

五、时间复杂度分析

红黑树的时间复杂度与树的高度有关(也就是和路径的节点个数有关),而红黑色最短的路径可以是logn(接近满二叉树),但是最长的路径是2logn,时间复杂度是2logn。与AVL树相比,时间复杂度看上去差一点,但是2logn和logn都是一个量级的,忽略常数,都是O(logn)

从代码复杂性来看,AVL树要调多次旋转,所以个人认为AVL树复杂一点。红黑树的旋转次数比AVL树,因为它的旋转条件放松一些。但是红黑色是近似平衡,所以同量级下红黑树的高度比AVL要高很多

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

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

相关文章

VS运行程序时报错--无法定位程序输入点

发现问题&#xff1a; VS 在运行程序时&#xff0c;报错&#xff1a; 找到原因&#xff1a; 因为我在替换动态库的时候&#xff0c;只替换了lib库&#xff0c;没有替换运行目录下的dll库&#xff0c;运行时候的dll与程序中的lib库不对应。 替换库后就能解决这个问题。

PyTorch使用------自动微分模块

目录 &#x1f354; 梯度基本计算 1.1 单标量梯度的计算 1.2 单向量梯度的计算 1.3 多标量梯度计算 1.4 多向量梯度计算 1.5 运行结果&#x1f4af; &#x1f354; 控制梯度计算 2.1 控制不计算梯度 2.2 注意: 累计梯度 2.3 梯度下降优化最优解 2.4 运行结果&#x1…

dgl库安装

此篇文章继续上一篇pytorch已经安装成功的情况 &#xff08;python3.9&#xff0c;pytorch2.2.2&#xff0c;cuda11.8&#xff09; 上一篇pytorch安装教学链接 选择与之匹配的版本 输入下方代码进行测试 import dgl.data dataset dgl.data.CoraGraphDataset() print(‘Numb…

契约锁与您相约2024新疆数字经济创新大会暨新疆数字丝路博览会

9月20日&#xff0c;由新疆数字经济联合会主办&#xff0c;多家行业协会及企业共同承办的“2024(第一届)新疆数字经济创新发展大会暨新疆数字丝路博览会”在新疆国际会展中心盛大开幕&#xff0c;活动期间&#xff0c;契约锁作为电子签章行业领先的服务商携数字可信系列产品亮相…

小程序服务零工市场

零工市场小程序有着信息发布、岗位匹配、线上接单、零工人员保障险参保、技能培训、费用结算、完工确认、服务评价、纠纷调解等功能&#xff0c;为求职者和雇主搭建一座高效、便捷、精准的对接桥梁。 用工单位通过小程序的“雇主找人”&#xff0c;发布招聘信息&#xff0c;找到…

本地生活商城开发搭建 同城O2O线上线下推广

同城本地化商城目前如火如荼&#xff0c;不少朋友咨询本地生活同城平台怎么开发&#xff0c;今天商淘云与大家分享同城O2O线上商城的设计和开发。 本地生活商城一般会涉及到区域以及频道类&#xff0c;一般下单需要支持用户定位、商家定位&#xff0c;这样利于用户可以快速找到…

一文解读OLAP的工具和应用软件

OLAP&#xff08;OnlineAnalyticalProcessing&#xff09;是一种用于快速分析大规模、多维度数据的方法。OLAP工具和应用软件则是帮助人们进行OLAP分析的重要工具。本文将介绍几种常见的OLAP工具和应用软件&#xff0c;并探讨它们在数据分析中的作用。 一 OLAP工具的分类 在选…

巴菲特的长期投资策略:新投资者实现财务自由的启示

在投资界&#xff0c;沃伦巴菲特的名字几乎无人不晓。作为伯克希尔哈撒韦公司的董事长和首席执行官&#xff0c;巴菲特以其卓越的投资智慧和长期价值增长策略&#xff0c;成为了全球投资者的偶像。巴菲特的成功不仅仅是因为他的财富&#xff0c;更在于他对投资的深刻理解和对财…

Centos 7 搭建Samba

笔记&#xff1a; 环境&#xff1a;VMware Centos 7&#xff08;网络请选择桥接模式&#xff0c;不要用NAT&#xff09; 遇到一个问题就是yum 安装404&#xff0c;解决办法在下面&#xff08;没有遇到可以无视这句话&#xff09; # 安装Samba软件 yum -y install samba# 创建…

使用MinIO+PicGo在服务器搭建图床

创建minio目录 用于存放Minio可执行文件 mkdir /usr/local/minio下载minio # 进入到/usr/local/minio cd /usr/local/minio # 执行下载 wget https://dl.min.io/server/minio/release/linux-amd64/minio # 授权下载文件为可执行文件 chmod x minio创建存储目录 # 新建data存…

最短路: Djikstra

最短路: Djikstra 适用于边权非负 如果存在负边权, 则当前距离dist最小的点, 不一定就是实际离源点最近的点,可能有负边导致其它路径离当前点更近 如下图所示, 如果存在负边, y点距离S点最近, 所以选中y点进行松弛, 贪心思想 当边权非负,离起点S最近的点,不能被更新, 如果在…

相亲交友系统 现代爱情的导航仪

在这个数字化的时代&#xff0c;人们的生活方式发生了翻天覆地的变化&#xff0c;其中最显著的变化之一便是交友方式的转变。编辑h17711347205随着社会节奏的加快&#xff0c;越来越多的人选择通过相亲交友系统来寻找人生伴侣。相亲交友系统不仅简化了传统的交友流程&#xff0…

【版本更新】TDuckX表单1.9版来了

hi&#xff0c;朋友们大家好&#xff0c;填鸭表单TDuckX迎来了9月的版本更新&#xff1b;接下来让我们看看本次更新的详细内容吧。 1.新增360评估(Bate版本) 360度评估反馈&#xff08;360Feedback&#xff09;&#xff0c;又称“360度考核法”或“全方位考核法”&#xff0c…

人工智能在肿瘤浸润淋巴细胞研究中的最新进展|文献速递·24-09-20

小罗碎碎念 文献速递&#xff5c;目录 一、胆道癌治疗应答的新型AI生物标志物&#xff1a;肿瘤浸润性淋巴细胞的空间分布 补充文献&#xff1a;22年发表于JCO的一篇类似文献 二、生物标志物在肝细胞癌管理中的作用&#xff1a;从发现到临床应用 三、肿瘤样本中免疫细胞浸润水…

pdb文件查看工具pdbripper.exe

下载地址:https://www.bing.com/ck/a?!&&p249322afbfbc575bJmltdHM9MTcyMTM0NzIwMCZpZ3VpZD0yMjBkODE2MC1hYjNhLTZkYTMtMGVlYi05NWQ5YWE3OTZjOGEmaW5zaWQ9NTE4Mg&ptn3&ver2&hsh3&fclid220d8160-ab3a-6da3-0eeb-95d9aa796c8a&psqpdbripper.exe&…

[Linux]:信号(上)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 信号的引入 1.1 信号的概念 在Linux系统中&#xff0c;信号&#xff08;…

2024年及未来:构筑防御通胀的堡垒,保护您的投资

随着全球经济的波动和不确定性&#xff0c;通货膨胀已成为投资者不得不面对的现实问题。通胀会侵蚀货币的购买力&#xff0c;从而影响投资的实际回报。因此&#xff0c;制定有效的策略来保护投资免受通胀影响&#xff0c;对于确保资产的长期增值至关重要。在2024年及未来&#…

着色器ShaderMask

说明 实现一个渐变进度条&#xff0c;要求&#xff1a; 颜色渐变的过程是循序渐进的&#xff0c;而不是看起来像是将渐变条逐渐拉长了。 效果 源码 // 渐变进度条Stack(children: [// 背景色板Container(width: 300,height: 8,decoration: BoxDecoration(borderRadius: Bord…

Vue2中路由的介绍和使用

一.路由的基本概念 一说路由&#xff0c;大家首先想到的可能是路由器&#xff0c;路由器的原理就是给连接的设备一个IP地址&#xff0c;通过IP地址来映射到设备&#xff0c;实现连接&#xff0c;本质上是一种映射关系。 在vue2中就是路径与组件间的映射。 路由是使用vue2制作…

基于SpringBoot+Vue的“课件通”中小学教学课件共享平台

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…