C++之红黑树

红黑树

  • 红黑树的概念
  • 红黑树的性质
  • 红黑树结点的定义
  • 红黑树的插入
  • 红黑树的验证
  • 红黑树与AVL树的比较

红黑树的概念

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

红黑树的性质

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

为什么红黑树就能保证其最长路径中节点个数不会超过最短路径节点个数的两倍?

从性质3,4可以得出,一棵红黑树的最短可能路径就是全为黑结点,即为N:
在这里插入图片描述
而最长可能路径就是由一黑一红结点构成的路径,该路径当中黑色结点与红色结点的数目相同,即长度为2N ;
在这里插入图片描述

红黑树结点的定义

红黑树结点的定义其实和AVL树差不多,只不过红黑树结点少了平衡因子,多了颜色。

//枚举结点颜色
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;//结点颜色color _col;//构造函数RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){}
};

为什么构造结点时,默认将结点的颜色设置为红色?

我们可以发现,红黑树的有一条性质是所有路径黑色结点的数目都相等,如果欧姆尼新插入的结点默认为黑色,就会破坏这条性质,我们就需要对红黑树进行调整。

如果我们插入的是红结点,此时如果父结点是红结点,我们就需要进行调整,如果父结点是黑结点,我们就不需要进行调整。

红黑树的插入

红黑树的插入步骤分为以下三步:

  1. 根据二叉搜索树的性质,找到待插入位置;
  2. 在待插入位置插入新结点;
  3. 如果父结点为红色,就进行调整。

我们可以发现,前两步其实AVL树没有区别,我们只要在第三步进行调整就可以了。

那么,怎么对红黑树插入结点进行调整呢?

如果我们插入结点的父结点是黑色的,我们就不需要进行任何调整,因为这并没有破坏红黑树的性质;如果我们插入结点的父结点是红结点,我们就需要对红黑树进行调整了。

插入结点的父结点为红色,就说明祖父结点一定存在且为黑色,对红黑树的调整主要是对叔叔结点的调整。

红黑树的调整一共分为三种方式:

1.cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述
插入结点为红,父结点为红,我们需要将父结点与叔叔结点都调整为黑色,然后将祖父结点调整为红色,如果此时g这棵树不为子树,就再将祖父结点调整为黑色;

对应的抽象图如下:
在这里插入图片描述
如果g不为子树,最后将g调整为黑色就完成了红黑树的调整;
在这里插入图片描述
如果g为子树,一次调整以后g还有双亲,双亲如果是红色,将p,u改为黑,g改为红,然后把g当成cur,继续向上调整,还要继续向上调整。

2.cur为红,p为红,g为黑,u不存在/u存在且为黑,p为g的左孩子,cur为p的左孩子或者p为g的右孩子,cur为g的右孩子

我们均已被插入结点在根结点左侧为例:

如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点则cur和p一定有一个节点的颜色是黑色,就不满足性质:每条路径黑色节点个数相同。
在这里插入图片描述
此时我们只需要以p为旋转点进行右单旋,然后进行颜色调整即可:
在这里插入图片描述
当u存在且为黑时:

此时一定是情况一向上调整才会出现的情况,这种情况下cur不会是新插入的结点,是经过前一次调整以后的结点:
在这里插入图片描述
我们以新插入结点在a的左侧为例:
在这里插入图片描述

此时我们以g为旋转点进行右单旋,然后进行颜色调整,红黑树即可平衡;

同样,如果被插入结点在根结点右侧,我们只需要进行相应左旋在调整颜色即可实现红黑树平衡;

抽象图如下:

被插入结点在根结点左侧:
在这里插入图片描述
被插入结点在根结点右侧:
在这里插入图片描述

3.cur为红,p为红,g为黑,u不存在/u存在且为黑,p为g的左孩子,cur为p的右孩子或者p为g的右孩子,cur为p的左孩子

u不存在时:
在这里插入图片描述

当u存在且为黑时,我们以下面这种情况为例:
在这里插入图片描述
我们先已p为轴点进行左单旋,再以g为轴点进行右单旋,最后进行颜色调整,红黑树保持平衡;

抽象图如下:

