【数据结构】红黑树相关知识详细梳理

1. 红黑树的概念

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

        例如:
 

2. 红黑树的性质 

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

        如何理解以上性质呢:

                首先,根据红黑树的主要特点:最长路径中节点个数不会超过最短路径节点
个数的两倍
。在以上性质的约束下,我们可以假设出最长路径和最短路径的差值来看看他是否符合要求。例如:

        可以看到,通过几条性质的约束,的确可以使最长路径中节点个数不会超过最短路径节点个数的两倍,从而达到整棵树的相对平衡。

3. 红黑树插入

        和AVL树一样,在插入节点破坏树的平衡的情况下,红黑树也要进行旋转操作来维持树的平衡。那么什么情况下红黑树会进行旋转,如何旋转呢?

3.1 插入变色/旋转

        在进行旋转操作之前,我们首先要决定插入节点的颜色:

        黑色可以吗?根据性质4(对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点),如果我们将插入节点设为黑色,则一定会破坏规则,由于树形结构的路径具有唯一性,插入节点一定会违反性质4。

        所以我们默认设定除根节点外的(性质2)插入节点为红色。

        那么就有了以下几种情况:

        情况1:

        情况2: 

 

        情况3:

3.2删除

        和AVL树一样,红黑树的删除比较复杂,掌握插入的原理就够用了,这里不再详细说明。

4. 红黑树模拟实现

4.1 红黑树结构:

            首先为了方便封装map和set,我定义了一个头节点方便后续操作。

(头节点的父节点指向红黑树的根,左节点指向红黑树的最小值,右节点指向红黑树的最大值)

        

enum Color
{RED,BLACK
};
template<class T>
struct RBTreeNode
{RBTreeNode(const T& data = T())//构造:_data(data),_color(RED)//新节点默认插入红色,_pParent(nullptr),_pLeft(nullptr),_pRight(nullptr){}T _data;//数据Color _color;//颜色RBTreeNode* _pParent;//父节点RBTreeNode* _pLeft;//左节点RBTreeNode* _pRight;//右节点
};template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public://默认构造RBTree(){//为了后序封装map和set,本文在实现时给红黑树多增加了一个头节点_pHead = new Node;//可以特别把头节点设为黑色_pHead->_color = BLACK;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;_pHead->_pParent = _pHead;}//析构~RBTree(){Destroy(GetRoot());delete _pHead;}//插入bool Insert(const T& data);//查找Node* Find(const T& data);//获取红黑树最左侧节点Node* LeftMost();//获取红黑树最右侧节点Node* RightMost();//检测是否为有效红黑树bool IsValidRBTree();
private://检测是否为有效红黑树bool _IsValidRBTree(Node* pRoot,size_t k,size_t blackcount);//左单旋void RotateL(Node* pParent);//右单旋void RotateR(Node* pParent);//获取根节点Node* GetRoot();//析构void Destroy(Node* pRoot);
private:Node* _pHead;
};

 4.2 构造/析构

//默认构造
RBTree()
{//为了后序封装map和set,本文在实现时给红黑树多增加了一个头节点_pHead = new Node;//可以特别把头节点设为黑色_pHead->_color = BLACK;_pHead->_pLeft = _pHead;_pHead->_pRight = _pHead;_pHead->_pParent = _pHead;
}//析构
~RBTree()
{Destroy(GetRoot());delete _pHead;
}//析构
template<class T>
void RBTree<T>::Destroy(Node* pRoot)
{if (pRoot == nullptr)return;Destroy(pRoot->_pLeft);Destroy(pRoot->_pRight);delete pRoot;
}

4.3 获取元素: 

//查找
template<class T>
typename RBTree<T>::Node* RBTree<T>::Find(const T& data)
{if (_pHead->_pParent == _pHead)return nullptr;Node* pcur = GetRoot();while (pcur){if (data == pcur->_data)return pcur;if (data > pcur->_data)pcur = pcur->_pRight;elsepcur = pcur->_pLeft;}return pcur;
}//获取红黑树最左侧节点
template<class T>
typename RBTree<T>::Node* RBTree<T>::LeftMost()
{if (GetRoot() == nullptr)return nullptr;return _pHead->_pLeft;
}//获取红黑树最右侧节点
template<class T>
typename RBTree<T>::Node* RBTree<T>::RightMost()
{if (GetRoot() == nullptr)return nullptr;return _pHead->_pRight;
}//获取红黑树根节点
template<class T>
typename RBTree<T>::Node* RBTree<T>::GetRoot()
{if (_pHead->_pParent == _pHead)return nullptr;return _pHead->_pParent;
}

