【C++篇】红黑树的实现

目录

前言:

一,红黑树的概念

1.1,红黑树的规则

1.2,红黑树的最长路径 

1.3,红黑树的效率分析

 二,红黑树的实现

2.1,红黑树的结构

2.2,红黑树的插入

2.2.1,大致过程 

2.2.2,情况1:变色处理 

 2.2.3,情况2:单旋+变色

2.2.4,情况3:双旋+变色

2.3,红黑树插入代码的实现

2.4,红黑树的验证

三,整体代码

四,测试代码


前言:

本篇会用到上篇【AVL树的实现】中的旋转知识。

一,红黑树的概念

        红黑树是一颗二叉搜索树,它的每一个节点增加一个存储为来表示节点的颜色。可以是红色或者黑色。它通过对从根开始到叶子节点的每条路径上各个节点颜色的约束,确保最长路径不会超过最短路径的2倍,从而实现平衡的。

1.1,红黑树的规则

1,每个节点不是红色就是黑色。

2,根节点是黑色的。

3,红色节点的两个孩子只能是 黑色节点或者是空节点。也就是说不能出现连续的红色节点

4,对于任意一个节点,从该节点开始,到叶子节点的所有路径上,均包含相同数量的黑色节点。

以上 都是红黑树,满足红黑树的规则。

1.2,红黑树的最长路径 

1,由第四条规则可知,从根节点开始的每条路径上,黑色节点的数量相同。所以在极端场景下,一颗红黑树,它的最短路径就是节点全为黑色的路径。假设红黑树的每条路径黑色节点数量都为b,那么最短路径的节点数量为b.

2,由 第三条规则可知,一条路径上不能由连续的红色节点,最长路径是由一黑一红间隔组成的,所以最长路径为2*b。

3,而对于一颗红黑树,最长和最短路径不一定存在。我们可以得出对于任意一颗红黑树,它的任意 一条路径长度x都是,b<=x<=2*b.

1.3,红黑树的效率分析

假设N是红黑树节点的数量,h是最短路径的长度,最长路径不超过2*h

可以得到2^h-1<= N <= 2^(2*h)-1,推出h大致为logN,也就意味着红黑树的增删查该最坏走2*logN,时间复杂度O(logN).

 二,红黑树的实现

2.1,红黑树的结构

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)
    {}
    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:

   //...

private:

    Node* _root=nullptr;
};
 

2.2,红黑树的插入

2.2.1,大致过程 

1,插入一个值需要按照搜索树的规则进行插入,再判断插入后是否满足红黑树的规则。

2,如果是空树插入,新增节点就是黑色节点。如果是非空树插入,新增节点就必须是红色节点,因为如果插入黑色节点,就一定会破坏规则4,而插入红色节点是有可能会破坏规则3,而且对于规则3来说,解决方法比规则4的解决方法容易。

3,非空树插入后,如果父节点是黑色节点,则没有违反任何规则,插入结束。

4,非空树插入后,如果父节点是红色节点,则违反规则3,进一步分析。

2.2.2,情况1:变色处理 

由上图可知,c为红,p为红,g为黑,u存在且为红。在这种情况下,我们需要将p和u变黑,g变红,g成为新的c,继续向上更新。

分析:

        因为p和u都是红色的,g是黑色的。把p和u变黑,左边子树路径各增加一个黑色节点,g再变红,相当于g所在路径的黑色节点数量不变,同时解决了c和p连续红节点的问题。

        需要继续往上跟新是因为g是红色,如果g的父亲还是红色,就需要继续处理;如果g的父亲是黑色,则处理结束;如果g是整棵树的根,再将g变成黑色即可。 

 2.2.3,情况2:单旋+变色

c为红,p为红,u不存在或者u存在且u为黑色。在这种情况下,就需要进行单旋+变色处理

分析:

u不存在时,c一定是新增节点。

u存在且为黑色时,c一定不是新增节点,是在c的子树中插入,符号情况1,经过情况1后,c被调整为红色的。

 上图展示的是右单旋的场景,下面是根据右单旋,画出的左单旋大致图,与右单旋过程相似:

 总结:经过单旋+变色后,我们可以看到p做了子树新的根,且p是黑色的,所以不管p的父亲是黑色的还是红色的,都满足红黑树的规则,所以此时,就不需要往上更新了。

2.2.4,情况3:双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增结点,u存在且为黑,则 c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。

 上图展示的是左右双旋+变色,同样右左双旋类似:

