数据结构:红黑树的原理和实现

文章目录

  • 红黑树的概念
  • 红黑树的性质
  • 红黑树的模拟实现
    • 红黑树的平衡问题
  • 整体实现和测试

本篇用于进行红黑树的拆解和模拟实现,为之后的map和set的封装奠定基础

红黑树的概念

红黑树也是一种二叉搜索树,但是在每一个节点的内部新增了一个用以表示该节点颜色的值,有黑色和红色两种,通过对任何一条从根到叶子的路径上的各个节点着色方式的限制,红黑树可以保证没有一条路径可以比其他路径长出两倍,因此是平衡的

红黑树的基本模式如下图所示
在这里插入图片描述

红黑树的性质

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

为什么红黑树具有最长路径中节点的个数不超过最短路径个数的2倍?

其实原因在于红黑树的性质,在红黑树中可以存在两个相同黑色节点连在一起,但是绝对不会存在两个连在一起的红色节点,并且每个路径上的黑色节点数量是相同的,基于这两点原因,在红黑树中最长的路径不过是一个红节点穿插一个黑节点…而最短的路径就是所有黑节点是一个接着一个,基于这样的原因就可以保证上面的性质了

红黑树的模拟实现

基本的定义

enum Color
{RED,BLEAK
};template<class K, class V>
struct BSTreeNode
{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;BSTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;BSTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

为什么这里在定义信息的时候,默认值使用的是RED?

由于红黑树的性质可以知道,一条路径中的黑节点的数量是确定的,当插入的是一个红色节点时,最多会影响的是当前路径的信息,但是如果插入的是一个黑色节点,那么势必会引起整个树中所有完整的路径中的异常,会破坏红黑树中的平衡

红黑树的平衡问题

在插入新节点后,红黑树的平衡可能会受到破坏,下面分情况来进行讨论

定义:当前节点为cur,父亲节点为parent,祖父节点为grandparent,叔叔节点为uncle,而红黑树的插入问题重点看叔叔

1. 如果双亲节点是黑色

在这里插入图片描述
最简单的一种情况,不需要做任何处理,只需要插入即可

2. cur为红色,parent为红色,grandfather为黑色,uncle存在并且是红色

在这里插入图片描述
此时,出现了两个红色节点相继出现的情况,这种情况是不被允许的,因此要做出调整:把parent和uncle都改成黑色,同时将grandfather改成红色

此时需要继续进行情况讨论,如果grandfather是根节点,那么就意味着此时调整已经完毕了,不需要再进行调整,因此把根节点置为黑色,而如果grandfather不为根节点,并且上面一个节点还是红色,那么此时又有两个红色节点相继出现了,此时就需要继续进行调整,把grandfather当成cur,然后进行调整即可

在这里插入图片描述
3. cur为红色,parent为红色,grandfather为黑色,uncle不存在或者是黑色

根据uncle的情况来进行分析:

  1. 如果uncle节点不存在,那么就说明cur一定是新插入的节点,这是因为路径下的黑色节点必定要相同,此时又有两种情况,可能插入在parent的左右两边

在这里插入图片描述

  1. 如果uncle节点存在,并且是黑色,那么就意味着cur节点一定是黑的,现在体现为红色是因为cur子树在调整的过程中把cur的节点变成红色了,如果cur是新插入节点,那么红黑树原来就是错的,因为下面的场景不存在
    在这里插入图片描述
    所以一定是这样的情景:

在这里插入图片描述

而此时cur并不是新插入的节点,新插入节点是cur的左右子树中的一个,现在体现为红色是因为下面子树的调整把cur变成红色了,它原来是黑色的

那么此时就要进行旋转了:令grandparent右旋即可完成降高度的效果,再进行变色即可

在这里插入图片描述
因此将上述的过程都综合起来,就可以完成代码的实现了

