拿捏红黑树(C++)

文章目录

  • 前言
  • 一、红黑树介绍
  • 二、插入操作
  • 三、验证红黑树
  • 四、红黑树与AVL性能比较与应用
  • 五、总体代码
  • 总结


前言

我们之前介绍了一种AVL的高阶数据结构,在本篇文章中,我们将会介绍一种与AVL旗鼓相当的数据结构–红黑树。
我们并且会对它的部分接口进行模拟实现

一、红黑树介绍

AVL是保证左右高度不超过1,实现平衡。
红黑树是在每个节点存储位表示颜色,包括红色和黑色,并且保证最长路径的节点个数不超过最短节点路径的两倍,我们就可以达到一种近似平衡
在这里插入图片描述

性质

🌟每个节点颜色不是红色就是黑色
🌟根节点是黑色的
🌟如果一个节点是红色,那么它的孩子必须是黑色节点(不允许出现连续的红色节点
🌟每条路径都包含相同数量的黑色节点(路径:根节点到空)
🌟空节点设置为黑色,这个节点也称为NIL节点

为什么这几条规则就可以保证最长路径的节点数量不超过最短路径节点数量的两倍呢??

我们从极端场景分析:
最短路径:全黑节点
最长路径:一黑一红

二、插入操作

我们需要一个位置表示颜色,这里我们采用枚举(enum)的方式。

我们插入到节点是插入红色呢还是黑色呢??

我们看一下主要的规则:每条路径都包含相同数量的黑色节点;不允许出现连续的红色节点。

如果我们插入黑色节点,每条路径都会受到影响,我们是很难控制调整的。
如果我们插入红色节点,不允许出现连续的红色节点。我们只是在一条路径上插入,只需要调整这条路径上的节点,保证不出现连续的红色节点就可以。

先把大框架实现。

enum Col
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Col  _col;//表示颜色pair<K, V>_kv;RBTreeNode(const pair<K, V>kv):_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED)//初始化为红色,_kv(kv){}
};template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:private:Node* _root = nullptr;
};

如何插入节点呢??
1.按照二叉搜索树规则找到插入节点
2.进行颜色调整

我们插入的是红色节点,如果父亲节点的颜色也是红色,我们就需要进行调整。
如果父亲节点的颜色是黑色,我们就不需要进行处理,直接退出。

调整:
我们这里的关键是看叔叔
因为我们已经知道我们插入的节点是红色,并且父亲也是红色,爷爷节点必定是黑色。
我们唯一不确定的就是叔叔节点

uncle存在且为红

在这里插入图片描述

我们分析一下具象图

当a/b/c/d/e都为空时,cur就是新插入节点。
在这里插入图片描述

我们需要把p和u变黑,同时把g变黑
在这里插入图片描述

如果这个节点是根节点,我们就把g变黑。
否则cur=g,p=g->p;继续调整。(原来根是黑色,现在变为红色)

当c/d/e是包含一个黑色节点的子树

有四种情况
在这里插入图片描述

我们选择最简单的看一下,插入的位置有四个选择。
在这里插入图片描述

我们把p和u变黑,同时g变红

下图才是我们这种去情况的具象图,这种情况是由之前的情况调整过来的。
在这里插入图片描述

把p和u变黑,同时g变红
如果这个节点是根节点,我们就把g变黑。
否则cur=g,p=g->p;继续调整。(原来根是黑色,现在变为红色)

在这里插入图片描述
我们发现g就是根节点,我们把g变黑

在这里插入图片描述

我们发现现在每条路径上黑节点的数量增加了,由原来两个黑节点变为现在3个黑节点。
黑色节点的数量增加是在根节点增加的,根节点增加就相当于每条路径都增加。

uncle不存在,或者存在且为黑

在这里插入图片描述

如果u不存在,cur就是新增加点,我们通过之前的变色已经完成不了了。
在这里插入图片描述

我们这时就需要进行旋转,旋转方式还是按照AVL树的情况。
这个场景下我们需要右旋

在这里插入图片描述
旋转完之后,进行变色,p变黑,g变红
在这里插入图片描述

如果是下面这种情况,我们就需要进行双旋之后,再进行变色
在这里插入图片描述