同样经过双旋+变色后,c成为新的根,且c为黑色,所以也不需要继续向上更新了。

2.3,红黑树插入代码的实现

bool Insert(const pair<k, v>& kv)
{//插入根节点,color->Blackif (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}//根据搜索树的规则查找插入位置Node* cur = _root;Node* parent = nullptr;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);cur->_col = Red;//与父亲节点连接好if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;//颜色处理+旋转while (parent&& parent->_col == Red){//gNode* grandfather = parent->_parent;if (parent == grandfather->_left){//p为g的左边//    g//  p   uNode* uncle = grandfather->_right;//叔叔存在且为红,情况1if (uncle && uncle->_col == Red){//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;//继续向上处理cur = grandfather;parent = cur->_parent;}else{//情况2,3//叔叔不存在或者叔叔为黑//u为黑,则c之前是黑的//u不存在,则c是新插入的if (cur == parent->_left){//右单旋场景//   g//  p   u// cRotateR(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//左右双旋场景//    g//  p   u//     cRotateL(parent);RotateR(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要进行向上更新break;}}else{//p为g的右边//   g// u   pNode* uncle = grandfather->_left;if (uncle && uncle->_col == Red){//情况1//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{//情况2,3if (cur == parent->_right){//左单旋场景//  g// u   p//       cRotateL(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//右左双旋场景//   g// u   p//   cRotateR(parent);RotateL(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要继续向上更新break;}}}//跟的颜色可能被调整为红色,最后一步改为黑色即可_root->_col = Black;return true;
}
//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}
}
//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}
}

2.4,红黑树的验证

我们只需验证形成的树是否满足红黑树的四条规则即可。

其中,规则1和规则 2可以直接检查。

对于规则3,我们可以进行前序遍历,遍历到一个节点,判断该节点的颜色,再判断它的两个孩子的颜色,这样做太麻烦了。我们可以反过来,遍历到一个节点,如果他是红色的,判断它的父亲节点是否为黑色。

对于规则4,我们可以先从根开始找到一条路径上黑色节点的个数refNum,再对整棵树进行前序遍历,用变量 blackNum记录黑色节点的个数,当遍历到空的时候,与refNum比较即可。

bool Check(Node* root, int BlackNum, int num)
{if (root == nullptr){if (BlackNum != num)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;if (root->_col == Black)BlackNum++;return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
}
//验证
bool isbalance()
{if (_root == nullptr)return true;if (_root->_col == Red)return false;int num = 0;Node* cur = _root;while (cur){if (cur->_col == Black)num++;cur = cur->_left;}return Check(_root, 0, num);
}

三,整体代码

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){}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:bool Insert(const pair<k, v>& kv){//插入根节点,color->Blackif (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}//根据搜索树的规则查找插入位置Node* cur = _root;Node* parent = nullptr;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);cur->_col = Red;//与父亲节点连接好if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;//颜色处理+旋转while (parent&& parent->_col == Red){//gNode* grandfather = parent->_parent;if (parent == grandfather->_left){//p为g的左边//    g//  p   uNode* uncle = grandfather->_right;//叔叔存在且为红,情况1if (uncle && uncle->_col == Red){//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;//继续向上处理cur = grandfather;parent = cur->_parent;}else{//情况2,3//叔叔不存在或者叔叔为黑//u为黑,则c之前是黑的//u不存在,则c是新插入的if (cur == parent->_left){//右单旋场景//   g//  p   u// cRotateR(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//左右双旋场景//    g//  p   u//     cRotateL(parent);RotateR(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要进行向上更新break;}}else{//p为g的右边//   g// u   pNode* uncle = grandfather->_left;if (uncle && uncle->_col == Red){//情况1//变色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{//情况2,3if (cur == parent->_right){//左单旋场景//  g// u   p//       cRotateL(grandfather);//变色parent->_col = Black;grandfather->_col = Red;}else{//右左双旋场景//   g// u   p//   cRotateR(parent);RotateL(grandfather);//变色cur->_col = Black;grandfather->_col = Red;}//不需要继续向上更新break;}}}//跟的颜色可能被调整为红色,最后一步改为黑色即可_root->_col = Black;return true;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}}void Inorder(){_Inorder(_root);}int Height(){return _Height(_root);}int size(){return _size(_root);}Node* Find(const k& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}//验证bool isbalance(){if (_root == nullptr)return true;if (_root->_col == Red)return false;int num = 0;Node* cur = _root;while (cur){if (cur->_col == Black)num++;cur = cur->_left;}return Check(_root, 0, num);}
private:bool Check(Node* root, int BlackNum, int num){if (root == nullptr){if (BlackNum != num)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;if (root->_col == Black)BlackNum++;return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);}int _size(Node* root){if (root == nullptr)return 0;return _size(root->_left) + _size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}Node* _root=nullptr;
};

