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

目录

  • 一、红黑树的定义
    • 1.为什么提出红黑树?
    • 2.红黑树的概念
    • 3.红黑树的性质
  • 二、红黑树的实现
    • 1.红黑树的结构
    • 2.红黑树的插入
      • 2.1 uncle为红色
      • 2.2 uncle为黑色,且是grandfather的右孩子
      • 2.3 uncle为黑色,且是grandfather的左孩子
    • 3.红黑树的验证
  • 4.红黑树的查找效率分析
  • 5.整体代码

一、红黑树的定义

1.为什么提出红黑树?

AVL树和红黑树的插入、删除和查找操作的时间复杂度都是 l o g ( n ) log(n) log(n),既然如此,为什么会提出红黑树这一概念呢?
AVL树在插入和删除过程中,为了保持平衡性,会非常频繁地调整全树整体的拓扑结构,代价较大,为此在AVL树的平衡标准上进一步放宽条件,引入了红黑树的结构。

2.红黑树的概念

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

3.红黑树的性质

一棵红黑树是满足如下红黑性质的二叉排序树:

  1. 每个结点或是红色,或是黑色的。
  2. 根结点是黑色的。
  3. 叶结点(这里指的是空结点)都是黑色的。
  4. 不存在两个相邻的红结点(即红结点的父结点和孩子结点均是黑色的)。
  5. 对每个结点,从该结点到任意一个叶结点的简单路径上,所含黑结点的数量相同。

对于这样的红黑树,有两个结论:

  1. 从根到叶结点的最长路径不大于最短路径的2倍。
  2. 有n个内部结点的红黑树的高度 h < = 2 l o g 2 ( n + 1 ) h<=2log_2(n+1) h<=2log2(n+1)

二、红黑树的实现

1.红黑树的结构

红黑树的结构就是在二叉排序树的基础上加上了一个表示颜色的变量,我们可以使用枚举来实现。

enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;T _data;Colour _col;	// 颜色RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}
};

2.红黑树的插入

红黑树的插入操作也需要先按照二叉排序树的插入方式插入结点,然后根据其是否破坏了红黑树的性质来进行调整。
当我们插入一个新结点时,其默认颜色是红色,不过当这个新结点为根时,则要把它改成黑色。
红黑树的插入调整共分为三种情况,想要知道该选用哪种方式,要看的是叔叔结点的位置和颜色,因此我们想要进行调整,必须准备好四个结点:cur(当前结点)、parent(父结点)、grandfather(爷结点)和uncle(叔叔结点)。

2.1 uncle为红色

当叔叔结点为红色时,不需要旋转操作即可完成调整,先将parent和uncle变为黑色,再将grandfather变为红色即可,如果grandfather是根结点,则调整后要将其改成黑色,如果其父结点是红色则需要继续向上调整。
在这里插入图片描述

2.2 uncle为黑色,且是grandfather的右孩子

这里的uncle可以为空,因为叶结点就是黑色的。
此时若cur在parent的左侧,则可以进行右旋,右旋后将parent和grandfather的颜色进行交换。
在这里插入图片描述
若cur载parent的右侧,需要进行两次旋转,先对parent进行左旋,此时即可看作是cur在parent的左侧了,再对grandfather进行右旋,再将cur改为黑色,parent和grandfather改为红色即可。
在这里插入图片描述

2.3 uncle为黑色,且是grandfather的左孩子

这种情况则和上一种情况相对称,cur在parent的右侧,则可以进行左旋,左旋后将parent和grandfather的颜色进行交换。
若cur载parent的左侧,需要进行两次旋转,先对parent进行右旋,此时即可看作是cur在parent的右侧了,再对grandfather进行左旋,再将cur改为黑色,parent和grandfather改为红色即可。

以下是插入的代码实现:

bool Insert(const T& data)
{// 先按照二叉排序树的方式进行排序if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (data < cur->_data){parent = cur;cur = cur->_left;}else if (data > cur->_data){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(data);if (data < parent->_data){parent->_left = cur;}else{parent->_right = 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){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}// 当叔叔为黑色else{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;parent->_col = RED;grandfather->_col = RED;}break;}}// 当叔叔在爷结点的左侧else{Node* uncle = grandfather->_left;// 当叔叔为红色if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else // 当叔叔为黑色{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;parent->_col = RED;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}

3.红黑树的验证

由于红黑树的删除过于复杂,这里就不再谈了。
关于红黑树的验证,我们需要从红黑树的性质入手,查看当前红黑树是否违背了性质。性质1、3不必检查,剩下的就是2、4和5了。
关于性质2,只需要简单一条if语句即可判定。
对于性质4,我们可以对红黑树进行遍历,如果父结点和当前结点同时为红色,则可以返回false。
性质5想要判断有一点麻烦了,我们可以使用一个mark来记录某一条路径上有多少黑色结点,然后递归每一条路径,来判断是否有路径上黑色结点总值与mark不同。
以下是具体实现:

bool Isbalance()
{if (_root && _root->_col == RED){cout << "根结点为红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++benchmark;}cur = cur->_left;}return _Check(_root, 0, benchmark);
}bool _Check(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark){cout << "某条路径黑色节点数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blacknum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续红色节点" << endl;return false;}return _Check(root->_left, blacknum, benchmark)&& _Check(root->_right, blacknum, benchmark);}

4.红黑树的查找效率分析

我们先引入一个概念,
黑高:从某结点出发(不含该结点)到达一个叶结点的任意一个简单路径上的黑结点总数称为该结点的黑高(记为bh),黑高的概念是由性质5确定的。根结点的黑高称为红黑树的黑高。
比如下图中13的bh=2,15的bh=1。
在这里插入图片描述
由红黑树的性质可知,从根到任意一个叶结点的简单路径最短时,就是这条路径上全是黑结点。某条路径最长时,这条路径必然是由黑结点和红结点相间构成的。这样也就引出了结论1:从根到叶结点的最长路径不大于最短路径的两倍。这样就能够知道根的黑高至少为h/2,于是就有 n > = 2 h / 2 − 1 n>=2^{h/2}-1 n>=2h/21可以证得结论2。
可见红黑树的“适度平衡”,由AVL树的“高度平衡”,降低到“人体一个结点左右子树的高度,相差不会超过2倍”,也降低了动态操作时调整的频率。对于一颗动态查找树,若插入和删除操作比较少,查找操作比较多,则采用AVL树比较合适,否则采用红黑树比较合适。
但由于维护这种高度平衡所付出的代价比获得的收益大得多,红黑树的实际应用更广泛,比如C++中的map和set、Java中的TreeMap和TreeSet就是用红黑树实现的。

5.整体代码

enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;T _data;Colour _col;	// 颜色RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}
};template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:~RBTree(){_Destroy(_root);}Node* Find(const T& key){Node* cur = _root;while (cur){if (cur->_data < key){cur = cur->_right;}else if (cur->_data > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Insert(const T& data){// 先按照二叉排序树的方式进行排序if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (data < cur->_data){parent = cur;cur = cur->_left;}else if (data > cur->_data){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(data);if (data < parent->_data){parent->_left = cur;}else{parent->_right = 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){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}// 当叔叔为黑色else{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;parent->_col = RED;grandfather->_col = RED;}break;}}// 当叔叔在爷结点的左侧else{Node* uncle = grandfather->_left;// 当叔叔为红色if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else // 当叔叔为黑色{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;parent->_col = RED;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}bool Isbalance(){if (_root && _root->_col == RED){cout << "根结点为红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++benchmark;}cur = cur->_left;}return _Check(_root, 0, benchmark);}int Height(){return _Height(_root);}void InOrder(){_InOrder(_root);cout << endl;}
protected:void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}bool _Check(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark){cout << "某条路径黑色节点数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blacknum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续红色节点" << endl;return false;}return _Check(root->_left, blacknum, benchmark)&& _Check(root->_right, blacknum, benchmark);}int _Height(Node* root){if (root == nullptr){return 0;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_data << " ";_InOrder(root->_right);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;subR->_left = parent;parent->_right = subRL;Node* ppnode = parent->_parent;		// 记录parent的父结点parent->_parent = subR;if (subRL){subRL->_parent = parent;}if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){// subL:parent的左孩子// subLR:parent的左孩子的右孩子,注意:该点可能不存在Node* subL = parent->_left;Node* subLR = subL->_right;subL->_right = parent;parent->_left = subLR;Node* ppnode = parent->_parent;		// 记录parent的父结点,用于连接新的子树parent->_parent = subL;if (subLR){subLR->_parent = parent;}if (ppnode == nullptr){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}
private:Node* _root = nullptr;
};void RBTreeTest1()
{// int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };RBTree<int> t1;for (auto e : a){t1.Insert(e);}t1.InOrder();cout << t1.Isbalance() << endl;
}void RBTreeTest2()
{srand(time(0));const size_t N = 5000000;RBTree<int> t;for (size_t i = 0; i < N; ++i){size_t x = rand() + i;t.Insert(x);}cout << t.Isbalance() << endl;cout << t.Height() << endl;
}

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

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

相关文章

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

解决问题&#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;以及基于协同过滤算法…

SuperMap GIS基础产品FAQ集锦(20240701)

一、SuperMap iDesktopX 问题1&#xff1a;对于数据提供方提供的osgb格式的数据&#xff0c;如何只让他生成一个s3mb文件呢&#xff1f;我用倾斜入库的方式会生成好多个s3mb缓存文件 11.1.1 【解决办法】不能控制入库后只生成一个s3mb文件&#xff1b;可以在倾斜入库的时候设…

2024第17届中国西部(重庆)留学移民海外置业展览会

2024第17届中国西部&#xff08;重庆&#xff09;留学移民海外置业展览会 邀请函 主办单位&#xff1a; 中国西部教体医融合博览会组委会 承办单位&#xff1a;重庆中博展览有限公司 展会背景&#xff1a; 成都和重庆是中国新一线城市&#xff0c;是西部经济的核心增长极&a…