被插入结点在根结点左侧:
在这里插入图片描述
被插入结点在根结点右侧:
在这里插入图片描述
代码实现:

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){//如果插入key值小于根结点key值if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}//如果插入key值大于根结点key值else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}//相等,返回falseelse{return false;}}//创建cur结点cur = new Node(kv);//颜色初始化为红色cur->_col = RED;//如果插入key值小于parent结点key值if (parent->_kv.first > kv.first){//在左边插入parent->_left = cur;}//如果插入key值大于parent结点key值else{//在右边插入parent->_right = cur;}//跟父结点连接起来cur->_parent = parent;while (parent && parent->_col == RED){//定义祖父节点Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK);//如果父结点在祖父节点左边if (parent == grandfather->_left){//定义叔叔结点在祖父结点右边Node* uncle = grandfather->_right;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//进行颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点左边if (cur == parent->_left){//右单旋+颜色调整RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//左右双旋+颜色调整RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//如果父结点在祖父节点右边else{//定义叔叔结点在祖父结点左边Node* 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){//左单旋+颜色调整RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//右左双旋+颜色调整RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//根结点变为黑色_root->_col = BLACK;return true;
}

红黑树的验证

我们可以判断每条路径的黑节点数量是否相等来判断该二叉树是否为红黑树:

void InOrder()
{_InOrder(_root);cout << endl;
}bool IsBalance()
{//如果根结点为空,返回true;if (_root == nullptr){return true;}//如果根结点为红色,则返回false;if (_root->_col == RED){cout << "根结点不是黑色" << endl;return false;}//定义一个黑色结点数量的基准值,为最左边路径黑色结点数量int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){benchmark++;}cur = cur->_left;}//黑色结点数量int BlackNum = 0;return PrevCheck(_root, BlackNum, benchmark);
}
bool PrevCheck(Node* root, int BlackNum, int benchmark)
{//说明此时路径已经走完了if (root == nullptr){//如果基准值不等于黑色结点数量,返回falseif (benchmark != BlackNum){cout << " 某条黑色结点数量不相等" << endl;return false;}//否则返回truereturn true;}//根结点为黑色,黑色结点数量++if (root->_col == BLACK){BlackNum++;}//存在连续红色节点,返回falseif (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色结点" << endl;return false;}//递归进行判断return PrevCheck(root->_left, BlackNum, benchmark)&& PrevCheck(root->_right, BlackNum, benchmark);
}

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单。

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

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

相关文章

[SUCTF2019]SignIn 题解

是一个64位的文件 使用了RSA加密算法 N是103461035900816914121390101299049044413950405173712170434161686539878160984549 使用在线网站分离得到p&#xff0c;q 然后编写脚本进行解密 import gmpy2 import binasciip 282164587459512124844245113950593348271 q 366669…

AI伦理:科技发展中的人性之声

文章目录 AI伦理的关键问题1. 隐私问题2. 公平性问题3. 自主性问题4. 伦理教育问题 隐私问题的拓展分析数据收集和滥用隐私泄露和数据安全 公平性问题的拓展分析历史偏见和算法模型可解释性 自主性问题的拓展分析自主AI决策伦理框架 伦理教育的拓展分析伦理培训 结论 &#x1f…

windows11安装docker时,修改默认安装到C盘

1、修改默认安装到C盘 2、如果之前安装过docker&#xff0c;请删除如下目录&#xff1a;C:\Program Files\Docker 3、在D盘新建目录&#xff1a;D:\Program Files\Docker 4、winr&#xff0c;以管理员权限运行cmd 5、在cmd中执行如下命令&#xff0c;建立软联接&#xff1a; m…

JVM 内存结构

一、程序计数器 1.1 定义 当前线程所执行的字节码的行号指示器&#xff0c;用于记住下一条 jvm 的执行地址。 1.2 特点 1、线程私有 2、不存在内存溢出 二、虚拟机栈 2.1 定义 每个线程运行时所需要的内存&#xff0c;称为虚拟机栈。 2.2 特点 1、每个栈由多个栈帧&#x…

OceanBase 里的 schema 是什么?

李博洋 OceanBase 技术部研发工程师。 OceanBase 开源社区里经常会看到一些类似于 “ schema 是什么” 的疑问&#xff1a; 很多同学经常会误以为在 OceanBase 里&#xff0c;schema 只是 database 的同义词&#xff0c;这次分享就从 schema 是什么这个问题稍微展开聊一下。 首…

【4-5章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第4章 RDD编程&#xff08;21节&#xff09; Spark生态系统&#xff1a; Spark Core&#xff1a;底层核心&#xff08;RDD编程是针对这个&#xff09;Spark SQL&#xff1a;…

Linux命令200例:mkfs用于创建文件系统

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0…

Kafka3.0.0版本——消费者(消费者组初始化流程图解)

一、消费者组初始化流程图解 每个consumer都发送JoinGroup请求&#xff0c;如下图所示&#xff1a; 选出一个consumer作为leader&#xff0c;如下图所示&#xff1a; 把要消费的topic情况发送给leader 消费者&#xff0c;如下图所示&#xff1a; leader会负责制定消费方案…