首先对p进行左旋
在这里插入图片描述

再对g进行右旋
在这里插入图片描述
最后进行变色,cur变为黑,g变为红
在这里插入图片描述

总结:
🌟我们新插入节点颜色是红色
🌟如果新插入节点的父亲节点是黑色,我们不进行调整,直接退出
🌟如果新插入节点的父亲节点是红色,此时关键看叔叔。
🌟如果叔叔存在且为红,将p和u变黑,g变红。判断g是否为根节点。如果为根节点,g变黑。否则继续调整。
我们不关心左右,p和u是g的左右都不收影响,cur是p的左右也不受影响。
🌟如果uncle不存在或者存在且为黑,调整完之后结束。原来根是黑色,现在根也是黑色,不影响
🌟p为g的左孩子,cur为p的左孩子,对g进行右单旋,p变黑,g变红
🌟p为g的右孩子,cur为p的右孩子,对g进行左单旋,p变黑,g变红
🌟p为g的左孩子,cur为p的右孩子,对p进行左单旋,对g进行右单旋,c变黑,g变红
🌟p为g的右孩子,cur为p的左孩子,对p进行右单旋,对g进行左单旋,c变黑,g变红

	bool Insert(const pair<K, V>kv){if (_root == nullptr){_root = new Node(kv);//根节点是黑色_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}//存在else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//颜色调整while (parent && parent->_col == RED){//因为parent存在且不是黑色节点,则parent一定不是根,一定存在。Node* grandfather = parent->_parent;if (grandfather->_left == parent){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);parent->_col = BLACK;grandfather->_col = RED;}//左子树的右边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);parent->_col = BLACK;grandfather->_col = RED;}//右子树的左边else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}_root->_col = BLACK;}return true;}

我们有可能需要大量判断是否为根节点的情况,我们直接在结尾处加上
_root->_col = BLACK;暴力处理

三、验证红黑树

我们如何进行验证是一颗红黑树呢??
我们从主要的规则入手

🌟根节点是黑色的
🌟不允许出现连续的红色节点
🌟每条路径都包含相同数量的黑色节点

我们只需要判断这三条成立,就能保证最长路径的节点个数不超过最短节点路径的两倍,从而证明这就是一颗红黑树。

根是黑节点很好证明,其他两条呢??

不允许出现连续红色节点,判断一个节点和它的父节点是否都是红色。
这里如果判断这个节点和它的孩子节点会很复杂。

每条路径都包含相同数量的黑色节点,我们可以选择其中一条路径,计算出有多少个黑色节点,从而判断其他路径的黑色节点数量。

bool  IsBalance(){if (_root && _root->_col == RED){cout << "根节点为红色" << endl;return false;}//参考值int level = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){level++;}cur = cur->_left;}return check(_root, 0, level);}//BlackNum不能加引用
bool check(Node* root, int BlackNum, int level)
{if (root == nullptr){if (BlackNum != level){cout << "不包含相同数量的黑色节点" << endl;return false;}return true;}//判断红节点if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "出现连续红节点" << endl;return false;}if (root->_col == BLACK){BlackNum++;}return check(root->_left, BlackNum, level) && check(root->_right, BlackNum, level);
}

四、红黑树与AVL性能比较与应用

性能

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

AVL高度logN,红黑树高度logN*2.红黑树搜索效率相对AVL差一点,但是logN足够小,可以忽略不计。
N=10亿。logN=30.

应用:
1.C++ STL库 – map/set、mutil_map/mutil_set
2.Java 库
3. linux内核
4.其他库

五、总体代码

#pragma onceenum Col
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Col  _col;pair<K, V>_kv;RBTreeNode(const pair<K, V>kv):_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED),_kv(kv){}
};template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>kv){if (_root == nullptr){_root = new Node(kv);//根节点是黑色_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}//存在else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//颜色调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){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);parent->_col = BLACK;grandfather->_col = RED;}//左子树的右边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);parent->_col = BLACK;grandfather->_col = RED;}//右子树的左边else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}_root->_col = BLACK;}return true;}//1.根节点是黑色//2.不包含连续的红色节点//3.每条路径都包含相同黑色节点bool  IsBalance(){if (_root && _root->_col == RED){cout << "根节点为红色" << endl;return false;}//参考值int level = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){level++;}cur = cur->_left;}return check(_root, 0, level);}void Inorder(){_Inorder(_root);}
private://BlackNum不能加引用bool check(Node* root, int BlackNum, int level){if (root == nullptr){if (BlackNum != level){cout << "不包含相同数量的黑色节点" << endl;return false;}return true;}//判断红节点if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "出现连续红节点" << endl;return false;}if (root->_col == BLACK){BlackNum++;}return check(root->_left, BlackNum, level) && check(root->_right, BlackNum, level);}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right= subRL;if (subRL){subRL->_parent = parent;}subR->_left= parent;Node* ppnode = parent->_parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (parent == ppnode->_left){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppnode = parent->_parent;parent->_parent = subL;if (ppnode == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parent == ppnode->_left){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}Node* _root = nullptr;
};

总结

以上就是今天要讲的内容,本文仅仅详细介绍了红黑树的特征,已经模拟实现了插入操作 。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

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

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

相关文章

Autoxjs 实践-Spring Boot 集成 WebSocket

概述 最近弄了福袋工具&#xff0c;由于工具运行中&#xff0c;不好查看福袋结果&#xff0c;所以我想将福袋工具运行数据返回到后台&#xff0c;做数据统计、之后工具会越来越多&#xff0c;就弄了个后台&#xff0c;方便管理。 实现效果 WebSocket&#xff1f; websocket是…

动态规划(01背包+并查集)

P1455 搭配购买 题意&#xff1a;就是说有n朵云&#xff0c;每朵云有自己的价钱&#xff08;重量&#xff09;和价值&#xff08;价值&#xff09;&#xff0c;还有我自己现在有钱的数目&#xff08;背包&#xff09;&#xff0c;然后还告诉你&#xff0c;哪几朵云是属于捆绑销…

“独特团购策略引领小程序商城一月狂赚600万“

你是否曾经对那些富有创意且成功的商业模式心生羡慕&#xff0c;最终它们通过非凡的业绩证明了自身的价值&#xff1f;今日&#xff0c;我要分享的是一个独特的小程序商城案例&#xff0c;它凭借一种别出心裁的团购策略&#xff0c;在短短一个月内实现了超过600万的营收&#x…

LeetCode 56 合并区间

本题中可以学到的比较重要的方法 lambda表达式定义自定义比较器Comparator Arrays.sort(intervals,(v0,v1)->{return v0[0] - v1[0];}); (附 : 这种形式也适合于优先队列创建时的自定义比较器定义) 比如&#xff1a; PriorityQueue<Integer> minTop new Priorit…

JAVA小案例-输出100-150中能被3整除的数,每5个换行

JAVA小案例-输出100-150中能被3整除的数&#xff0c;每5个换行 代码如下&#xff1a; public class Continue {/*** continue练习&#xff0c;输出100-150中能被3整除的数&#xff0c;每5个换行* param args*/public static void main(String[] args) {int count 0;//计数器…

【kubernetes】探索k8s集群的存储卷、pvc和pv

目录 一、emptyDir存储卷 1.1 特点 1.2 用途 1.3部署 二、hostPath存储卷 2.1部署 2.1.1在 node01 节点上创建挂载目录 2.1.2在 node02 节点上创建挂载目录 2.1.3创建 Pod 资源 2.1.4访问测试 2.2 特点 2.3 用途 三、nfs共享存储卷 3.1特点 3.2用途 3.3部署 …

ARM32开发--GPIO输入模式

知不足而奋进 望远山而前行 目录 文章目录 前言 浮空输入 上拉输入 下拉输入 模拟输入 总结 前言 在数字电路设计和嵌入式系统开发中&#xff0c;理解输入信号的处理方式对确保系统稳定性和可靠性至关重要。不同的输入处理方式包括上拉输入、下拉输入、浮空输入和模拟输…

解决JSON.stringify 方法在序列化 BigInt 类型时的错误

今天学nest时&#xff0c;使用apifox发送请求获取数据&#xff0c;结果还一直报错&#xff0c;而且还是我从未见过的 Do not know how to serialize a BigInt at JSON.stringify (<anonymous>) at stringify&#xff0c; 我都是跟着人家敲的&#xff0c;我就纳闷了&…

06Docker-Compose和微服务部署

Docker-Compose 概述 Docker Compose通过一个单独的docker-compose.yml模板文件来定义一组相关联的应用容器&#xff0c;帮助我们实现多个相互关联的Docker容器的快速部署 一般一个docker-compose.yml对应完整的项目,项目中的服务和中间件对应不同的容器 Compose文件实质就…

高德面试:为什么Map不能插入null?

在 Java 中&#xff0c;Map 是属于 java.util 包下的一个接口&#xff08;interface&#xff09;&#xff0c;所以说“为什么 Map 不能插入 null&#xff1f;”这个问题本身问的不严谨。Map 部分类关系图如下&#xff1a; 所以&#xff0c;这里面试官其实想问的是&#xff1a;为…

【Python系列】Python 方法变量参数详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

RetroMAE-文本embedding算法

1)输入文本经掩码操作后由编码器&#xff08;Encoder&#xff09;映射为隐空间中的语义向量&#xff1b;而后解码器&#xff08;Decoder&#xff09;借助语义向量将另一段独立掩码的输入文本还原为原始的输入文本 2)编码器的掩码率为15%-30%&#xff1b;解码器的掩码率为50%-70…

