C++进阶 | [4.3] 红黑树

摘要:什么是红黑树,模拟实现红黑树

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

1. 红黑树的性质

注意:红黑树是一种搜索二叉树。性质如下:

① 红黑树的根节点为 Black ;

Red 节点不连续,即 Red 节点一定有 Black 的孩子节点;

③ 每条路径含有相同数量的 Black 节点;(关于这点下文会详细解释)

④ 每个节点要么是 Black 的要么是 Red 的;

nullptr 节点为 Black(在红黑树中这样的节点被称为NIL节点,这样是为了方便找准路径,非硬性要求)

  • 路径 - path从根节点到空,称为一条路径。
    最短路径:如果一条路径的所有节点均为 Black ,则该路径为最短路径;
    最长路径: Black Red Black Red Black Red Black ……Red NIL 这样总是黑红节点相间的路径为最长路径。(注意:路径的结尾为空节点,而空节点一定是 Black 的,又因为红黑树的每条路径含有数量相同的 Black 节点,因此可得最长路径=最短路径×2-1

如图所示,path1和path2为该红黑树的最短路径;path8为该红黑树的最长路径。红黑树中的叶节点为特殊的叶节点,即 nullptr 节点,在此图中写作“NIL”。
与AVL树比较,AVL树是更接近满二叉树的严格平衡,而红黑树是近似平衡。


2. 模拟实现红黑树

1)框架

同样的,先定义红黑树的节点。根据红黑树对节点的“颜色”要求,这里采用枚举(enum)类型来表达节点的“颜色”。示例代码如下。

ps.默认新创建的节点为红色(下文会解释这样做的原因)。

enum Colour
{RED, BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode(T data = T()):_data(data), _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _col(RED)//默认新创建的节点都是红色的{}T _data;RBTreeNode<T>* _pLeft;RBTreeNode<T>* _pRight;RBTreeNode<T>* _pParent;Colour _col;
};// 模拟实现红黑树的插入--注意:为了后序封装map和set,在实现时给红黑树多增加了一个头结点
template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree(){_pHead = new Node;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;}private:Node* _pHead;
};

2)insert

红黑树的插入函数实现的思路同AVL树是类似的。

-🟡Part1.插入新节点-   红黑树首先是一个BST,先按BST的规则插入新节点。代码如下。注意:当我们插入新的节点时,总是默认新插入的节点为红色。理由是:如果在红黑树中插入黑色节点将对整棵红黑树造成影响,因为红黑树要求每条路径上含有数量相同的黑色节点,一旦插入黑色节点必然要应对红黑树的调整,而插入红色节点,若该新节点的parent节点不为红色节点则不用调整这棵红黑树。

// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false.注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{if (_pHead->_pLeft == _pHead && _pHead->_pRight == _pHead){Node* _pNewNode = new Node(data);_pHead->_pLeft = _pHead->_pRight = _pNewNode;_pNewNode->_pParent = _pHead;_pNewNode->_col = BLACK;//跟节点为黑色return true;}Node* pCur = _pHead->_pLeft;Node* pParent = pCur->_pParent;while (pCur){pParent = pCur;if (pCur->_data > data){pCur = pCur->_pLeft;}else if (pCur->_data < data){pCur = pCur->_pRight;}else{return false;}}Node* _pNewNode = new Node(data);if (pParent->_data > data)pParent->_pLeft = _pNewNode;elsepParent->_pRight = _pNewNode;_pNewNode->_pParent = pParent;//……return true;
}

-🟡Part2.调整红黑树-  由于插入的新节点默认为红色,因此当插入导致出现连续的红色节点时,需要调整红黑树。 

如上图。下一步,继续对圈出的subtree进行展开:👇

  • 情况一:当圈出的subtree的根节点 psibling 为红色,这种情况不需要旋转,直接调整节点颜色即可。从下图的换色中不难看出,这样换色之后,每个路径的黑色节点数量不受影响。注意:当 pGrandparent 变为红色后,可能会与 其parent 形成连续的红色节点,因此需要继续向上操作,即令 pCur = pGrandparent(赋值操作),继续判断,并在必要的情况下调整。(这个“向上的”思路和AVL树的调整是类似的)
    ps.pParent 不一定是 pGrandparent 的节点,pCur 不一定就是 pParent 的节点。这里之所以分析下图中的这一种情况是因为,这类情况的调整不需要旋转,即直接调整节点颜色——pParent和psibling都变红,因此这两个节点相对于pGrandparent的左右位置不重要,pCur不做改变,因此pCur相对于pParent的左右位置也不重要。但需要旋转调整的情况就不一样了,pParent 、pGrandparent 、pCur 这三个节点之间的关系决定了旋转的方向,下面“情况二”会详细讲解。

  • 情况二:当圈出的subtree根节点为黑色,这种情况需要旋转+调色,下面对这种情况继续细分。👇
    ps.这棵被圈出的 subtree 黑色节点数目为n,在情况二这种情况下,psibing已经是一个黑色节点了,因此其子树黑色节点为n-1,如图中所写。(n≥1) 另外,注意 psibing 可能为NIL,即黑色的空节点。

    如下图,第三列表示调色后的结果,调色思路可以归纳为——旋转后的树的新根变为黑色,旋转前的树的旧根变为红色。