SpringBoot / Vue 对SSE的基本使用(简单上手)

一、SSE是什么&#xff1f; SSE技术是基于单工通信模式&#xff0c;只是单纯的客户端向服务端发送请求&#xff0c;服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放&#xff0c;等数据更新的时候才返回给客户端&#xff0c;当客户端接收到消息后&#xff0c;…

(18)线程的实例认识:线程的控制,暂停,继续,停止,线程相互控制,协作

话不多&#xff0c;但比较中肯&#xff0c;本文参照c# 线程暂停继续的实现方式_哔哩哔哩_bilibili 一、老方式 1、这是一个老的实现方式&#xff0c;基本不推荐&#xff0c;背后控制的原理需要了解。 界面&#xff1a;三个button一个textbox …

Python爬取电影信息:Ajax介绍、爬取案例实战 + MongoDB存储

Ajax介绍 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于在Web应用程序中实现异步通信的技术。它允许在不刷新整个网页的情况下&#xff0c;通过在后台与服务器进行数据交换&#xff0c;实时更新网页的一部分。Ajax的主要特点包括&#xff1a; 异步通…

软件测试行业35岁职场魔咒,你准备怎么应对?

以前就流传一种说法“IT行业职场35岁危机”&#xff0c;那时我半信半疑。 或许那时觉得还离我比较遥远&#xff0c;也或许那时每天都重复着996&#xff0c;工作上的任务已经应接不暇&#xff0c;每天都处在忙碌中&#xff0c;也没精力花时间去过多思考这个问题。休息的时候能好…

CMS指纹识别

一.什么是指纹识别 常见cms系统 通过关键特征&#xff0c;识别出目标的CMS系统&#xff0c;服务器&#xff0c;开发语言&#xff0c;操作系统&#xff0c;CDN&#xff0c;WAF的类别版本等等 1.识别对象 1.CMS信息&#xff1a;比如Discuz,织梦&#xff0c;帝国CMS&#xff0…

【Linux】进程概念I --操作系统概念与冯诺依曼体系结构

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我吧&#xff01;你定不会失望。 本篇导航 1. 冯诺依曼体系结构为什么这样设计? 2. 操作系统概念为什么我们需要操作系统呢?操作系统怎么进行管理? 计算机是由两部分组…

数学分析:势场

首先从散度的物理解释开始。首先&#xff0c;在球内的向量场的散度的积分&#xff0c;等于它在球边界上的流量的积分。所以根据积分中值定理&#xff0c;我们可以这么理解散度&#xff0c;它就是这个体积内的速度场的平均密度。而速度场只和源有关&#xff0c;所以它表示的某个…

03-系统篇-内存碎片

一.常见的malloc内存分配原理 1内存分配原理 linux中应用层动态分配内存一般是用的malloc函数&#xff0c;而malloc在glibc中实现时&#xff0c;是用sbrk()来分内存. 在前面的章节中&#xff0c;我们了解到了堆的概念&#xff0c;堆在内存中&#xff0c;是一断连续的内存&…

语音识别数据的采集方法:基本流程数据类型

“人工智能是一种模仿人类功能的产品。数据采集的方法需要针对特定的场景需求。”—–Mark Brayan (澳鹏CEO) 我们一直说&#xff0c;对于一个高质量的人工智能产品离不开高质量的训练数据。对于不同的人工智能我们需要不同的数据对其训练。要采集正确的数据去训练特定的模型才…

【Apollo】自动驾驶技术的介绍

阿波罗是百度发布的名为“Apollo&#xff08;阿波罗&#xff09;”的向汽车行业及自动驾驶领域的合作伙伴提供的软件平台。 帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快速搭建一套属于自己的自动驾驶系统。 百度开放此项计划旨在建立一个以合作为中…

SQL语言的分类:DDL(数据库、表的增、删、改)、DML(数据的增、删、改)

数据库管理系统&#xff08;数据库软件&#xff09;功能非常多&#xff0c;不仅仅是存储数据&#xff0c;还要包含&#xff1a;数据的管理、表的管理、库的管理、账户管理、权限管理等。 操作数据库的SQL语言&#xff0c;基于功能&#xff0c;划分为4类&#xff1a; 1、数据定…

单片机第三季-第一课:STM32基础

官方网址&#xff1a;STMCU中文官网 STM32系列分类&#xff1a; 型号命名原则&#xff1a; STM32F103系列&#xff1a; 涉及到的几个概念&#xff1a; DMA&#xff1a;Direct Memory Access&#xff0c;直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间&…