实现红黑树

目录

红黑树的概念

红黑树的节点结构定义

红黑树的插入

红黑树的验证

实现红黑树完整代码


红黑树的概念

红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red
Black 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍 ,因而是 接近平衡 的。
注意:可以认为AVL树是严格平衡的,而红黑树是近似平衡的
而上述所说的 对任何一条从根到叶子的路径上各个结点着色方式的限制具体如下:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(不存在连续的红色节点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
5. 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )

有了上述限制,最短路径就是只包含黑色节点的路径,而最长路径就是黑红交替,所以所有路径长度范围都在[最短路径,最短路径的2倍]这个区间之间, 保证了红黑树近似平衡

红黑树的节点结构定义

//颜色定义成枚举类型
enum Color
{RED,BLACK
};//节点结构定义
template<class K, class V>
struct RBTreeNode
{//三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;//键值对pair<K, V> _kv;//颜色变量Color _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

红黑树的插入

插入新节点我们选择插入红色节点,理由如下:

1.插入黑色节点,一定破坏性质4,所有路径的黑色节点个数都要变化

2.插入红色节点,可能破坏性质3,插入红色节点的父亲是黑色节点,没有破坏性质3, 不需要调整;插入红色节点的父亲是红色节点,破坏了性质3, 需要进行调整

综合考虑,插入红色节点的代价相比插入黑色节点小很多,因此我们选择插入红色节点

插入情况的分类:

上面已经提到,插入红色节点的父亲是黑色节点,没有破坏性质3,因此无需调整,因此下面只讨论插入红色节点的父亲是红色节点的情况

处理方法总体分两类

1.插入节点的叔叔节点存在且为红色 --- 变色即可

字母含义: cur --- 插入节点, p --- cur的父亲节点,  g --- cur的爷爷节点, u --- cur的叔叔节点

ps: g一定存在,因为p是红色节点, 不可能为根节点, 而g也一定是黑色节点,因为如果是红色节点,说明在cur插入之前红黑树就已经出问题了!

插入之后cur和p都是红色,因为不能出现连续红色节点,我们选择把p变黑,而p变黑之后,g-p-cur的这条路径多了一个黑色节点,因此我们又把g变红,g变红之后,g-u的这条路径又少了一个黑色节点,由于叔叔节点存在且为红色,因此我们又把u变黑即可

ps:变色完之后,发现g变成了红色,而上图的树可能只是一颗子树,因此g变红之后,接着:

1.如果g是根,把g变黑,因为红黑树要求根节点是黑色

2.如果g不是根,g必然有父亲,继续判断g的父亲节点的颜色,如果g的父节点是黑色,停止处理(因为没有再违反红黑树规则了);如果g的父节点是红色,此时又出现了连续的红节点,需要继续处理, 把cur更新到g的位置,继续循环处理!

2.插入节点的叔叔节不存在 或者 存在且为黑色 --- 旋转 + 变色

这种情况下,单纯的变色已经解决不了问题了!需要 旋转+变色 来处理

具体采用哪种旋转方法,在前面的博客 实现AVL树-CSDN博客 已经讲解过了

注意:无论是哪种旋转+变色方式,发现最终当前子树的根节点都变成了黑色,此时无论cur的父节点是黑色还是红色都不违反红黑树的规则,因此旋转+变色完成之后,直接break跳出循环即可!

右单旋 + 变色 (p是g的左,cur是p的左)

左右双旋 + 变色 (p是g的左,cur是p的右)

 左单旋 + 变色 (p是g的右,cur是p的右)

右左双旋 + 变色 (p是g的右,cur是p的左)

红黑树旋转代码(除了不需要调平衡因子,其他代码与AVL树一样):

	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 (parentParent == nullptr){_root = subR;_root->_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 (parentParent == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}//左右双旋void RotateLR(Node* parent){RotateL(parent->_left);RotateR(parent);}//右左双旋void RotateRL(Node* parent){RotateR(parent->_right);RotateL(parent);}

红黑树插入代码:

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;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED) //父亲不存在或者父亲为黑色就不需要继续调整了!{Node* grandfather = parent->_parent;if (grandfather->_left == parent) //父亲是爷爷的左孩子{Node* uncle = grandfather->_right;//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//调整完之后,更新cur, 判断是否还需要调整cur = grandfather;parent = cur->_parent;}//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色else{if (cur == parent->_left) //单纯左边高{RotateR(grandfather); //右单旋//变色parent->_col = BLACK;grandfather->_col = RED;}else //cur是parent的右边{RotateLR(grandfather); //左右双旋//变色cur->_col = BLACK;grandfather->_col = RED;}break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整}}else //父亲是爷爷的右孩子{Node* uncle = grandfather->_left;//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//调整完之后,更新cur, 判断是否还需要调整cur = grandfather;parent = cur->_parent;}//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色else{if (cur == parent->_left) //cur是parent的左边,进行右单旋{RotateRL(grandfather); //右左单旋//变色cur->_col = BLACK;grandfather->_col = RED;}else //cur是parent的右边{RotateL(grandfather); //左单旋//变色grandfather->_col = RED;parent->_col = BLACK;}break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整}}}_root->_col = BLACK;return true;
}