4.4 旋转: 

//左单旋
template<class T>
void RBTree<T>::RotateL(Node* pParent)
{Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;Node* grand = pParent->_pParent;subR->_pParent = grand;//如果祖父节点不是头节点if (grand != _pHead){if (pParent == grand->_pRight)grand->_pRight = subR;elsegrand->_pLeft = subR;}//如果祖父节点是头节点else{grand->_pParent = subR;}subR->_pLeft = pParent;pParent->_pParent = subR;pParent->_pRight = subRL;//如果subRL不为空if (subRL)subRL->_pParent = pParent;//变色pParent->_color = RED;subR->_color = BLACK;
}//右单旋
template<class T>
void RBTree<T>::RotateR(Node* pParent)
{Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;Node* grand = pParent->_pParent;//如果祖父节点不是头节点subL->_pParent = grand;if (grand != _pHead){if (pParent == grand->_pRight)grand->_pRight = subL;elsegrand->_pLeft = subL;}//如果祖父节点是头节点else{grand->_pParent = subL;}subL->_pRight = pParent;pParent->_pParent = subL;pParent->_pLeft = subLR;//如果subLR不为空if (subLR)subLR->_pParent = pParent;//变色pParent->_color = RED;subL->_color = BLACK;
}

4.5 插入: 

template<class T>
bool RBTree<T>::Insert(const T& data)
{Node* pcur = GetRoot();//如果树为空,只有头节点if (pcur == nullptr){//直接插入节点Node* newnode = new Node(data);_pHead->_pParent = newnode;_pHead->_pLeft = newnode;_pHead->_pRight = newnode;newnode->_pParent = _pHead;newnode->_color = BLACK;//插入成功,返回truereturn true;}//树不为空,按照搜索树规律找到插入位置Node* parent = nullptr;while (pcur){//如果树中存在该元素,插入失败if (data == pcur->_data)return false;parent = pcur;if (data > pcur->_data)pcur = pcur->_pRight;elsepcur = pcur->_pLeft;}//在正确的位置插入节点Node* newnode = new Node(data);if (data > parent->_data)parent->_pRight = newnode;elseparent->_pLeft = newnode;newnode->_pParent = parent;//插入后检查红黑树结构是否需要调整pcur = newnode;Node* grand = nullptr;Node* uncle = nullptr;//以pcur为基准,循环向上调整while (pcur!=GetRoot()){//更新父,叔节点parent = pcur->_pParent;grand = parent->_pParent;uncle = nullptr;//如果父节点颜色为黑,则不需要调整,跳出循环if (parent->_color == BLACK){break;}//父节点颜色为红,违反规则,需要调整else{//此时祖父节点一定存在if (grand->_pRight == parent)uncle = grand->_pLeft;elseuncle = grand->_pRight;//如果叔节点存在if (uncle){//如果叔节点为红色if (uncle->_color == RED){//父,叔节点都变黑,祖父节点变红parent->_color = BLACK;uncle->_color = BLACK;grand->_color = RED;//如果祖父节点为根节点,把祖父节点变黑if (grand == GetRoot())grand->_color = BLACK;//更新pcurpcur = grand;}//如果叔节点为黑色else{//如果叔节点为祖父节点的右节点if (uncle == grand->_pRight){//如果pcur为parent的右节点if (pcur == parent->_pRight){//对parent左单旋RotateL(parent);pcur->_color = RED;}//此时可看做pcur为parent的左节点的情况RotateR(grand);}//如果叔节点为祖父节点的左节点else{//如果pcur为parent的左节点if (pcur == parent->_pLeft){//对parent右单旋RotateR(parent);pcur->_color = RED;}//此时看看作pcur为parent的右节点的情况RotateL(grand);}}}//如果叔节点不存在,则pcur一定为新增节点else{//如果parent为grand的右节点if (parent == grand->_pRight){//如果pcur为parent的左节点if (pcur == parent->_pLeft){RotateR(parent);pcur->_color = RED;}	RotateL(grand);}//如果parent为grand的左节点else{//如果pcur为parent的右节点if (pcur == parent->_pRight){RotateL(parent);pcur->_color = RED;}RotateR(grand);}}}}//更新最大最小值Node* max = GetRoot();while (max->_pRight){max = max->_pRight;}Node* min = GetRoot();while (min->_pLeft){min = min->_pLeft;}//将最小给头节点的左_pHead->_pLeft = min;//将最大给头节点的右_pHead->_pRight = max;//统一返回真return true;
}

