数据结构 —— 红黑树

ops/2025/2/2 6:02:39/

目录

1. 初识红黑树

1.1 红黑树的概念

 1.2 红⿊树的规则

1.3 红黑树如何确保最长路径不超过最短路径的2倍

1.4 红黑树的效率:O(logN)

2. 红黑树的实现 

2.1 红黑树的基础结构框架

2.2 红黑树的插⼊

2.2.1 情况1:变色

2.2.2 情况2:单旋+变色

2.2.3 情况3:双旋+变色  

2.3 验证一棵树是否为红黑树

2.4 代码汇总



1. 初识红黑树

1.1 红黑树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的


 1.2 红⿊树的规则

1. 每个结点不是红⾊就是⿊⾊

     
2. 根结点是⿊⾊的

   
3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊结点

     
4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点
   

 


1.3 红黑树如何确保最长路径不超过最短路径的2倍

1. 由规则4可知,从根到NULL结点的每条路径都有相同数量的⿊⾊结点,所以极端场景下,最短路径就就是全是⿊⾊结点的路径,假设最短路径⻓度为hb

  

hb:从某个节点出发(不包含该节点)到达一个叶子节点的任意一条简单路径上黑色节点的个数

    
2. 由规则2和规则3可知,任意⼀条路径不会有连续的红⾊结点,所以极端场景下,最⻓的路径就是⼀⿊⼀红间隔组成,那么最⻓路径的⻓度为2*bh

     
3. 综合红⿊树的4点规则⽽⾔,理论上的全⿊最短路径和⼀⿊⼀红的最⻓路径并不是在每棵红⿊树都存在的。假设任意⼀条从根到NULL结点路径的⻓度为x,那么bh <= h <= 2*bh


1.4 红黑树的效率:O(logN)

红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜 ⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格


2. 红黑树的实现 

2.1 红黑树的基础结构框架

#pragma once
//定义一个枚举
enum Colour
{//枚举里面定义颜色RED,BLACK
};//默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{//这⾥更新控制平衡也要加⼊parent指针pair<K, V> _kv;//也要实现成三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv) :_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:
private:Node* _root = nullptr;
};

2.2 红黑树的插⼊

1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则

    

2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须是红⾊结点,因为⾮空树插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的

     

3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束

     

4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种 情况分别处理

    

说明:下图中假设我们把新增结点标识为c(cur),c的⽗亲标识为p(parent),p的⽗亲标识为 g(grandfather),p的兄弟标识为u(uncle)


2.2.1 情况1:变色

新增结点标识为c(cur),c的⽗亲标识为p(parent),p的⽗亲标识为 g(grandfather),p的兄弟标识为u(uncle)

c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红,再在把g当做新的c,继续往上更新

    
分析:因为p和u都是红⾊,g是⿊⾊,把p和u变⿊,左边⼦树路径各增加⼀个⿊⾊结点,g再变红,相当于保持g所在⼦树的⿊⾊结点的数量不变,同时解决了c和p连续红⾊结点的问题,需要继续往上更新因为,g是红⾊

   

如果g的⽗亲还是红⾊,那么就还需要继续处理;如果g的⽗亲是⿊⾊,则处理结束了;如果g就是整棵树的根,再把g变回⿊⾊

   

 情况1只变⾊,不旋转。所以⽆论c是p的左还是右,p是g的左还是右,都是上⾯的变⾊处理⽅式


2.2.2 情况2:单旋+变色

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点

    

u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊

如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则

 

如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则


2.2.3 情况3:双旋+变色  

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点

    

u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊

如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则

如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则 


2.3 验证一棵树是否为红黑树

规则1:枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊

   

规则2:直接检查根即可

   

规则3:前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检 查⽗亲的颜⾊就⽅便多了

    

规则4:前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到 ⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可 

bool Check(Node* root, int blacknum, const int retnum)
{if (root == nullptr){if (blacknum != retnum){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, retnum)&& Check(root->_right, blacknum, retnum);
}
bool IsBalance()
{if (_root == nullptr){return true;}if (_root->_col == BLACK){return false;}int retnum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){retnum++;}cur = cur->_left;}return Check(_root, 0, retnum);
}

2.4 代码汇总

