超详细红黑树的模拟实现

在这里插入图片描述

前言

有人说设计出AVL树的的人是个大牛,那写红黑树(RBTree)的人就是天才!
上一篇文章,我们已经学习了AVL树,牛牛个人认为AVL树已经够优秀了,那让我们一起探究一下,为什么红黑树比AVL树的结构还要优秀吧!

目录

  • 前言
  • 一、红黑树的介绍
  • 二、手撕红黑树
    • 2.1 框架结构分析
      • 2.11 结点颜色
      • 2.12 结点类
      • 2.13 红黑树结构
    • 2.2 接口实现
      • 2.21 插入接口(重点)
        • 情况1: 父亲是爷爷的左,cur结点是父亲的左。 (左左)
        • 情况2: 父亲是爷爷的左,cur结点是父亲的右。 (左右)
        • 情况3: 父亲是爷爷的右,cur结点是父亲的左。 (右左)
        • 情况4: 父亲是爷爷的右,cur结点是父亲的左。 (右右)
      • 2.22 最左侧结点(LeftMost)
      • 2.23 最右侧结点(RightMost)
      • 2.24 检测函数(次重点)
      • 2.25 获取根节点
      • 2.25 获取红黑树的高度
      • 2.26 find函数
  • 三、结语:

一、红黑树的介绍

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

红黑树,是一种自平衡的二叉查找树,它的性质比较复杂,但却非常重要,常用于C++中的STL库中的setmap等容器。红黑树的节点有两种颜色:红色(red)和黑色(black)。它具有如下五个性质:

  1. 每个节点是红色或者黑色的。
  2. 根节点是黑色的。
  3. 每个叶子节点(这里特指最下面的空节点)是黑色的。
  4. 如果一个节点是红色的,则它的子节点必须是黑色的。(即:每条路径上不能出现连续的红结点)
  5. 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。

由于红色结点的父亲必须是黑色结点,并且每条路径上的黑色结点的个数也必须相同,所以得到了红黑树最长路径中节点个数不会超过最短路径节点个数的两倍

这也就决定了,红黑树的高度是log(n)级别的。
例如,下面这个就是红黑树

在这里插入图片描述

二、手撕红黑树

2.1 框架结构分析

2.11 结点颜色

红黑树较于AVL树,不在使用平衡因子,而是增设了颜色变量,这里我们可以枚举出这两种颜色,方便使用。

	enum Colour	//枚举出颜色{RED,		//红色BLACK		//黑色};

2.12 结点类

同AVL树一样,红黑树也是三叉链

	//结点类template<class K, class V>struct RBTreeNode{//指针域RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;	//数据域Colour _Col;RBTreeNode(const pair<K, V>& kv)//构造函数:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _Col(RED)				//注意这里,默认新构造的结点是红色的{}};

2.13 红黑树结构

	//红黑树的结构template<class K, class V>class RBTree{typedef RBTreeNode<K, V> Node;public:// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false//此版本红黑树对于重复元素,插入失败bool Insert(const pair<K, V>& kv);// 搜索红黑树中是否存在值为data的节点。//存在返回该节点的地址,不存在则返回nullptrNode* Find(const pair<K, V>& data);// 获取红黑树最左侧节点Node* LeftMost();// 获取红黑树最右侧节点Node* RightMost();//(这里的玩法大家应该不陌生了)	int Height();//计算红黑树的高度bool IsValidRBTRee();// 检测红黑树是否为有效的红黑树private:bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack);int Height(Node* root);// 左单旋void RotateL(Node* pParent);// 右单旋void RotateR(Node* pParent);// 为了操作树简单起见:获取根节点Node*& GetRoot();private:Node* _root = nullptr;};

2.2 接口实现

2.21 插入接口(重点)

本篇主要讲解的部分就是红黑树的插入操作。

函数名 :insert
返回值插入成功,返回true;插入失败,返回false
形参键值对
//插入函数
template<class K, class V>
bool RBTree<K, V>::Insert(const pair<K, V>& kv) {
}

(1).如果是第一次插入,则插入的是根节点,则需要特殊处理,因为要给根节点root赋值。

在结点类中我们提到,在创建的新节点我们给与了默认颜色RED(红色),而红黑树的根节点必须是BLACK(黑色)的,这里一定要记得修改一下颜色。