4.6 检验红黑树: 

//检测是否为有效红黑树
template<class T>
bool RBTree<T>::IsValidRBTree()
{Node* pRoot = GetRoot();if (pRoot == nullptr)//空树是有效红黑树return true;//检查根节点是否为黑色if (pRoot->_color != BLACK)return false;//记录任意一条路径的黑节点个数size_t blackcount = 0;Node* pcur = pRoot;while (pcur){if (pcur->_color == BLACK)++blackcount;pcur = pcur->_pRight;}size_t k = 0;//调用子函数return _IsValidRBTree(pRoot,k,blackcount);
}//检测是否为有效红黑树
template<class T>
bool RBTree<T>::_IsValidRBTree(Node* pRoot, size_t k, size_t blackcount)
{if (pRoot == nullptr){//一条路径走完,检查黑色节点个数是否和一开始给的相同if (k == blackcount)return true;elsereturn false;}//检查是否有连续的红色节点Node* pParent = pRoot->_pParent;//根节点为红色才判断pParent,不用考虑根节点if ( pRoot->_color == RED && pParent->_color == RED)//有连续的红色节点,违反规则return false;//如果pRoot为黑色节点,计数k+1if (pRoot->_color == BLACK)++k;//继续向子树递归return _IsValidRBTree(pRoot->_pLeft, k, blackcount) && _IsValidRBTree(pRoot->_pRight, k, blackcount);
}

5. 红黑树和AVL树的比较

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

6. 红黑树的应用

        1. C++ STL库 -- map/set、mutil_map/mutil_set。
        2. Java 库。
        3. linux内核。
        4. 其他一些库。

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

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

相关文章

pytorch使用LSTM模型进行股票预测

文章目录 tushare获取股票数据数据预处理构建模型训练模型测试模型tushare获取股票数据 提取上证指数代码为603912的股票:佳力图,时间跨度为2014-01-01到今天十年的数据。 import tushare as ts pro = ts.pro_api()#准备训练集数据df = ts.pro_bar(ts_code=603912.SH, star…

【git】配置 Git 的换行符处理和安全性||安装 Ruby

配置 Git 的换行符处理和安全性&#xff1a; git config --global core.autocrlf input&#xff1a;这个设置确保在提交代码时&#xff0c;Git 会将 CRLF&#xff08;Windows 的换行符&#xff09;转换为 LF&#xff08;Unix 的换行符&#xff09;&#xff0c;但在检出代码时不…

conda虚拟环境安装包、依赖同一管理

在 Python 的虚拟环境中&#xff0c;每个环境都是独立的&#xff0c;这意味着即使两个环境需要相同的库&#xff0c;它们也会分别安装各自的副本。这样做是为了避免不同项目之间相互影响&#xff0c;确保每个项目都有一个干净且隔离的环境。 方法一&#xff1a;使用 Conda 的共…

快手:数据库升级实践,实现PB级数据的高效管理|OceanBase案例

本文作者&#xff1a;胡玉龙&#xff0c;快手技术专家 快手在较初期采用了OceanBase 3.1版本成功替换了多个核心业务、数百套的MySQL集群。至2023年&#xff0c;快手的数据量已突破800TB大关&#xff0c;其中最大集群的数据量更是达到了数百TB级别。为此&#xff0c;快手将数据…

< IDE编程环境配置>

IDE编程环境配置 LIB&#xff0c;DLL区别 我们在写项目时会链接&#xff08;调用&#xff09;第3方库&#xff0c;或者比如在vs的解决方案solution创建项目project时&#xff0c;不仅可以开发可执行程序exe&#xff08;可单独运行&#xff09;&#xff08;windows/控制台 应用…

Spring Boot与iTextPdf:高效生成PDF文件预览

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 在现代应用程序开发中&#xff0c;生成PDF文件是一个常见的需求。PDF文件因其跨平台性和易读性&#xff0c;被广泛应用于文档交换、报告生成和打印预览等场景。Spring Boot作为一个用于简化Spring应用开发的框…

高级java每日一道面试题-2024年10月6日-数据库篇-MVCC是什么? 它的底层原理是什么?

如果有遗漏,评论区告诉我进行补充 面试官: MVCC是什么? 它的底层原理是什么? 我回答: 多版本并发控制&#xff08;Multi-Version Concurrency Control, MVCC&#xff09;是一种用于数据库管理系统中的并发控制方法。MVCC 通过为每个事务提供数据的不同版本&#xff0c;允许…

【python】追加写入excel

输出文件运行前&#xff08;有两张表&#xff0c;“表1”和“Sheet1”&#xff09;&#xff1a; 目录 一&#xff1a;写入单表&#xff08;删除所有旧工作表&#xff0c;写入新表&#xff09;二&#xff1a;写入多表&#xff08;删除所有旧工作表&#xff0c;写入新表&#x…

Java - LeetCode面试经典150题 - 区间 (三)

区间 228. 汇总区间 题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中…

【C语言进阶】系统测试与调试

1. 引言 在开始本教程的深度学习之前&#xff0c;我们需要了解整个教程的目标及其结构&#xff0c;以及为何进阶学习是提升C语言技能的关键。 目标和结构&#xff1a; 教程目标&#xff1a;本教程旨在通过系统化的学习&#xff0c;从单元测试、系统集成测试到调试技巧&#xf…

JavaScript中的数组改变原数组的方法

数组 var a [1, 2, 3, 5, 8, 13, 21] 改变原数组的方法 push(value) 数组末尾添加一个或多个元素&#xff0c;并返回新的数组长度 推入&#xff0c;a.push(34) 简单&#xff0c;不演示了 pop() 删除最后一个元素&#xff0c;并返回该元素的值 弹出&#xff0c;a.pop()…

MySQL 数据库的备份与恢复

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

探索Python的魔法:装饰器模式的奥秘

引言 装饰器模式是一种结构型设计模式&#xff0c;它通过创建一个包装对象来包含真实的对象&#xff0c;从而在不修改原有对象的基础上扩展其功能。在Python中&#xff0c;装饰器模式尤为流行&#xff0c;因为它提供了一种非常Pythonic的方式来增强函数或类的功能。 基础语法…

TS系列(7):知识点汇总

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 一、TS是什么&#xff1f; TypeScript 由微软开发&#xff0c;是基于 JavaScript 的一个扩展语言。TypeScript 包含 JavaScript 的所有内容&#xff0c;是 JavaScript 的超集。TypeScript 增加了静态类型检…

LLM+知识图谱新工具! iText2KG:使用大型语言模型构建增量知识图谱

iText2KG是一个基于大型语言模型的增量知识图谱构建工具&#xff0c;通过从文本文档中提取实体和关系来逐步构建知识图谱。该工具具有零样本学习能力&#xff0c;能够在无需特定训练的情况下&#xff0c;在多个领域中进行知识提取。它包括文档提炼、实体提取和关系提取模块&…

Unity3D 客户端多开

Unity3D 实现客户端多开 客户端多开 最近在做好友聊天系统&#xff0c;为了方便测试&#xff0c;需要再开一个客户端。 简单的方法&#xff0c;就是直接拷贝一个新的项目&#xff0c;但是需要很多时间和占用空间。 查阅了网络资料&#xff0c;发现有一种软链接&#xff0c;…

Python水循环标准化对比算法实现

&#x1f3af;要点 算法区分不同水循环数据类型&#xff1a;地下水、河水、降水、气温和其他&#xff0c;并使用相应标准化降水指数、标准化地下水指数、标准化河流水位指数和标准化降水蒸散指数。绘制和计算特定的时间序列比较统计学相关性。使用相关矩阵可视化集水区和显示空…

河南移动:核心营业系统稳定运行超300天,数据库分布式升级实践|OceanBase案例

河南移动&#xff0c;作为电信全业务运营企业&#xff0c;不仅拥有庞大的客户群体和业务规模&#xff0c;还引领着业务产品与服务体系的创新发展。河南移动的原有核心营业系统承载着超过6000万的庞大用户量&#xff0c;管理着超过80TB的海量数据&#xff0c;因此也面临着数据规…

MongoDB 的基本使用

目录 数据库的创建和删除 创建数据库 查看数据库 删除数据库 集合的创建和删除 显示创建 查看 删除集合 隐式创建 文档的插入和查询 单个文档的插入 insertOne insertMany 查询 嵌入式文档 查询数组 查询数组元素 为数组元素指定多个条件 通过对数组元素使…

pWnos1.0 靶机渗透 (Perl CGI 的反弹 shell 利用)

靶机介绍 来自 vulnhub 主机发现 ┌──(kali㉿kali)-[~/testPwnos1.0] …