	bool insert(const pair<K, V>& kv){Node* cur = _root;Node* parent = nullptr;// 根据搜索二叉树的基本逻辑完成if (_root == nullptr){_root = new Node(kv);}else{// 插入数据while (cur){if (cur->_kv.second > kv.second){// 插入元素小于当前节点元素,插入到左边parent = cur;cur = cur->_left;}else if (cur->_kv.second < kv.second){// 插入元素大于当前节点元素,插入到右边parent = cur;cur = cur->_right;}else{return false;}}// 此时parent指向最后一个节点,cur为空cur = new Node(kv);if (parent->_kv.second > cur->_kv.second){// 如果插入节点小于它的父亲,就插入到左边parent->_left = cur;cur->_parent = parent;}else if (parent->_kv.second < cur->_kv.second){// 如果插入节点大于它的父亲,就插入到右边parent->_right = cur;cur->_parent = parent;}else{return false;}}// 至此,普通二叉搜索树的插入已经完成,该进行红黑树的高度调整了// 终止条件是parent为空,或者parent已经是黑色了,就意味着不需要调整了// parent是红色,意味着grandparent一定存在while (parent && parent->_col == RED){// 更变的核心是舅舅,因此要先找到舅舅// 整体来说还有两种情况,parent可能是grandparent的左或者右,舅舅就是另外一边Node* grandparent = parent->_parent;if (parent == grandparent->_left){Node* uncle = grandparent->_right;// 1. 舅舅存在,并且是红色if (uncle && uncle->_col == RED){//     g//   p   u// c// 变色parent->_col = uncle->_col = BLACK;grandparent->_col = RED;// 向上处理cur = grandparent;parent = cur->_parent;}// 2. 舅舅不存在else{// 如果cur是左孩子if (cur == parent->_left){//     g//   p// c// 对grandparent进行右旋RotateR(grandparent);// 变色cur->_col = grandparent->_col = RED;parent->_col = BLACK;}// 如果cur是右孩子else{//     g               g//  p       -->     c         -->    c//    c           p                p   g// 对parent左旋,对grandparent右旋RotateL(parent);RotateR(grandparent);// 变色cur->_col = BLACK;parent->_col = grandparent->_col = RED;}// 更新之后parent和grandparent顺序乱了,而且也不需要继续调整了,直接breakbreak;}}// parent是grandparent的右孩子,相同的逻辑再进行一次else{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandparent->_col = RED;// 继续往上处理cur = grandparent;parent = cur->_parent;}else{if (cur == parent->_right){//   g//      p//         cRotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{//     g//       p //     cRotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}// 不管上面怎么变都无所谓,只需要保证根节点是黑的就可以了_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}

整体实现和测试

enum Color
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:RBTree():_root(nullptr){}bool insert(const pair<K, V>& kv){Node* cur = _root;Node* parent = nullptr;// 根据搜索二叉树的基本逻辑完成if (_root == nullptr){_root = new Node(kv);}else{// 插入数据while (cur){if (cur->_kv.second > kv.second){// 插入元素小于当前节点元素,插入到左边parent = cur;cur = cur->_left;}else if (cur->_kv.second < kv.second){// 插入元素大于当前节点元素,插入到右边parent = cur;cur = cur->_right;}else{return false;}}// 此时parent指向最后一个节点,cur为空cur = new Node(kv);if (parent->_kv.second > cur->_kv.second){// 如果插入节点小于它的父亲,就插入到左边parent->_left = cur;cur->_parent = parent;}else if (parent->_kv.second < cur->_kv.second){// 如果插入节点大于它的父亲,就插入到右边parent->_right = cur;cur->_parent = parent;}else{return false;}}// 至此,普通二叉搜索树的插入已经完成,该进行红黑树的高度调整了// 终止条件是parent为空,或者parent已经是黑色了,就意味着不需要调整了// parent是红色,意味着grandparent一定存在while (parent && parent->_col == RED){// 更变的核心是舅舅,因此要先找到舅舅// 整体来说还有两种情况,parent可能是grandparent的左或者右,舅舅就是另外一边Node* grandparent = parent->_parent;if (parent == grandparent->_left){Node* uncle = grandparent->_right;// 1. 舅舅存在,并且是红色if (uncle && uncle->_col == RED){//     g//   p   u// c// 变色parent->_col = uncle->_col = BLACK;grandparent->_col = RED;// 向上处理cur = grandparent;parent = cur->_parent;}// 2. 舅舅不存在else{// 如果cur是左孩子if (cur == parent->_left){//     g//   p// c// 对grandparent进行右旋RotateR(grandparent);// 变色cur->_col = grandparent->_col = RED;parent->_col = BLACK;}// 如果cur是右孩子else{//     g               g//  p       -->     c         -->    c//    c           p                p   g// 对parent左旋,对grandparent右旋RotateL(parent);RotateR(grandparent);// 变色cur->_col = BLACK;parent->_col = grandparent->_col = RED;}// 更新之后parent和grandparent顺序乱了,而且也不需要继续调整了,直接breakbreak;}}// parent是grandparent的右孩子,相同的逻辑再进行一次else{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandparent->_col = RED;// 继续往上处理cur = grandparent;parent = cur->_parent;}else{if (cur == parent->_right){//   g//      p//         cRotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{//     g//       p //     cRotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}// 不管上面怎么变都无所谓,只需要保证根节点是黑的就可以了_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void inorder(){_inorder(_root);cout << endl;}bool isbalance(){return _isbalance(_root);}private:bool _check(Node* root, int blacknum, const int RefVal){// 判断黑色路径数量是否相等if (root == nullptr){if (blacknum != RefVal){cout << "黑色节点数量不相等" << endl;return false;}return true;}// 判断是否有连续的红色节点if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){blacknum++;}return _check(root->_left, blacknum, RefVal)&& _check(root->_right, blacknum, RefVal);}// 判断红黑树是否平衡bool _isbalance(Node* root){// 如果根节点是红的,说明错了if (root->_col == RED){cout << "根节点是红色" << endl;return false;}// 统计一下路径中有多少黑色节点int RefVal = 0;Node* cur = root;while (cur){if (cur->_col == BLACK)RefVal++;cur = cur->_left;}// 判断路径中的黑色节点是否相等return _check(root, 0, RefVal);}void _inorder(Node* root){if (root == nullptr)return;_inorder(root->_left);cout << root->_kv.second << " ";_inorder(root->_right);}Node* _root = nullptr;
};
int main()
{const int N = 100000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}RBTree<int, int> t;for (auto e : v){cout << "Insert:" << e << "->";t.insert(make_pair(e, e));cout << t.isbalance() << endl;}cout << t.isbalance() << endl;return 0;
}

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

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

相关文章

IDEA的优化配置教程

前言 IDEA 全称 IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以…

thinkphp5 连接多个服务器数据库

如果你的database.php 是这样&#xff0c; 这是默认的db连接配置 如果还想连接其他服务器&#xff0c;或数据库 在config.php中追加数据库配置&#xff0c; 在使用的地方调用&#xff1a; use think\Db;public function test(){$db3Db::connect(config(db3));$result $db3…

使用gitflow时如何合并hotfix

前言 在使用 git flow 流程时, 对于项目型的部署项目经常会遇到一个问题, 就是现场项目在使用历史版本时发现的一些问题需要修复, 但升级可能会有很大的风险或客户不愿意升级, 这时就要求基于历史版本进行 hotfix 修复. 基于历史发布版本的缺陷修复方式不同于最新发布版本的补…

自然语言处理实战项目21-两段文本的查重功能,返回最相似的文本字符串,可应用于文本查重与论文查重

大家好,我是微学AI,今天给大家介绍一下自然语言处理实战项目21-两段文本的查重功能,返回最相似的文本字符串,可应用于论文查重。本文想实现一种文本查重功能,通过输入两段文本,从中找出这两段文本中最相似的句子。这项技术有助于检测抄袭、抄袭的论文和文章,提高知识创新…

【SpringBoot】SpringBoot自动配置底层源码解析

概述 EnableAutoConfiguration源码解析SpringBoot常用条件注解源码解析SpringBoot之Mybatis自动配置源码解析SpringBoot之AOP自动配置源码解析SpringBoot Jar包启动过程源码解析 DeferredImportSelector接口 DeferredImportSelector和ImportSelector的区别在于&#xff1a; …

Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks - 翻译学习

知识密集型NLP任务的检索增强生成 - 论文学习 文章目录 Abstract1 Introduction2 Methods2.1 Models2.2 Retriever: DPR2.3 Generator: BART2.4 Training2.5 Decoding 3 Experiments3.1 Open-domain Question Answering3.2 Abstractive Question Answering3.3 Jeopardy Questio…

医疗器械维修工程师必须重视的方面

彩虹医疗器械维修技能培训开班报名中 长期班低至五折&#xff0c; 打破常规培训模式轻松愉快技术学习&#xff01; 两个多月时间&#xff0c;提升自我&#xff01; 点击进入 彩虹实训基地 理论实践结合教学 小班授课 立即咨询 1 工程师须重视 在医疗行业中&#xff0c;…

青少年编程学习 等级考试 信奥赛NOI/蓝桥杯/NOC/GESP等比赛资料合集

一、博主愚见 在当今信息技术高速发展的时代&#xff0c;编程已经成为了一种必备的技能。随着社会对于科技人才的需求不断增加&#xff0c;青少年编程学习正逐渐成为一种趋势。为了更好地帮助青少年学习编程&#xff0c;提升他们的技能和素质&#xff0c;博主结合自身多年从事青…

MacOS下VMware Fusion配置静态IP

前言 在虚拟机安装系统后&#xff0c;默认是通过DHCP动态分配的IP&#xff0c;这会导致每次重启虚拟机ip都可能会改变&#xff0c;使用起来会有很多不便。 配置静态IP 查看主机网关地址 cat /Library/Preferences/VMware\ Fusion/vmnet8/nat.conf 查看主机DNS&#xff0c;m…

总结MYSQL中VHARCHAR和TEXT

前几天在设计表结构时&#xff0c;针对表中的一个字段使用text还是使用varchar是受到了开发同学的挑战。本篇文章对text和varchar的区别做个总结。 VHARCHAR和TEXT对比 char(n)varchar(n)中括号中n代表字符的个数&#xff0c;并不代表字节个数&#xff0c;所以当使用了中文的…

笔记本分屏怎么操作?3个方法提高工作效率!

“有朋友知道笔记本怎么才能实现分屏吗&#xff1f;我在工作时&#xff0c;经常需要来回切换屏幕&#xff0c;效率真的太低了&#xff0c;有什么方法可以实现两个屏幕同时使用吗&#xff1f;” 在现代生活中&#xff0c;多任务处理已成为常态&#xff0c;而笔记本分屏技术为用户…

电脑监控软件丨功能详情丨特点分析

电脑监控软件的出现&#xff0c;是在信息技术的飞速发展以及计算机使用的普及的背景下产生的。随着计算机在企业、学校以及家庭等各个场所的广泛使用&#xff0c;管理和保护计算机数据安全的问题变得越来越重要。因此&#xff0c;电脑监控软件应运而生&#xff0c;旨在帮助用户…

浅谈掌动智能验收测试主要服务内容

所谓验收测试是对软件的功能性、性能效率、兼容性、易用性、可靠性、信息安全性、维护性、可移植性进行测试&#xff0c;对产品说明、用户文档集进行审阅&#xff0c;为科研项目、信息工程项目等进行第三方验收评测&#xff0c;交付验收测试报告。本文将介绍掌动智能验收测试主…

Rust 中的引用与借用

目录 1、引用与借用 1.1 可变引用 1.2 悬垂引用 1.3 引用的规则 2、slice 类型 2.1 字符串字面量其实就是一个slice 2.2 总结 1、引用与借用 在之前我们将String 类型的值返回给调用函数&#xff0c;这样会导致这个String会被移动到函数中&#xff0c;这样在原来的作用域…

Python数据结构: 列表(List)详解

在Python中&#xff0c;列表&#xff08;List&#xff09;是一种有序、可变的数据类型&#xff0c;被广泛用于存储和处理多个元素。列表是一种容器&#xff0c;可以包含任意数据类型的元素&#xff0c;包括数字、字符串、列表、字典等。本文将深入讨论列表的各个方面&#xff0…

TCP连接出现大量CLOSE_WAIT不回收的问题排查

背景 日常运维过程中&#xff0c;收到“应用A”突然挂起没有处理请求的告警&#xff0c;然后触发“存活检查”不通过&#xff0c;自动重启了。 问题 为什么“应用A”突然挂起&#xff1f; 分析 排查过程很长&#xff0c;走了很多弯路&#xff0c;这里只列出本案例有效行动…

K8S知识点(八)

&#xff08;1&#xff09;实战入门-Label 通过标签实现Pod的区分&#xff0c;说白了就是一种标签选择机制 可以使用命令是否加了标签&#xff1a; 打标签&#xff1a; 更新标签&#xff1a; 筛选标签&#xff1a; 修改配置文件&#xff0c;重新创建一个pod 筛选&#xff1…

Java集合框架

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

代理模式-静态动态代理-jdk动态代理-cglib动态代理

代理模式 静态代理 动态代理&#xff1a;jdk动态代理 cglib动态代理 注意 &#xff1a;下面的代码截图 要配合文字去看 我对代码的每一步都做了解释 所以需要配合图片观看提取吗1111https://pan.baidu.com/s/1OxQSwbQ--t5Zvmwzjh1T0A?pwd1111 这里直接把项目文件 及代码 …

MySQL(15):存储过程与函数

存储过程概述 含义&#xff1a; 存储过程的英文是 Stored Procedure 。它的思想很简单&#xff0c;就是一组经过 预先编译 的 SQL 语句的封装。 执行过程&#xff1a; 存储过程预先存储在 MySQL 服务器上&#xff0c;需要执行的时候&#xff0c;客户端只需要向服务器端发出调用…