四,测试代码

void TestRBTree1()
{RBTree<int, int> t;// 常规的测试用例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的带有双旋场景的测试用例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.Inorder();cout << t.isbalance() << endl;
}
int main()
{TestRBTree1();return 0;
}

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

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

相关文章

Autodl转发端口,在本地机器上运行Autodl服务器中的ipynb文件

通过 SSH 隧道将远程端口转发到本地机器 输入服务器示例的SSH指令和密码&#xff0c;将远程的6006端口代理到本地 在服务器终端&#xff0c;激活conda虚拟环境 conda activate posecnnexport PYOPENGL_PLATFORMegljupyter notebook --no-browser --port6006 --allow-root从…

镭速大文件传输视频文件预览实现原理

镭速可以支持视频预览&#xff0c;在测试过程中需要大量不同格式的视频&#xff0c;如果直接去找各种格式的视频不太现实&#xff0c;所以就会用到一个视频格式转换的工具ffmpeg&#xff0c;本文将介绍ffmpeg的基本使用方法。FFmpeg 是一个免费开源的音视频处理工具&#xff0c…

达梦8-DMSQL程序设计学习笔记1-DMSQL程序简介

1、DMSQL程序简介 DMSQL程序是达梦数据库对标准SQL语言的扩展&#xff0c;是一种过程化SQL语言。在DMSQL程序中&#xff0c;包括一整套数据类型、条件结构、循环结构和异常处理结构等&#xff0c;DMSQL程序中可以执行SQL语句&#xff0c;SQL语句中也可以使用DMSQL函数。 DMSQ…

C# 获取PDF文档中的字体信息(字体名、大小、颜色、样式等

在设计和出版行业中&#xff0c;字体的选择和使用对最终作品的质量有着重要影响。然而&#xff0c;有时我们可能会遇到包含未知字体的PDF文件&#xff0c;这使得我们无法准确地复制或修改文档。获取PDF中的字体信息可以解决这个问题&#xff0c;让我们能够更好地处理这些文件。…

git操作(Windows中GitHub)

使用git控制GitHub中的仓库版本&#xff0c;并在Windows桌面中创建与修改代码&#xff0c;与GitHub仓库进行同步。 创建自己的GitHub仓库 创建一个gen_code实验性仓库用来学习和验证git在Windows下的使用方法&#xff1a; gen_code仓库 注意&#xff0c;创建仓库时不要设置…

thinkphp6 + redis实现大数据导出excel超时或内存溢出问题解决方案

redis下载安装&#xff08;window版本&#xff09; 参考地址&#xff1a;https://blog.csdn.net/Ci1693840306/article/details/144214215 php安装redis扩展 参考链接&#xff1a;https://blog.csdn.net/jianchenn/article/details/106144313 解决思路&#xff1a;&#xff0…

设计模式-工厂模式/抽象工厂模式

工厂模式 定义 定义一个创建对象的接口&#xff0c;让子类决定实列化哪一个类&#xff0c;工厂模式使一个类的实例化延迟到其子类&#xff1b; 工厂方法模式是简单工厂模式的延伸。在工厂方法模式中&#xff0c;核心工厂类不在负责产品的创建&#xff0c;而是将具体的创建工作…

学习threejs,使用OrbitControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.OrbitControls 相机控…

Linux:认识Shell、Linux用户和权限

谈起Linux&#xff0c;一般是在说区别于Windows的Linux操作系统&#xff0c;而Linux刚开始用来指Linux内核。值得一提的是&#xff0c;安卓底层也是Linux内核&#xff0c;而安卓系统又是区别于Linux的操作系统。 目录 一、Shell 为什么会有Shell 什么是Shell Shell相关具体…

HCIP-VLAN-hybrid接口+DHCP实验

