【C++】:红黑树深度剖析 --- 手撕红黑树!

目录

  • 前言
  • 一,红黑树的概念
  • 二,红黑树的性质
  • 三,红黑树节点的定义
  • 四,红黑树的插入操作
    • 4.1 第一步
    • 4.2 第二步
    • 4.3 插入操作的完整代码
  • 五,红黑树的验证
  • 六,实现红黑树的完整代码
  • 五,红黑树与AVL树的比较

点击跳转至上一篇文章: 【C++】:AVL树的深度解析及其实现

点击跳转至文章:【C++】:二叉树进阶 — 搜索二叉树

前言

上一篇文章介绍了什么是AVL树和AVL树的实现,AVL树也有它的缺点:就是太过追求绝对平衡,比如在插入时要维护其绝对平衡,旋转次数太多,在删除时甚至有可能要一直旋转到根位置,使之性能低下

本篇文章介绍的红黑树也是一种平衡树,是通过改变节点颜色以及旋转操作,使之接近平衡

红黑树比AVL树的用途更加广泛,在一些方面效率甚至要优于AVL树,并且 map/set 的底层封装用的也是红黑树。

一,红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

在这里插入图片描述

二,红黑树的性质

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

三,红黑树节点的定义

红黑树的节点结构与AVL树的大致相同,只是AVL树中有节点的颜色,没有平衡因子。

//枚举颜色
enum Colour
{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;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(BLACK){}
};

四,红黑树的插入操作

首先我们要思考一个问题,插入节点时,到底是插入红色节点还是黑色节点?为什么?

答案:插入红色节点

因为我们知道红黑树最重要的两条性质是第(3)(4)条,通过维护这两条规则使之成为红黑树,而当新插入一个节点时,必定会破坏两条规则之一。

假设插入节点为黑色节点,则所有路径的黑色节点数量均不相同,如何让它们相同将是一个巨大的难题,而插入红色节点(此时一定是作为孩子节点),就破坏规则(3),但是只要根据其父亲和叔叔节点进行适当的变色就可以继续恢复规则(3)。

显而易见,规则(3)(4)就好比"慈父严母",非要选择得罪其中一人,那当然是"慈父"了。

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

4.1 第一步

按照二叉搜索的树规则插入新节点

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}elsereturn false;}cur = new Node(kv);//新增节点,颜色为红色cur->_col = RED;//链接时要判断链接在parent的左还是右if (parent->_kv.first > kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//检测新节点插入后,红黑树的性质是否造到破坏//……}

4.2 第二步

检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

约定:
(1) cur 为当前节点,p为父节点,g为祖父节点,u为叔叔节点

(2) 下面的抽象图中,a/b/c/d/e 表示具有 n 个节点的红黑树,n >=0

(一) 情况一: cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

(二) 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

在这里插入图片描述

下面我们根据 u 的情况用具象图分别解释单旋与双旋操作:

(1) u 不存在,a/b/c/d/e都是空,cur 是新增

p为g的左孩子,cur为p的左孩子,则进行右单旋转,再 p 变黑,g 变红

在这里插入图片描述

相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转,再 p 变黑,g 变红

在这里插入图片描述

p为g的左孩子,cur为p的右孩子,先针对p做左单旋转,再针对 g 做右单旋,cur 变黑,g 变红

在这里插入图片描述

(2) u 存在且为黑

单旋情况

在这里插入图片描述
双旋情况
在这里插入图片描述

4.3 插入操作的完整代码

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}elsereturn false;}cur = new Node(kv);//新增节点,颜色为红色cur->_col = RED;//链接时要判断链接在parent的左还是右if (parent->_kv.first > kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//    g// p     uif (parent == grandfather->_left){//找到u的位置Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//u存在且为红,把p/u变黑,g变红,继续向上调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//u不存在或存在且为黑if (cur == parent->_left){//     g//  p     u    //c//单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//  p     u    //	   c//双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{//    g// u     pNode* uncle = grandfather->_left;//u存在且为红,把p/u变黑,g变红,继续向上调整if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//u不存在或存在且为黑if (cur == parent->_right){//    g// u      p//            c//单旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// u     p//    c//双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}

五,红黑树的验证

红黑树的检测分为两步:

(1) 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
(2) 检测其是否满足红黑树的性质

这里重点检测的是性质(3)与性质(4)。

检测性质(3):只要判断当前节点与其父亲节点是否为连续的红色节点

检测性质(4):先计算出任意一条路径上的黑色节点作为参考值,再用这个参考值与其他路径上的黑色节点数量比较

private://中序遍历void InOrder(){_Inorder(_root);cout << endl;}//判断是否平衡bool IsBalance(){if (_root == nullptr)return true;//检查根节点if (_root->_col == RED)return false;// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++refNum;cur = cur->_left;}return Check(_root, 0, refNum);}private:bool Check(Node* root, int blackNum, const int refNum){//每条路径走到空后与参考值进行比较if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//检查是否存在连续的红色节点if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红色节点" << endl;return false;}//blackNum表示:根节点到当前节点的路径上黑色节点的数量if (root->_col == BLACK)blackNum++;return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}