#pragma once
#pragma once
//定义一个枚举
enum Colour
{//枚举里面定义颜色RED,BLACK
};//默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{//这⾥更新控制平衡也要加⼊parent指针pair<K, V> _kv;//也要实现成三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;//颜色RBTreeNode(const pair<K, V>& kv) :_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};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);cur->_col = RED;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 (parent == grandfather->_left){//	 g//p		u// //说明u在g的右边Node* uncle = grandfather->_right;//如果u存在并且u的颜色为红色if (uncle && uncle->_col == RED){//将u和p的颜色改为黑色,g的颜色改为红色uncle->_col = parent->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;//p寻找g的p节点}else{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{//   g// u   pNode* uncle = grandfather->_left;// 叔叔存在且为红,-》变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色//   g// u   p//       cif (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;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}private:Node* _root = nullptr;
};

完结撒花~


http://www.ppmy.cn/ops/131498.html

相关文章

什么是贪心算法

贪心算法&#xff08;Greedy Algorithm&#xff09;是一种逐步构建解决方案的方法&#xff0c;在每一步选择中都作出局部最优的选择&#xff0c;希望最终能够获得全局最优解。贪心算法的核心思想是贪心选择性质&#xff0c;即每次选择当前看来最好的解&#xff0c;不考虑未来可…

【用Rust写CAD】第一章 环境搭建

文章目录 1、搭建C/C编译环境2、安装Rust3、配置 PATH 环境变量4、验证安装结果5、安装编辑工具 1、搭建C/C编译环境 Rust 的编译工具依赖 C 语言的编译工具&#xff0c;这意味着你的电脑上至少已经存在一个 C 语言的编译环境。如果你使用的是 Linux 系统&#xff0c;往往已经…

C++ 之类和对象

类的定义 类是C中创建对象的蓝图&#xff0c;它封装了数据&#xff08;成员变量&#xff09;和行为&#xff08;成员函数&#xff09;。 class MyClass { public:// 公有成员void publicMethod();private:// 私有成员int privateVar; protected:// 保护成员int protectedVar;…

【教程】Git 标准工作流

目录 前言建仓&#xff0c;拉仓&#xff0c;关联仓库修改代码更新本地仓库&#xff0c;并解决冲突提交代码&#xff0c;合入代码其他常用 Git 工作流删除本地仓库和远程仓库中的文件日志打印commit 相关 前言 Git 是日常开发中常用的版本控制工具&#xff0c;配合代码托管仓库…

Spring Boot 中的拦截器 (HandlerInterceptor) 使用方案

Spring Boot 中的 拦截器 (HandlerInterceptor) 使用方案 引言 在 Spring Boot 中&#xff0c;HandlerInterceptor 是一个强大的工具&#xff0c;可以帮助我们在请求处理的生命周期中插入自定义逻辑。它允许我们在请求被处理之前和之后执行一些操作&#xff0c;例如请求验证、…

电机学习-SPWM原理及其MATLAB模型

SPWM原理及其MATLAB模型 一、SPWM原理二、基于零序分量注入的SPWM三、MATLAB模型 一、SPWM原理 SPWM其实是相电压的控制方式&#xff0c;定义三相正弦相电压的表达式&#xff1a; { V a m V m sin ⁡ ω t V b m V m sin ⁡ ( ω t − 2 3 π ) V c m V m sin ⁡ ( ω t 2…

图像识别中的高斯滤波和椒盐滤波的适用场景与不同实现

高斯滤波和椒盐滤波是图像处理中常用的滤波方法&#xff0c;用于去除图像中的噪声或者平滑图像。它们适用于不同的场景&#xff0c;并且有不同的实现方式。 高斯滤波适用于以下场景&#xff1a; 去除图像中的高斯噪声&#xff1a;高斯噪声是一种随机噪声&#xff0c;它的幅度…

前端性能优化2

常见问题&#xff1a; 首屏速度&#xff0c;白屏时间等。操作速度以及渲染速度。 一打开网页时白屏&#xff0c;是因为还需要从服务器那边请求HTML、JS这些过来&#xff0c;加载下来后才有东西渲染 首屏速度参考两方面&#xff1a;白屏时间&#xff08;资源加载时间、首屏js执…