1.拓扑及实验需求 2.需求分析 需要用到的技术 1、虚拟局域网&#xff08;VLAN&#xff09; 2、动态主机配置协议&#xff08;DHCP&#xff09; 3、单臂路由 首先先完成交换部分&#xff0c;创建对应的vlan将PC1与PC3划入vlan2且接口类型为access&#xff0c;而以外的PC都划…

【Redis】Redis 集群中节点之间如何通信?

【Redis】Redis 集群中节点之间如何通信&#xff1f; 一背景概述二通信协议Gossip 协议 三通信机制Gossip 消息类型(1).Ping消息(2).Pong消息(3).Meet消息(4).Fail消息 消息传播模式(1).反熵(Anti-entropy)(2).谣言传播(Rumor mongering) 四通信过程通信端口通信频率故障检测与…

《Keras 3 在 TPU 上的肺炎分类》

Keras 3 在 TPU 上的肺炎分类 作者&#xff1a;Amy MiHyun Jang创建日期&#xff1a;2020/07/28最后修改时间&#xff1a;2024/02/12描述&#xff1a;TPU 上的医学图像分类。 &#xff08;i&#xff09; 此示例使用 Keras 3 在 Colab 中查看 GitHub 源 简介 设置 本教程将介…

计算机网络 (42)远程终端协议TELNET

前言 Telnet&#xff08;Telecommunication Network Protocol&#xff09;是一种网络协议&#xff0c;属于TCP/IP协议族&#xff0c;主要用于提供远程登录服务。 一、概述 Telnet协议是一种远程终端协议&#xff0c;它允许用户通过终端仿真器连接到远程主机&#xff0c;并在远程…

微服务拆分

微服务拆分 接下来&#xff0c;我们就一起将黑马商城这个单体项目拆分为微服务项目&#xff0c;并解决其中出现的各种问题。 熟悉黑马商城 首先&#xff0c;我们需要熟悉黑马商城项目的基本结构&#xff1a; 大家可以直接启动该项目&#xff0c;测试效果。不过&#xff0c…

「刘一哥GIS」系列专栏《GRASS GIS零基础入门实验教程(配套案例数据)》专栏上线了

「刘一哥GIS」系列专栏《GRASS GIS零基础入门实验教程》全新上线了&#xff0c;欢迎广大GISer朋友关注&#xff0c;一起探索GIS奥秘&#xff0c;分享GIS价值&#xff01; 本专栏以实战案例的形式&#xff0c;深入浅出地介绍了GRASS GIS的基本使用方法&#xff0c;用一个个实例讲…

Lianwei 安全周报|2025.1.13

新的一周又开始了&#xff0c;以下是本周「Lianwei周报」&#xff0c;我们总结推荐了本周的政策/标准/指南最新动态、热点资讯和安全事件&#xff0c;保证大家不错过本周的每一个重点&#xff01; 政策/标准/指南最新动态 01 美国国土安全部发布《公共部门生成式人工智能部署手…

sparkSQL练习

1.前期准备 &#xff08;1&#xff09;建议先把这两篇文章都看一下吧&#xff0c;然后把这个项目也搞下来 &#xff08;2&#xff09;看看这个任务 &#xff08;3&#xff09;score.txt student_id,course_code,score 108,3-105,99 105,3-105,88 107,3-105,77 105,3-245,87 1…

使用docker-compose安装ELK(elasticsearch,logstash,kibana)并简单使用

首先服务器上需要安装docker已经docker-compose&#xff0c;如果没有&#xff0c;可以参考我之前写的文章进行安装。 https://blog.csdn.net/a_lllk/article/details/143382884?spm1001.2014.3001.5502 1.下载并启动elk容器 先创建一个网关&#xff0c;让所有的容器共用此网…

vue3+elementPlus之后台管理系统(从0到1)(day1)

vue3官方文档&#xff1a;https://cn.vuejs.org/guide/introduction.html 1、项目创建 确保电脑已安装node 查看命令&#xff1a; node -v进入项目目录&#xff0c;创建项目 npm init vuelatest Need to install the following packages: create-vue3.13.0 Ok to procee…

SparkSQL数据模型综合实践

文章目录 1. 实战概述2. 实战步骤2.1 创建数据集2.2 创建数据模型对象2.2.1 创建常量2.2.2 创建加载数据方法2.2.3 创建过滤年龄方法2.2.4 创建平均薪水方法2.2.5 创建主方法2.2.6 查看完整代码 2.3 运行程序&#xff0c;查看结果 3. 实战小结 1. 实战概述 在本次实战中&#…