六,实现红黑树的完整代码

RBTree.h

#pragma once#include <iostream>
#include <assert.h>
using namespace std;//枚举颜色
enum Colour
{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;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(BLACK){}
};template <class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:Node* Find(const pair<K, V>& kv){Node* cur = _root;while (cur){if (cur->_kv.first > kv.first)cur = cur->_left;else if (cur->_kv.first < kv.first)cur = cur->_right;elsereturn cur;}return nullptr;}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}elsereturn false;}cur = new Node(kv);//新增节点,颜色为红色cur->_col = RED;//链接时要判断链接在parent的左还是右if (parent->_kv.first > kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//    g// p     uif (parent == grandfather->_left){//找到u的位置Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//u存在且为红,把p/u变黑,g变红,继续向上调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//u不存在或存在且为黑if (cur == parent->_left){//     g//  p     u    //c//单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//  p     u    //	   c//双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{//    g// u     pNode* uncle = grandfather->_left;//u存在且为红,把p/u变黑,g变红,继续向上调整if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//u不存在或存在且为黑if (cur == parent->_right){//    g// u      p//            c//单旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g// u     p//    c//双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}//中序遍历void InOrder(){_Inorder(_root);cout << endl;}//判断是否平衡bool IsBalance(){if (_root == nullptr)return true;//检查根节点if (_root->_col == RED)return false;// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++refNum;cur = cur->_left;}return Check(_root, 0, refNum);}private://每个节点的位置记录一个值blackNum//blackNum表示:根节点到当前节点的路径上黑色节点的数量bool Check(Node* root, int blackNum, const int refNum){//每条路径走到空后与参考值进行比较if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){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, refNum)&& Check(root->_right, blackNum, refNum);}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;//改变之前记录Node* ppNode = parent->_parent;parent->_parent = subL;//parent为根if (parent == _root){_root = subL;_root->_parent = nullptr;}else{//parent为一颗子树if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;if (subRL)subRL->_parent = parent;parent->_right = subRL;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;//parent为根if (parent == _root){_root = subR;_root->_parent = nullptr;}else{//parent为一颗子树if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}
private:Node* _root = nullptr;
};//测试代码
void Test1()
{//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };int a[] = { 16,3,7,11,9,26,18,14,15 };RBTree<int, int> t;for (auto e : a)t.Insert({ e ,e });t.InOrder();cout << t.IsBalance() << endl;
}

Test.cpp

#include "RBTree.h"int main()
{Test1();return 0;
}

运行结果如下

中序遍历是有序的,说明是搜索二叉树,返回1,说明满足红黑树的性质,是平衡树

在这里插入图片描述

五,红黑树与AVL树的比较

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

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

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

相关文章

python实现盲反卷积算法

python实现盲反卷积算法 盲反卷积算法算法原理算法实现Python实现详细解释优缺点应用领域盲反卷积算法 盲反卷积算法是一种图像复原技术,用于在没有先验知识或仅有有限信息的情况下,估计模糊图像的原始清晰图像和点扩散函数(PSF)。盲反卷积在摄影、医学成像、天文学等领域…

前端数据可视化适配方案汇总

前端数据可视化适配方案汇总 1、前言2、方案一&#xff1a;vw vh2.1 实现效果2.2 实现思路2.3 实现代码2.3.1 css 方案2.3.1.1 sass2.3.1.2 less 2.3.2 js方案2.3.3 图表字体、间距、位移等尺寸自适应 3、scale3.1 实现效果3.2 实现思路3.3 实现代码 4、rem方案4.1 实现思路4.2…

2024暑假友谊赛 2

Problem - 1150B - Codeforces 小C是重度强迫症晚期患者&#xff0c;如果某些图形无法按照他的想法排列&#xff0c;那么他就会迎来他的末日。某天小C来到了心心念念的女神家里&#xff08;绝对不可能是女装大佬&#xff0c;绝对不可能&#xff09;&#xff0c;他发现地砖有两…

【漏洞复现】E-Cology OA——WorkflowServiceXml——SQL注入

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 E-Cology OA协同商务系统是一款面向中大型组织的数字化办公产品…

Mysql数据库第四次作业

mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…

昇思MindSpore学习入门-高阶自动微分

mindspore.ops模块提供的grad和value_and_grad接口可以生成网络模型的梯度。grad计算网络梯度&#xff0c;value_and_grad同时计算网络的正向输出和梯度。本文主要介绍如何使用grad接口的主要功能&#xff0c;包括一阶、二阶求导&#xff0c;单独对输入或网络权重求导&#xff…

7.24 模拟赛总结 [dp 专场] + tarjan

复盘 7:40 开题 看 T1 &#xff0c;妈呀&#xff0c;一上来就数数&#xff1f;盯了几分钟后发现会了&#xff0c;不就是 LCS 计数嘛 继续看&#xff0c;T2 看上去很恶心&#xff0c;线段覆盖&#xff0c;感觉可能是贪心什么的 再看 T3&#xff0c;先想了个 n 2 n^2 n2 的式…