示例代码:

{//RBTree// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false.注意:为了简单起见,本次实现红黑树不存储重复性元素bool Insert(const T& data){//前面插入新节点的部分省略///新插入的节点默认是红色的,因此若在这条含有新插入节点的路径中出现·连·续·红·色·节点则需要调整///pCur = _pNewNode;//以新插入的结点为起点向上调整while (pCur->_col == RED && pParent->_col == RED){Node* pGrandparent = pParent->_pParent;Node* pP_sibling = pGrandparent->_pLeft == pParent ? pGrandparent->_pRight : pGrandparent->_pLeft;if (pP_sibling && pP_sibling->_col == RED)//调色{pParent->_col = pP_sibling->_col = BLACK;if (pGrandparent != _pHead->_pLeft){pGrandparent->_col = RED;pCur = pGrandparent;pParent = pCur->_pParent;}}else//旋转+调色{if (pCur == pParent->_pLeft){if (pParent == pGrandparent->_pLeft)//右旋{RotateR(pGrandparent);pParent->_col = BLACK;}else//右左双旋{RotateR(pParent);RotateL(pGrandparent);//双旋后pCur成为新的subrootpCur->_col = BLACK;}pGrandparent->_col = RED;}else if (pCur == pParent->_pRight){if (pParent == pGrandparent->_pRight)//左旋{RotateL(pGrandparent);//左旋后pParent成为新的subrootpParent->_col = BLACK;}else//左右双旋{RotateL(pParent);RotateR(pGrandparent);pCur->_col = BLACK;}pGrandparent->_col = RED;}}}return true;}private:// 左单旋void RotateL(Node* subroot){Node* pGrandparent = subroot->_pParent;Node* subR = subroot->_pRight;Node* subR_L = subR->_pLeft;if (pGrandparent == _pHead){pGrandparent->_pLeft = pGrandparent->_pRight = subR;}else{if (subroot == pGrandparent->_pLeft){pGrandparent->_pLeft = subR;}elsepGrandparent->_pRight = subR;}subR->_pParent = pGrandparent;subR->_pLeft = subroot;subroot->_pParent = subR;subroot->_pRight = subR_L;if (subR_L)subR_L->_pParent = subroot;}// 右单旋void RotateR(Node* subroot){Node* pGrandparent = subroot->_pParent;Node* subL = subroot->_pLeft;Node* subL_R = subL->_pRight;if (pGrandparent == _pHead){pGrandparent->_pLeft = pGrandparent->_pRight = subL;}else{if (subroot == pGrandparent->_pLeft){pGrandparent->_pLeft = subL;}elsepGrandparent->_pRight = subL;}subL->_pParent = pGrandparent;subroot->_pLeft = subL_R;if (subL_R)subL_R->_pParent = subroot;subL->_pRight = subroot;subroot->_pParent = subL;}
};

ps.psibling 这个节点是有可能为空结点的,写旋转函数的时候要注意!

3)判断红黑树的合法性

要判断红黑树是否合法就需要比对着红黑树的性质一条一条来确认:

① 红黑树的根节点为 Black ;(⭕需要去判断树的根节点的颜色

Red 节点不连续,即 Red 节点一定有 Black 的孩子节点;(⭕需要验证

③ 每条路径含有相同数量的 Black 节点;(⭕需要验证

④ 每个节点要么是 Black 的要么是 Red 的;(✅通过enum类型来确保节点黑或红且无其他颜色可能

nullptr 节点为 Black。(✅这条显然不必验证

思路:选择一条路径并的到这条路径的黑色节点数量,并这条路径的黑色节点数量为标准,判断其他任以路径的黑色节点数量是否与其相等。简单起见,选择最左路径的黑色节点数量为标准数量去比对别的路径的黑色节点数量。

由上,关键问题在于如何计算出所有路径(除选择的标准路径)的外的黑色节点。以下图为例进行分析。

代码示例: 

// 检测红黑树是否为有效的红黑树
bool IsValidRBTRee()
{Node* pCur = _pHead->_pLeft;if (pCur == nullptr)return true;if (pCur->_col == RED)return false;size_t blackNUM = 0;while (pCur){if (pCur->_col == BLACK)++blackNUM;pCur = pCur->_pLeft;}++blackNUM;//算上最后的空节点size_t pathblack = 0;return _IsValidRBTRee(_pHead->_pLeft, blackNUM, pathblack);
}bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{if (pRoot == nullptr){++pathBlack;//算上最后的空节点if (blackCount == pathBlack)return true;elsereturn false;}if (pRoot->_col == BLACK){++pathBlack;return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);}else{if ((pRoot->_pLeft && pRoot->_pLeft->_col == RED)|| (pRoot->_pRight && pRoot->_pRight->_col == RED)){cout << "错误:存在连续的红色节点" << "->" << pRoot->_data << "->该节点附近存在连续红色节点" << endl;return false;}else{return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);}}
}

END

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

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

相关文章

分享一个在 WinForm 桌面程序中使用进度条展示报表处理进度的例子,提升用户体验

前言 在有些比较消耗时间的业务场景中&#xff0c;比如生成报表等&#xff0c;如果没有在操作的过程中向用户反馈操作进度&#xff0c;会让用户以为程序 “死” 掉了&#xff0c;用户体验非常不好。 WinForm 桌面程序项目与 Console 项目不一样&#xff0c;如果 Console 项目…

【C++】红黑树及其实现

目录 一、红黑树的定义1.为什么提出红黑树&#xff1f;2.红黑树的概念3.红黑树的性质 二、红黑树的实现1.红黑树的结构2.红黑树的插入2.1 uncle为红色2.2 uncle为黑色&#xff0c;且是grandfather的右孩子2.3 uncle为黑色&#xff0c;且是grandfather的左孩子 3.红黑树的验证 4…

虚拟机网络配置(静态网络)

解决问题&#xff1a;VMware中创建centOS虚拟机后使用ifconfig没有ip地址&#xff0c;但我想在主机&#xff08;Windows&#xff09;系统下使用shell连接虚拟机从而方便后续交互。 VMware中编辑->虚拟网络编辑器 &#xff08;注意需要管理员身份不然会无法修改&#xff09;…

PV操作经典例题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文☀️☀️☀️三、总结&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&am…

万字长文|下一代系统内存数据加速接口SDXI解读

本文内容分为5章节&#xff0c;总计10535字&#xff0c;内容较多&#xff0c;建议先收藏&#xff01; 1.SDXI技术产生的背景 2.SDXI相比DMA的优势 3.SDXI实现原理与架构 3.1 描述符环原理解读 3.2 上下文管理介绍 3.3 AKey与RKey解读 3.4 错误日志和状态管理 3.5 跨Function访…

ctfshow web入门 sqli-libs web552--web560

web552 宽字节注入 嗯原理我就不讲了&#xff0c;还是有点复杂后面有时间讲讲 总而言之就是用汉字把\的转义作用抵消了然后正常注入即可 ?id-1包 union select 1,2,3--?id-1包union select 1,(select group_concat(table_name) from information_schema.tables where tab…

事过无悔:人生中的释怀之道

在纷繁复杂的人生旅途中&#xff0c;我们常常会面临各种选择。这些选择&#xff0c;如同指引我们前行的路标&#xff0c;有时让我们欣喜&#xff0c;有时让我们遗憾。然而&#xff0c;我渐渐发现&#xff0c;事过无悔&#xff0c;是我们在面对这些选择时最顶级的释怀之道。 首…

MySQL 常见存储引擎详解(一)

本篇主要介绍MySQL中常见的存储引擎。 目录 一、InnoDB引擎 简介 特性 最佳实践 创建InnoDB 存储文件 二、MyISAM存储引擎 简介 特性 创建MyISAM表 存储文件 存储格式 静态格式 动态格式 压缩格式 三、MEMORY存储引擎 简介 特点 创建MEMORY表 存储文件 内…

节点级、系统级、实车级的LIN测试主要差异点

文章目录 前言一、节点级1.前期准备2.测试执行 二、系统级1.前期准备2.测试执行 三、实车级1.前期准备2.测试执行 总结 前言 LIN协议一致性测试主要指的是物理层&#xff08;电阻、电容、电压、地偏移、显隐性电平、频率占空比、位时间等&#xff09;、数据链路层&#xff08;…

window用户层文件系统fuse(wdm驱动)

dokany https://github.com/dokan-dev/dokany.git 需要安装wdk&#xff0c;2022可以直接通过windows driver扩展安装 项目使用nuget添加wdk依赖 Version of Uwp Package 10.0.26100.0 does not match TargetPlatformVersion 10.0.22621.0. windows sdk版本填写正确的版本号…

算法实验2.2、2.3

2.2主要内容 比较快速排序&#xff0c;归并排序以及堆排序算法的时间效率。了解影响算法执行时间的 主要因素以及如何降低算法的执行时间。 #include<iostream> using namespace std; #include<stdio.h> #include<malloc.h> #include<stdlib.h> #inc…

【论文阅读】-- 研究时间序列可视化,提升用户体验

Investigating Time Series Visualisations to Improve the User Experience 摘要1 引言2 相关工作互动技巧视觉编码坐标系 3 用户研究时间序列可视化互动技巧任务实验设计 4 结果交互技术的效果视觉编码的影响坐标系的影响 5 讨论交互技术的效果视觉编码的影响坐标系的影响 6 …

芒果YOLOv10改进122:注意力机制系列:最新结合即插即用CA(Coordinate attention) 注意力机制,CVPR 顶会助力分类检测涨点!

论文所提的Coordinate注意力很简单,可以灵活地插入到经典的移动网络中,而且几乎没有计算开销。大量实验表明,Coordinate注意力不仅有益于ImageNet分类,而且更有趣的是,它在下游任务(如目标检测和语义分割)中表现也很好。本文结合目标检测任务应用 应专栏读者的要求,写一…

cube-studio开源一站式机器学习平台,在线ide,jupyter,vscode,matlab,rstudio,ssh远程连接,tensorboard

全栈工程师开发手册 &#xff08;作者&#xff1a;栾鹏&#xff09; 一站式云原生机器学习平台 前言 开源地址&#xff1a;https://github.com/tencentmusic/cube-studio cube studio 腾讯开源的国内最热门的一站式机器学习mlops/大模型训练平台&#xff0c;支持多租户&…

【Linux】:环境变量

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux环境变量的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门…

mac|浏览器链接不上服务器但可以登微信

千万千万千万不要没有关梯子直接关机&#xff0c;不然就会这样子呜呜呜 设置-网络&#xff0c;点击三个点--选择--位置--编辑位置&#xff08;默认是自动&#xff09; 新增一个&#xff0c;然后选中点击完成 这样就可以正常上网了

【嵌入式DIY实例】- LCD ST7735显示DHT11传感器数据

LCD ST7735显示DHT11传感器数据 文章目录 LCD ST7735显示DHT11传感器数据1、硬件准备与接线2、代码实现本文介绍如何将 ESP8266 NodeMCU 板 (ESP-12E) 与 DHT11 (RHT01) 数字湿度和温度传感器连接。 NodeMCU 从 DHT11 传感器读取温度(以 C 为单位)和湿度(以 rH% 为单位)值,…

计算机网络-第5章运输层

5.1运输层协议概述 5.1.1进程之间的通信 运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。 通信的两端应当是两个主机中的应用进程。 运输层复用和分用&#xff1a;复用指在发送方不同的应用进程都可以…

【机器学习】FFmpeg+Whisper:二阶段法视频理解(video-to-text)大模型实战

目录 一、引言 二、FFmpeg工具介绍 2.1 什么是FFmpeg 2.2 FFmpeg核心原理 2.3 FFmpeg使用示例 三、FFmpegWhisper二阶段法视频理解实战 3.1 FFmpeg安装 3.2 Whisper模型下载 3.3 FFmpeg抽取视频的音频 3.3.1 方案一&#xff1a;命令行方式使用ffmpeg 3.3.2 方案二&a…

基于协同过滤的电影推荐与大数据分析的可视化系统

基于协同过滤的电影推荐与大数据分析的可视化系统 在大数据时代&#xff0c;数据分析和可视化是从大量数据中提取有价值信息的关键步骤。本文将介绍如何使用Python进行数据爬取&#xff0c;Hive进行数据分析&#xff0c;ECharts进行数据可视化&#xff0c;以及基于协同过滤算法…