红黑树的验证

判断是否是红黑树

	bool IsRBTree(){if (_root == nullptr) return true;if (_root->_col == RED){cout << "根节点为红色" << endl;return false;}//以最左路径的黑节点的个数作为参考值Node* cur = _root;int BlackCount = 0;while (cur){if (cur->_col == BLACK)BlackCount++;cur = cur->_left;}//调用子函数判断红黑树int count = 0;return _IsRBTree(_root, count, BlackCount);}
private:bool _IsRBTree(Node* root, int count, int BlackCount){if (root == nullptr){if (count != BlackCount) {cout << "黑色节点的个数不相等" << endl;return false;}return true;}//判断节点的孩子节点颜色不好判断, 转化成判断父亲节点颜色if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红节点" << endl;return false;}if (root->_col == BLACK)count++;//递归子问题return _IsRBTree(root->_left, count, BlackCount)&& _IsRBTree(root->_right, count, BlackCount);}

验证红黑树

#include "RBTree.h"#include <vector>
void test1()
{int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));}t.IsRBTree();cout << t.IsRBTree() << endl;
}void test2()
{const int N = 30;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand());cout << v.back() << endl;}RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));cout << "Insert:" << e << "->" << t.IsRBTree() << endl;}cout << t.IsRBTree() << endl;
}int main()
{//test1();test2();return 0;
}

实现红黑树完整代码