【工具】批量SKU生成器

一个用户加我&#xff0c;要我帮忙写一个生成SKU的工具&#xff0c;他希望可以自定义生成的选项&#xff0c;可以批量生成。我到网上找了好久也没有找到好用的&#xff0c;就花了一下午写了这个生成sku的功能 工具支持批量生成SKU&#xff0c;支持自定义配置项&#xff0c;支持…

多表连接查询和子查询

一、连接查询 连接查询是SQL语言最强大的功能之一&#xff0c;它可以执行查询时动态的将表连接起来&#xff0c;然后从中查询数据。 1.1、连接两表的方法 在SQL中连接两表可以有两种方法&#xff0c;一种是无连接规则连接&#xff0c;另一种是有连接规则连接。 无连接规则连…

Spring Boot 整合 spring-boot-starter-mail 实现邮件发送和账户激活

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

C#WPF数字大屏项目实战08--生产量/良品统计

1、区域划分 生产量/良品统计这部分位于第二列的第二行 2、livechart拆线图 定义折线图,如下: <lvc:CartesianChart> <lvc:CartesianChart.Series> <!--设置Series的类型为 Line 类型, 该类型提供了一些折线图的实现--> <lvc:LineSeries/>…

性能狂飙:SpringBoot应用优化实战手册