Vue 3 + Vite 项目中安装 Tailwind CSS

官网&#xff1a;安装 - TailwindCSS中文文档 | TailwindCSS中文网 tips&#xff1a;只按照官网的配置可能会导致样式不加载/加载不生效的问题 1、正确安装指令 npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p 自动生成 ​tailwind.config.js​…

【C++】string类(上)

个人主页~ string 一、标准库中的string类1、什么是string类2、string类的常用接口讲解&#xff08;1&#xff09;string类的常见构造&#xff08;2&#xff09;string类的容量操作&#xff08;3&#xff09;string类对象的访问及遍历&#xff08;4&#xff09;string类对象的修…

Java语言程序设计——篇七(2)

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 封装性与多态 封装性与访问修饰符类的访问权限类成员的访问权限 &#x1f320;防止类扩展和方法覆盖实战演练 抽象类实战演练 对象转换实战演练…

lambda表达式,真题示例

Lambda表达式 它使代码更加简洁、易读&#xff0c;函数式编程增强了代码的表达力。常用于对集合的操作&#xff0c;如遍历、过滤、转换等。 Lambda表达式的形式&#xff1a; 参数&#xff0c; 箭头&#xff08;->) 以及一个表达式&#xff1a; (String first, String sec…

Android P Input设备变化监听 Storage设备变化监听

InputManager.java中实现了InputDeviceListener接口&#xff0c;只需要新建一个类 implements InputDeviceListener &#xff0c;并且将类实例化注册给InputManager.getInstance().registerInputDeviceListener即可。 StorageManager同理 StorageManager中会调用StorageEventL…

还手动抄字幕?学会这3个视频转文字方法,轻松提取视频中的字幕!

大家有尝试过考试前极限抱佛脚吗&#xff1f; 在下不才&#xff0c;曾经试过一次&#xff0c;轻松在及格线低空飘过【大家不要学不要学不要学&#xff0c;重要的事情说三遍&#xff01;&#xff01;&#xff01;】 至于我当时究竟是怎么做到的呢&#xff1f;其实这里面有点小…

网络原理_初识

目录 一、局域网LAN 二、广域网WAN 三、网络通信基础 3.1 IP地址 3.2 端口号 3.3 协议 3.4 五元组 3.5 OSI七层模型 3.6 TCP/IP五层模型 3.7 网络设备所在分层 3.8 封装和分用 总结 一、局域网LAN 局域网&#xff0c;即 Local Area Network&#xff0c;Local 即标…

“微软蓝屏”全球宕机,敲响基础软件自主可控警钟

上周五&#xff0c;“微软蓝屏”“感谢微软 喜提假期”等词条冲上热搜&#xff0c;全球百万打工人受此影响&#xff0c;共同见证这一历史性事件。据微软方面发布消息称&#xff0c;旗下Microsoft 365系列服务出现访问中断。随后在全球范围内&#xff0c;包括企业、政府、个人在…

【定积分】

框架 概念&#xff0c;性质定积分计算基本特色变限积分及其导数反常积分&#xff08;广义积分&#xff09;定积分应用面积体积 讲解 1.概念&#xff0c;性质&#xff1a; 定积分就是求出曲线的面积&#xff1b;性质中要注意几个不等式的比较 2.定积分计算&#xff1a; 基本&…

物理机 gogs+jenkins+sonarqube 实现CI/CD

一、部署gogs_0.11.91_linux_amd64.tar.gz gogs官网下载&#xff1a;https://dl.gogs.io/ yum -y install mariadb-serversystemctl start mariadbsystemctl enable mariadbuseradd gittar zxvf gogs_0.11.91_linux_amd64.tar.gzcd gogsmysql -u root -p < scripts/mysql.…

vue3前端开发-小兔鲜项目-登录和非登录状态下的模板适配

vue3前端开发-小兔鲜项目-登录和非登录状态下的模板适配&#xff01;有了上次的内容铺垫&#xff0c;我们可以根据用户的token来判定&#xff0c;到底是显示什么内容了。 1&#xff1a;我们在对应的导航组件内修改完善一下内容即可。 <script setup> import { useUserSt…

svn软件总成全内容

SVN软件总成 概述&#xff1a;本文为经验型文档 目录 D:\安装包\svn软件总成 的目录D:\安装包\svn软件总成\svn-base添加 的目录D:\安装包\svn软件总成\tools 的目录D:\安装包\svn软件总成\tools\sqlite-tools-win32-x86-3360000 的目录D:\安装包\svn软件总成\安装包-----bt lo…

C#调用OpenCvSharp实现图像的角点检测

角点检测用于获取图像特征&#xff0c;以支撑运动检测、目标识别、图像匹配等方面的应用。常用的角点检测算法包括Kitchen-Rosenfeld算法、Harris算法、KLT算法、SUSAN算法等&#xff0c;本文学习并测试Harris角点检测算法。   关于Harris算法的数学原理请见参考文献1的第18、…