#pragma once#include <iostream>
using namespace std;//颜色定义成枚举类型
enum Color
{RED,BLACK
};//节点结构定义
template<class K, class V>
struct RBTreeNode
{//三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;//键值对pair<K, V> _kv;//颜色变量Color _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};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;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED) //父亲不存在或者父亲为黑色就不需要继续调整了!{Node* grandfather = parent->_parent;if (grandfather->_left == parent) //父亲是爷爷的左孩子{Node* uncle = grandfather->_right;//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//调整完之后,更新cur, 判断是否还需要调整cur = grandfather;parent = cur->_parent;}//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色else{if (cur == parent->_left) //单纯左边高{RotateR(grandfather); //右单旋//变色parent->_col = BLACK;grandfather->_col = RED;}else //cur是parent的右边{RotateLR(grandfather); //左右双旋//变色cur->_col = BLACK;grandfather->_col = RED;}break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整}}else //父亲是爷爷的右孩子{Node* uncle = grandfather->_left;//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//调整完之后,更新cur, 判断是否还需要调整cur = grandfather;parent = cur->_parent;}//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色else{if (cur == parent->_left) //cur是parent的左边,进行右单旋{RotateRL(grandfather); //右左单旋//变色cur->_col = BLACK;grandfather->_col = RED;}else //cur是parent的右边{RotateL(grandfather); //左单旋//变色grandfather->_col = RED;parent->_col = BLACK;}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 (parentParent == nullptr){_root = subR;_root->_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 (parentParent == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}//左右双旋void RotateLR(Node* parent){RotateL(parent->_left);RotateR(parent);}//右左双旋void RotateRL(Node* parent){RotateR(parent->_right);RotateL(parent);}//判断是否是红黑树bool IsRBTree(){if (_root == nullptr) return true;if (_root->_col == RED){cout << "根节点为红色" << endl;return false;}//以最左路径的黑节点的个数作为参考值Node* cur = _root;int BlackCount = 0;while (cur){if (cur->_col == BLACK)BlackCount++;cur = cur->_left;}//调用子函数判断红黑树int count = 0;return _IsRBTree(_root, count, BlackCount);}
private:bool _IsRBTree(Node* root, int count, int BlackCount){if (root == nullptr){if (count != BlackCount) {cout << "黑色节点的个数不相等" << endl;return false;}return true;}//判断节点的孩子节点颜色不好判断, 转化成判断父亲节点颜色if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红节点" << endl;return false;}if (root->_col == BLACK)count++;//递归子问题return _IsRBTree(root->_left, count, BlackCount)&& _IsRBTree(root->_right, count, BlackCount);}private:Node* _root = nullptr;
};

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

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

相关文章

1-2亿条数据需要缓存,如何合理设计存储

单机是不可能的&#xff0c;肯定是分布式存储 数据怎么落&#xff1f; 一般业界有三种解决方案 哈希取余分区 一致性哈希算法分区 哈希槽分区&#xff08;大厂专用&#xff0c;都在用&#xff09;最终的选择

信息检索(37):Query-as-context Pre-training for Dense Passage Retrieval

Query-as-context Pre-training for Dense Passage Retrieval 标题摘要1 引言2 初步&#xff1a;上下文监督预训练2.1 coCondenser2.2 CoT-MAE 3 查询即上下文预训练3.1 预训练3.2 微调 4 实验4.1 预训练4.2 微调4.3 基线4.4 主要结果4.5 域外评估 5 分析5.1 生成的查询数量的影…

面向电商家居行业3D室内场景合成中的空间感知

本文主要介绍了3D场景合成技术在电商领域&#xff0c;尤其是家居家装行业的应用。它解释了如何使用3D场景合成创建逼真的室内设计&#xff0c;让消费者能够交互式地查看和体验产品&#xff0c;提高购物的趣味性和效率。文章提到了两种主要的3D室内场景生成算法&#xff1a;传统…

RN开发搬砖经验之—分析与定位图片文件被清空的原因

如题 最近工作上处理的一个BUG&#xff0c;先讲下结论/原因&#xff0c;然后再分享该主题相关的东西 结论是&#xff1a;copy图片文件时&#xff0c;源路径与目标路径相同—输入输出流同时操作同一个文件&#xff0c;导致文件清空了&#xff01; copy文件的主要源码如下&#…

OFDM802.11a的FPGA实现(十二)使用FFT IP核添加循环前缀

原文链接&#xff08;相关文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA实现 目录 1.前言2.循环前缀3.硬件实现4.ModelSim仿真 1.前言 为了能够消除传输过程当中的符号间干扰&#xff0c;在IFFT处理完毕之后还要加上循环前缀。 2.循环前缀 实际通信信道中,由于接…

深度学习设计模式之单例模式

一、单例模式简介 一个类只能有一个实例&#xff0c;提供该实例的全局访问点&#xff1b; 二、单例模式实现步骤 使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。 私有构造函数保证了不能通过构造函数来创建对象实例&#xff0c;只能通过公有静态函数返…

【计算机网络】物理层 通信基础、奈氏准则、香农公式 习题2

下列说法中正确的是( )。 A. 信道与通信电路类似&#xff0c;一条可通信的电路往往包含一个信道 B.调制是指把模拟数据转换为数字信号的过程 C. 信息传输速率是指通信信道上每秒传输的码元数 D.在数值上&#xff0c;波特率等于比特率与每符号所含的比特数的比值 信息传输速率&a…

65-CPLD电路设计(安路为例)

视频链接 CPLD电路设计&#xff08;安路为例&#xff09;01_哔哩哔哩_bilibili CPLD电路设计&#xff08;以安路为例&#xff09; 浅谈板级电源设计的三种方法_哔哩哔哩_bilibili 参考【浅谈板级电源设计的三种方法】 FPGA板级硬件实战S1&#xff5e;7课 实战Power2-电…

^_^填坑备忘^_^C#自动化编程实现STK+Exata对卫星互联网星座进行网络仿真

C#实际选择 STK11版本 or STK12版本的问题备注。 【C#自动化客户端调用STK时&#xff0c;实际选择 STK11版本 or STK12版本 的调试运行备注】 以下代码“更新并重新打包备份为”〔testSTKQualNetInterface备份08.1_★避坑★【种子卫星&#xff1a;天线直接安装在卫星上&#…

centos7.9系统安全加固

1、限制用户登陆 vim /etc/hosts.deny&#xff0c;若禁止192.168.0.158对服务器进行ssh的登陆&#xff0c;添加如下内容 sshd : 192.168.0.158 添加完毕后就生效了&#xff0c;直接用192.168.0.158访问主机&#xff0c;就无法连接了&#xff0c;显示 Connection closing...Soc…

系统需求开发和管理指南(软件标准文件Word)

1.需求获取的方式 2.需求分析的准则 3.需求分析的方法 4.需求开发考虑的方面 5.需求确认的方法 6.需求优先级的设定 7.需求文档编制规范要求 软件全文档获取方式一&#xff1a;本文末个人名片直接获取。 软件全文档获取二&#xff1a;软件项目开发全套文档下载_软件项目文档-C…

Xilinx FPGA底层逻辑资源简介(1):关于LC,CLB,SLICE,LUT,FF的概念

LC&#xff1a;Logic Cell 逻辑单元 Logic Cell是Xilinx定义的一种标准&#xff0c;用于定义不同系列器件的大小。对于7系列芯片&#xff0c;通常在名字中就已经体现了LC的大小&#xff0c;在UG474中原话为&#xff1a; 对于7a75t芯片&#xff0c;LC的大小为75K&#xff0c;6输…

VPN方案和特点

VPN方案和特点 VPN&#xff0c;或者称为虚拟专用网络&#xff0c;是一种保护你的在线安全和隐私的技术。它可以创建一个加密的连接&#xff0c;使你的在线活动对其他人不可见。以下是一些常见的VPN协议和它们的特点&#xff1a; 开放VPN (OpenVPN)&#xff1a;这是一种极为可…

阿里云和AWS负载均衡服务对比分析

在云计算时代,负载均衡作为一种关键的网络基础设施,承担着在多个服务器之间分发网络流量的重要任务。作为全球两大主要的云服务提供商,阿里云和Amazon Web Services(AWS)都提供了强大的负载均衡解决方案。本文将从性能、功能、可用性和成本等方面对两者进行对比分析。我们九河云…

600/天,海外项目值班,接不接?

朋友介绍了一个海外项目&#xff0c;广告系统短期维护&#xff0c;刚上线需要维护14天也就是2个星期&#xff0c;费用单价600/天&#xff0c;主要工作内容&#xff1a;北京晚上12点-早上8点值班&#xff0c;如果有问题及时响应并修复。 如果我年轻10岁&#xff0c;这个项目我倒…

【牛客】SQL202 找出所有员工当前薪水salary情况

1、描述 有一个薪水表&#xff0c;salaries简况如下&#xff1a; 请你找出所有员工具体的薪水salary情况&#xff0c;对于相同的薪水只显示一次,并按照逆序显示&#xff0c;以上例子输出如下&#xff1a; 2、题目建表 drop table if exists salaries ; CREATE TABLE sala…

【全开源】Java知识付费教育付费资源付费平台公众号小程序源码

特色功能&#xff1a; 多样化的内容呈现&#xff1a;资源付费平台小程序支持图文、音视频、直播等多种形式的内容呈现&#xff0c;为用户提供了丰富的学习体验。直播课程&#xff1a;专家或讲师可以通过小程序进行在线授课&#xff0c;与用户实时互动&#xff0c;增强了学习的…

【Linux】AlmaLinux 9.4版本发布

AlmaLinux 9.4 正式版发布&#xff0c;该版本基于 Redhat Enterprise 9.4&#xff0c;内核版本号&#xff1a; 5.14.0-427.13.1.el9_4.x86_64 相对于Rocky Linux&#xff0c; AlmaLinux更加的稳定&#xff0c;生产环境建议使用AlmaLinux来替代CentOS 7.x AlmaLinux 9.4版本系统…

【Python-爬虫】

Python-爬虫 ■ 爬虫分类■ 1. 通用网络爬虫&#xff1a;&#xff08;搜索引擎使用&#xff0c;遵守robots协议&#xff09;■ robots协议&#xff08;君子协议&#xff09; ■ 2. 聚集网络爬虫&#xff1a;自己写的爬虫程序 ■ urllib.request&#xff08;要导入的模块&#x…

风丘方案助力车企升级 解决“国六”标准新难题

一 背景 尾气排放指标是衡量汽车质量和品质的主要指标之一&#xff0c;且汽车的尾气排放必须达到相应的标准才准许出厂&#xff0c;因此&#xff0c;对汽车排放的尾气进行检测是汽车生产过程的重要环节。汽车尾气检测过程是在排放实验室里进行的&#xff0c;这需要模拟汽车实际…