在数字时代&#xff0c;速度就是生命&#xff0c;性能就是王道&#xff01;《极速启航&#xff1a;SpringBoot性能优化的秘籍》带你深入SpringBoot的内核&#xff0c;探索如何打造一个飞速响应、高效稳定的应用。从基础的代码优化到高级的数据库连接池配置&#xff0c;再到前端…

数据库与数据库管理系统 MySQL的安装 SQL语言学习:DDL、DML

day51 数据库 数据库&#xff08;database&#xff09;就是一个存储数据的仓库。为了方便数据的存储和管理&#xff0c;它将数据按照特定的规律存储在磁盘上。 通过数据库管理系统&#xff0c;可以有效地组织和管理存储在数据库中的数据&#xff0c;如数据库管理系统MySQL 数据…

Python-3.12.0文档解读-内置函数repr()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 概述 详细描述 自定义类的行为 使用示例 异常处理 注意事项 总结 记…

【Linux】深入理解文件操作:从C语言接口到系统调用与缓冲区管理

文章目录 前言&#xff1a;1. 铺垫1.1. 对文件表述符的理解 2. 重新使用C文件接口&#xff1a;对比一下重定向2.1. 什么叫当前路径&#xff1f;2.2. 写入文件2.3. 读文件2.4. 程序默认打开的文件流2.5. 输出2.6. 输入 3. 系统调用提供的文件接口3.1. open 打开文件3.2. open函数…