//第一次插入if (_root == nullptr) {_root = new Node(kv);_root->_Col = BLACK;		//注意根节点一定是黑色的,默认构造的新节点是红色的,所以这里要改一下。return true;}

(2) 寻找插入位置
红黑树也是二叉搜索树,学到这里,相信友友们在AVL树和二叉搜索树学习阶段,已经知道如何寻找插入位置。

	//寻找插入位置while (cur) {parent = cur;if (_root->_kv.first > kv.first) {cur = cur->_left;		//插入的值当前结点的值小,往左走}else if (_root->_kv.first < kv.first) {cur = cur->_right;		//插入的值当前结点的值大,往右走}else {return false;	//本篇实现的红黑树,对于重复值,插入失败}}//判断插入在左边还是右边cur = new Node(kv);if (kv.first < parent->_kv.first) {				//插入在左边parent->_left = cur;}else {									//插入在右边parent->_right = cur;}cur->_parent = parent;					//保证三叉链的关系

3.看uncle(叔叔)

叔叔(uncle)?这里我将当前结点的父亲(parent)的兄弟称为叔叔结点。

示例:
在这里插入图片描述
当我们新增一个结点时,默认新节点的颜色为RED,如果它的父亲结点是黑色的,则不需要做任何调整,直接插入成功!

在这里插入图片描述
当父亲结点是红色的时候,则与新增结点一起,会构成连续的红色结点,此时需要调整。

调整规则主要看uncle叔叔结点。

情况1: 父亲是爷爷的左,cur结点是父亲的左。 (左左)

👻情况:叔叔存在且为红
🔑调整方案: 变色+向上更新在这里插入图片描述(图片为博主原创,请勿随意转发使用)

👻情况:叔叔不存在,或者存在且为黑
🔑调整方案: 右旋+变色

在这里插入图片描述

在这里插入图片描述(图片为博主原创,请勿随意转发使用)

情况2: 父亲是爷爷的左,cur结点是父亲的右。 (左右)

👻情况:叔叔存在且为红
🔑调整方案: 变色+向上更新
在这里插入图片描述(图片为博主原创,请勿随意转发使用)

👻情况:叔叔不存在,或者存在且为黑
🔑调整方案: 左右双旋+变色

示例图:
在这里插入图片描述

未写(图片为博主原创,请勿随意转发使用)

情况3: 父亲是爷爷的右,cur结点是父亲的左。 (右左)

👻情况:叔叔存在且为红
🔑调整方案: 变色+向上更新
这里不画图了,牛牛画累了。

👻情况:叔叔不存在,或者存在且为黑
🔑调整方案: 右左双旋+变色
在这里插入图片描述

在这里插入图片描述(图片为博主原创,请勿随意转发使用)

情况4: 父亲是爷爷的右,cur结点是父亲的左。 (右右)

👻情况:叔叔存在且为红
🔑调整方案: 变色+向上更新
在这里插入图片描述(图片为博主原创,请勿随意转发使用)

👻情况:叔叔不存在,或者存在且为黑
🔑调整方案: 左旋+变色
在这里插入图片描述(图片为博主原创,请勿随意转发使用)

总结:
红黑树的插入主要看uncle
分为两种情况:
(1)uncle存在且为红
调整方案: 变色+继续向上调整
(2)uncle不存在或者uncle存在且为黑
调整方案: 旋转+变色

至于如何旋转,因为红黑树没有采用平衡因子的方式,所以我们采用判断grandfather与parent 和 parentcur的关系结构来决定。
下图是具体调整总结:
在这里插入图片描述

总代码:

bool Insert(const T& kv) {if (_root == nullptr) {_root = new Node(kv);_root->_Col = BLACK;		//注意根节点一定是黑色的,默认构造的新节点是红色的,所以这里要改一下。return true;}Node* cur = _root, * parent = nullptr;//寻找插入位置while (cur) {parent = cur;if (_root->_kv.first > kv.first) {cur = cur->_left;}else if (_root->_kv.first < kv.first) {cur = cur->_right;}else {return false;}}//判断插入在左边还是右边cur = new Node(kv);if (kv.first < parent->_kv.first) {				//插入在左边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=parent->_Col = BLACK;grandfather->_Col = RED;//继续向上更新cur = grandfather;parent = grandfather->_parent;}else {									//叔叔不存在或者 存在且为黑if (cur == parent->_left) {			//			g//		p//cRotateR(grandfather);grandfather->_Col = RED;parent->_Col = BLACK;}else {//		g//	p//		cRotateL(parent);RotateR(grandfather);cur->_Col = BLACK;grandfather->_Col = RED;}break;	//此时最顶端的结点已经变成黑色了,不需要继续向上更新了。}}else {		// 如果父亲是爷爷的右孩子Node* uncle = grandfather->_left;		//那么叔叔就是爷爷的左孩子//叔叔存在且为红if (uncle && uncle->_Col == RED) {//变色uncle->_Col = parent->_Col = BLACK;grandfather->_Col = RED;//继续向上更新cur = grandfather;parent = grandfather->_parent;}else {									//叔叔不存在或者 存在且为黑if (cur == parent->_left) {//	g//		p//	c//注意旋转时的传参RotateR(parent);RotateL(grandfather);grandfather->_Col = RED;cur->_Col = BLACK;}else {//	g//		p//			cRotateL(grandfather);		//注意旋转时的传参parent->_Col = BLACK;grandfather->_Col = RED;}break;	//此时最顶端的结点已经变成黑色了,不需要继续向上更新了。}}}_root->_Col = BLACK;		//最后根节点一定是黑的return true;}

2.22 最左侧结点(LeftMost)

对于二叉搜索树,如果我们按中序遍历,则可以得到一个有序序列。
中序遍历的首个结点: 最左侧结点
中序遍历的最后结点: 最右侧结点

在这里插入图片描述

	// 获取红黑树最左侧节点template<class K, class V>typename RBTree<K, V>::Node* RBTree<K, V>::LeftMost() {Node* left_most = _root;while (left_most->left) {left_most = left_most->left;}return left_most;}

2.23 最右侧结点(RightMost)

	// 获取红黑树最右侧节点template<class K, class V>typename RBTree<K, V>::Node* RBTree<K, V>::RightMost() {Node* right_most = _root;while (right_most->right) {right_most = right_most->left;}return right_most;}

2.24 检测函数(次重点)

在实现红黑树时,也许我们会遇到各种问题,好不容易跑通代码后,我们缺无法判断自己实现的红黑树是否正确,是否符合红黑树的规则。

此时,我们可以设计一个检测函数,检测实现的红黑树是否平衡。

  1. 空树也是红黑树
  2. 根节点必须是红黑树
  3. 我们可以设置一个“基准值”,基准值为红黑树一条路径中的黑色结点的个数。
  4. 遍历每条红黑树的路径,判断红黑树结点的个数,是否与基准值相等。
  5. 除此之外,出现连续两个红色结点则返回false
// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
template<class K, class V>
bool  RBTree<K, V>::IsValidRBTRee() {if (_root == nullptr)	//空树也是红黑树return true;if (_root->_Col != BLACK)	//根节点必须是红黑树{return false;}// 基准值int pathBlack = 0;Node* cur = _root;while (cur){if (cur->_Col == BLACK)++pathBlack;cur = cur->_left;}_IsValidRBTRee(_root, 0, pathBlack);
}template<class K, class V>
bool RBTree<K, V>::_IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack) {if (pRoot == nullptr){if (blackCount != pathBlack)		//一条路径走到底,也就是走到叶子结点以后,判断这条路径上的黑色结点个数(blackCount)是否与 设定的黑色结点个数相同(pathBlack)return false;return true;}if (pRoot->_Col == BLACK){++blackCount;}if (pRoot->_Col == RED && pRoot->_parent && pRoot->_parent->_Col == RED){cout << _root->_kv.first << "出现连续红色节点" << endl;return false;}//递归访问左右子树return _IsValidRBTRee(pRoot->_left, blackCount, pathBlack)&& _IsValidRBTRee(pRoot->_right, blackCount, pathBlack);
}

2.25 获取根节点

	template<class K, class V>typename RBTree<K, V>::Node*& RBTree<K, V>::GetRoot() {return _root;}

2.25 获取红黑树的高度

	template<class K, class V>int RBTree<K, V>::Height(){return Height(_root);}template<class K, class V>int RBTree<K, V>::Height(typename RBTree<K, V>::Node* root){if (root == nullptr)return 0;int leftHeight = Height(root->_left);	//计算左子树的高度int rightHeight = Height(root->_right);	//计算右子树的高度return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}

2.26 find函数

template<class K, class T,>
typename RBTree<class K, class T, class Of_T>::Node* RBTree<class K, class T, class Of_T>::Find(const T& data) {Node* cur = _root;while (cur){if (data > cur->_data){cur = cur->_right;}else if (data < cur->_data){cur = cur->_left;}else return cur;}return nullptr;  // 找不到目标元素时返回nullptr
}

三、结语:

看完本篇文章,我们不难知道,对于插入操作,无论是红黑树还是avl树,要维持对应的“平衡”,会进行沿路径的更新,其中涉及大量的旋转操作,而红黑树较于avl树那种严格的高度差在-11之间,红黑树的平衡条件相对宽松,这也就大大减少了的为了维持平衡的大量旋转操作,而且还能保证效率在log(N),这也就是为啥说红黑树较于avl树更加优秀。
你赞同这个观点吗?
在这里插入图片描述

后续牛牛会模拟实现mapset,会在那篇文章封装红黑树,对红黑树进行改造,增加迭代器等功能。帮助友友们更加深入理解mapset容器。

在这里插入图片描述

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

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

相关文章

链表类型题目

文章目录 简介链表的常用技巧两数相加原理代码代码|| 两两交换链表中的节点代码原理 重排链表(重要)原理代码 合并 K 个升序链表代码递归代码 K 个一组翻转链表原理代码 简介 大家好,这里是jiantaoyab,这篇文章给大家带来的是链表相关的题目练习和解析,希望大家能相互讨论进步 …

[线代]自用大纲

部分内容整理自张宇和网络 序 题型分布&#xff1a; 题型单题分值题目数量总分值选择题5315填空题515解答题12112 *一道大题可能用到六部分所有知识 矩阵 性质 k k k倍和乘积行列式 ∣ k A ∣ k n ∣ A ∣ |kA|k^n|A| ∣kA∣kn∣A∣ ∣ A B ∣ ≠ ∣ A ∣ ∣ B ∣ |AB|≠…

如何解决微服务的数据一致性分发问题?

介绍 系统架构微服务化以后,根据微服务独立数据源的思想,每个微服务一般具有各自独立的数据源,但是不同微服务之间难免需要通过数据分发来共享一些数据,这个就是微服务的数据分发问题。Netflix/Airbnb等一线互联网公司的实践[参考附录1/2/3]表明,数据一致性分发能力,是构…

【 10X summary report】怎么看?详细解读笔记

报告内容 在开始正式的分析之前&#xff0c;需要查看在对齐和计数过程中生成的任何总结统计信息。下图是由Cell Ranger工具创建的10X总结报告&#xff0c;在从10X scRNA-seq实验生成计数矩阵时会生成。 The left half of the report describes sequencing and mapping statist…

C++之stack

1、stack简介 stack是实现的一个先进后出&#xff0c;后进先出的容器。它只有一个出口&#xff0c;只能操作最顶端元素。 2、stack库函数 &#xff08;1&#xff09;push() //向栈压入一个元素 &#xff08;2&#xff09;pop() //移除栈顶元素 &#xff08;3…

基于springboot+vue的中国陕西民俗网

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

nginx,php-fpm

一&#xff0c;Nginx是异步非阻塞多进程&#xff0c;io多路复用 1、master进程&#xff1a;管理进程 master进程主要用来管理worker进程&#xff0c;具体包括如下4个主要功能&#xff1a; &#xff08;1&#xff09;接收来自外界的信号。 &#xff08;2&#xff09;向各worker进…

SAP PP学习笔记04 - BOM2 -通过Serial来做简单的BOM变式配置,副明细,BOM状态,BOM明细状态,项目种类,递归BOM

本章继续讲BOM。 本章讲通过Serial来做简单的BOM变式配置。还讲了BOM的相关概念&#xff1a;副明细&#xff0c;BOM状态&#xff0c;BOM明细状态&#xff0c;项目种类&#xff0c;递归BOM 等。 1&#xff0c;通过Serial&#xff08;序列号&#xff09;来做简单的 VC&#xff0…

Some collections -- 2024.3

一、TensorFlow Android (dataset: Mnist) We used TensorFlow to define and train our machine learning model, which can recognize handwritten numbers, called a number classifier model in machine learning terminology. We transform the trained TensorFlow mod…

2024.03.01作业

1. 基于UDP的TFTP文件传输 #include "test.h"#define SER_IP "192.168.1.104" #define SER_PORT 69 #define IP "192.168.191.128" #define PORT 9999enum mode {TFTP_READ 1,TFTP_WRITE 2,TFTP_DATA 3,TFTP_ACK 4,TFTP_ERR 5 };void get_…

高维中介数据:基于交替方向乘子法(ADMM)的高维度单模态中介模型的参数估计(入门+实操)

全文摘要 用于高维度单模态中介模型的参数估计&#xff0c;采用交替方向乘子法&#xff08;ADMM&#xff09;进行计算。该包提供了确切独立筛选&#xff08;SIS&#xff09;功能来提高中介效应的敏感性和特异性&#xff0c;并支持Lasso、弹性网络、路径Lasso和网络约束惩罚等不…

npm 镜像源切换与设置

项目背景 依赖安装中断或响应特别慢。 可以看到当前所用的镜像是 https://registry.npmjs.org 。 切换淘宝镜像之后总算能够安装下来 命令行模式 查看当前镜像源 # 查看当前镜像源 npm config get registry 可以看到默认情况下是官方默认全局镜像 https://registry.npmjs.o…

竞争加剧下,登顶后的瑞幸该做什么?

瑞幸咖啡仅用短短18个月时间从品牌创立到纳斯达克上市&#xff0c;刷新全球最快上市记录。2020年因交易造假事件被勒令退市股价暴跌80%&#xff0c;有人说这个创造了赴美IPO奇迹的“巨婴”将是下一个倒下的ofo。2023年瑞幸咖啡以逆势超速增长领跑咖啡赛道有力回应了市场的质疑&…

Java多线程实现发布和订阅

目录 简介 步骤 1: 定义消息类 步骤 2: 创建发布者 步骤 3: 创建订阅者 步骤 4: 实现发布-订阅模型 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡…

棋牌室计时计费管理系统的灯控器连接教程

棋牌室计时计费管理系统的灯控器连接教程 一、前言 以下教程以 佳易王棋牌室计时计费管理系统软件V18.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 如上图&#xff0c;计时计费软件在开始计时的时候&#xff0c;点击 开始计时 如果连接了…

YOLOv9独家改进|动态蛇形卷积Dynamic Snake Convolution与空间和通道重建卷积SCConv与RepNCSPELAN4融合

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 Dynamic Snake Convolution是一种针对细长微弱的局部结构特征与复杂多变的全局形态特征设计的卷积模块。 SCConv是一种即插即用的空间…

华为OD机试真题C卷-篇6

100分值题 宽度最小的子矩阵部门人力分配电脑病毒感染会议室占用时间段 宽度最小的子矩阵 给定一个n行 * m列的矩阵&#xff1b;给定一个k个整数的数组k_list&#xff1b;在n*m的矩阵中找一个宽度最小的子矩阵&#xff0c;该子矩阵包含k_list中所有的整数&#xff1b; 输入描述…

项目管理:高效推动项目成功的关键

项目管理&#xff1a;高效推动项目成功的关键 在当今竞争激烈的商业环境中&#xff0c;项目管理已成为企业实现目标和取得成功的关键因素。有效的项目管理不仅能够确保项目按时完成&#xff0c;还能在预算范围内达到预期的质量标准。本文将探讨项目管理的重要性、关键环节以及…

Maven安装并配置本地仓库

一、安装Maven 1.下载链接 Maven官网下载链接 Binary是可执行版本&#xff0c;已经编译好可以直接使用。 Source是源代码版本&#xff0c;需要自己编译成可执行软件才可使用。 tar.gz和zip两种压缩格式,其实这两个压缩文件里面包含的内容是同样的,只是压缩格式不同 tar.gz格…

Stable Video文本生成视频公测地址——Scaling Latent Video Diffusion Models to Large Datasets

近期&#xff0c;Stability AI发布了首个开放视频模型——"Stable Video"&#xff0c;该创新工具能够将文本和图像输入转化为生动的场景&#xff0c;将概念转换成动态影像&#xff0c;生成出电影级别的作品&#xff0c;旨在满足广泛的视频应用需求&#xff0c;包括媒…