红黑树(数据结构篇)

数据结构之红黑树

红黑树(RB-tree)

概念

  • 红黑树是AVL树的变种,它是每一个节点或者着成红色,或者着成黑色的一棵二叉查找树。
  • 对红黑树的操作在最坏情形下花费O(logN)时间,它的插入操作使用的是非递归形式实现
  • 红黑树的高度最多是2log(N+1)

特性

  • 红黑树是具有着色性质二叉查找树,也就意味着树的节点值是有序的,且每个节点只可能是红色或者黑色
  • 红黑树的根是黑色的
  • 如果一个节点是红色的,那么它的子节点必须是黑色的
  • 一个节点到一个空指针每一条路径必须包含相同数目的黑色节点

自顶向下插入操作

  1. 如果使用自底向上插入的话还需要进行逐步递归是他们保证满足红黑树特性,效率就降低了。

  2. X为新插入节点(在下面的第三操作中为当前节点),P为X的父节点,G为P的父节点(也就是X的祖父节点),GP为G的父节点(也就是P的祖父节点,X的曾祖父节点)

  3. 因为红黑树是一颗二叉查找树,因此在插入时需要查找要插入的值的正确位置,在这个查找路径中,如果遇到节点(X)为黑色而子节点全部为红色,我们就进行翻转操作,也就是将该节点(X)着成红色,子节点全部着成黑色翻转后:

    如果翻转后发现P和X节点都是红色就需要根据树的结构进行旋转操作

    1. 如果X,P,G形成"一字形",则对P的父节点G(也就是X的祖父节点)与P进行单旋转,并将新根也就是P着成黑色,新根的子节点都着成红色
    2. 如果X,P,G形成"之字形",则对G与X节点进行双旋转,并将新根着成黑色(也就是X节点),然后将新根的子节点着成红色
  4. 如果该节点(X)是黑色则继续将X下降,直到找到红色节点继续翻转或者找到指定插入位置,找到指定位置也就是当前节点位置X就进行插入,新节点也是红色,需要重新判断其父节点是否为红色,为红色又需要进行翻转操作来调整

自顶向下删除操作

  1. 自顶向下删除也需要保证红黑树的性质,插入是插入一片红色的叶子节点,那么反过来我们删除一个红色叶子节点就不会破坏红黑树性质自顶向下插入的翻转操作是将红色节点减少,并将红色节点上浮,因为删除是插入的逆过程,因此删除的翻转操作就是要将树中的红色节点增多,并将红色节点下沉,这样我们删除红色叶子节点的概率更大,并且不会破坏红黑树性质

  2. 删除操作一共有5种情况需要解决

    1. 删除节点cur跟其兄弟节点s原本颜色为黑色父亲节点p为红色
    2. s的两个儿子都是红色,这样双旋转和单旋转都可以,这里优先选择ps单选转调整,情况1-case4
    3. s的左儿子为红色,需要ps.l双旋转调整(s.l为s的左儿子),情况2-case1
    4. s的右儿子为红色,需要ps单旋转调整,情况3-case2
    5. s有两个黑色儿子,直接cur,p,s颜色翻转操作调整,情况4-case3
    6. p和cur为黑色s为红色,需要交换sp节点的颜色,并且sp单旋转调整,情况5-case5
    7. cur为红色,可以继续将cur下降,也就是当前cur指向原本cur的子节点,如果为红色继续下降,如果为黑色就判断是否需要操作
  3. tomove指向要删除节点也就是目标节点,而p指向真正要删除的叶子节点,cur则while循环完后则是指向nil节点,因为将tomove标记完,就进行cur和p就查找tomove右子树的最小值节点进行删除,而while循环终止条件为cur==nil情况,因此p指向真正要删除的节点

  4. 找到tomove和p后,将tomove的data等于p的data,将p删除,因为p为叶子节点,将p的父节点指向nil。

image 情况2-case1

image 情况3-case2

image 情况4-case3

image-20240502001308851 情况1-case4

image 情况5-case5

计算红黑树层数

  • 需要对log2(树中总共节点数+1)向上取整

代码:

int Height(const int count){return std::ceil(std::log2(count+1));
}

代码实现

#include <iostream>
#include <queue>
#include <math.h>
#include <limits.h>
using namespace std;typedef enum {red,black} colortype;struct RBNode{int data;RBNode *left,*right,*parent;colortype color;   //颜色RBNode(const int val,RBNode* l,RBNode* r,RBNode* p,colortype c=red):data(val),left(l),right(r),parent(p),color(c){};
};class RBtree{
public:RBtree(){nil=new RBNode(INT_MAX, nullptr, nullptr, nullptr,black);root= nullptr;t=new RBNode(INT_MIN,nil,nil,nil,black);size=0;}~RBtree(){clear();delete t;delete nil;};void insert(const int val);      //插入操作void del(const int val);       //删除操作RBNode* find(const int val);   //查找操作void print();     //打印操作,层序遍历//清空操作void clear(){clear(root);root= nullptr;t->right=nil;size=0;}protected:void overturnred(const int val,RBNode* &cur);    //翻转操作,将当前节点变成红色,子节点变成黑色void overturnblack(int val,RBNode* &cur);   //翻转操作,将当前节点变成黑色,子节点变成红色RBNode* SingleRotatewithleft(RBNode* &k1);RBNode* SingleRotatewithright(RBNode* &k1);RBNode* Rotate(const int val,RBNode* &k1){if(val<k1->data){return k1->left=val<k1->left->data? SingleRotatewithleft(k1->left): SingleRotatewithright(k1->left);}else{return k1->right=val<k1->right->data? SingleRotatewithleft(k1->right): SingleRotatewithright(k1->right);}}void clear(RBNode* &rt);// 计算红黑树层数int Height(int nodeCount) {// 红黑树的层数为 log2(nodeCount+1)return (int)std::ceil(std::log2(nodeCount+1));}
private:RBNode* root;RBNode* nil;   //空节点,color为黑色RBNode* t;  //根标记,用于删除操作的便捷int size;
};RBNode* RBtree::SingleRotatewithleft(RBNode *&k1) {RBNode* k2;k2=k1->left;k1->left=k2->right;k2->right=k1;return k2;
}RBNode* RBtree::SingleRotatewithright(RBNode *&k1) {RBNode* k2;k2=k1->right;k1->right=k2->left;k2->left=k1;return k2;
}//翻转操作
void RBtree::overturnred(const int val,RBNode* &cur) {cur->color=red;cur->left->color=cur->right->color=black;RBNode* p=cur->parent;if(p->color==red){RBNode* g=p->parent;g->color=red;if((val<g->data)!=(val<p->data)){     //双旋转p= Rotate(val,g);}cur= Rotate(val,g->parent);cur->color=black;}root->color=black;
}//插入操作
void RBtree::insert(const int val) {if(root== nullptr){root=new RBNode(val,nil,nil, t,black);t->right=root;size++;return;}RBNode *cur,*p;cur=p=root;while (cur!=nil){p=cur;if(cur->left->color==red&&cur->right->color==red){overturnred(val,cur);}cur=val<p->data?p->left:p->right;}if(cur!=nil){return;}cur=new RBNode(val, nil, nil, p);if(val<p->data){p->left=cur;}else{p->right=cur;}overturnred(val,cur);size++;
}void RBtree::overturnblack(int val, RBNode *&cur) {cur->color=red;RBNode* p=cur->parent;RBNode* s=val<p->data?p->left:p->right;//case4:要删除节点cur跟其兄弟节点s原本颜色为黑色,父亲节点p为红色,s的两个儿子都是红色,这样双旋转和单旋转都可以,这里优先选择ps单选转//case2:要删除节点cur跟其兄弟节点s原本颜色为黑色,父亲节点p为红色,s的右儿子为红色情况,需要ps单旋转调整if(s->right->color==red){val=s->right->data;}//case1:要删除节点cur跟其兄弟节点s原本颜色为黑色,父亲节点p为红色,s的左儿子为红色的情况,需要ps.l双旋转调整else if(s->left->color==red){val=s->left->data;}//case3:要删除节点cur跟其兄弟节点s原本颜色为黑色,父亲节点p为红色,s有两个黑儿子(nil节点也是黑色),直接将颜色翻转即可else{//翻转操作if(s!=nil){s->color=red;}p->color=black;return;}if((val<s->data)!=(val<p->data)){Rotate(val,p);}RBNode* g=p->parent;Rotate(val,g);//将调整完的cur的新祖父也就是s或者s的左儿子变成红色,也就是删除完cur后将颜色调整到之前cur在翻转前的情况g->color=red;g->left->color=g->right->color=black;
}void RBtree::del(const int val) {RBNode* tomove=nil;  //找到删除节点RBNode *g,*p,*s,*cur;g=p=t,s=t->left,cur=root;while (cur!=nil){//翻转颜色if(cur->left->color==black&&cur->right->color==black){overturnblack(val,cur);}else{g=p;p=cur;if(val<p->data){cur=p->left,s=p->right;}else{tomove=cur,cur=p->right,s=p->left;}//case5:此时肯定p和cur都为黑色,因为如果p为红色早就翻转了,s肯定是红色,将s变成黑色,p变为红色,sp单旋转调整if(cur->color==black){s->color=black;p->color=red;//单旋转完,cur新祖父变为s,将s重新更改g= Rotate(val,g);s=val<p->data?p->left:p->right;//调整完该情况就重新检查上述操作continue;}//else,cur一定为红色,则可以直接继续将cur继续下降}g=p;p=cur;if(val<p->data){cur=p->left,s=p->right;}else{tomove=cur,cur=p->right,s=p->left;}}root->color=black;   //保证红黑树性质2不被破坏,也就是根一定为黑色//判断是否找到真正要删除的节点,如果找不到就退出if(tomove==nil&&tomove->data!=val){cout<<"未找到要删除对应值的节点";return;}//tomove是要删除的节点,而p指向的是真正要删除的节点tomove->data=p->data;if(g->left==p) g->left=nil;else g->right=nil;delete p;size--;
}RBNode* RBtree::find(const int val) {if(root!= nullptr){RBNode* cur=root;while (cur!=nil){if(cur->data==val) return cur;cur=val<cur->data?cur->left:cur->right;}if(cur==nil){cout<<"树中没有指定值节点"<<endl;}}return root;
}void RBtree::print() {if(root== nullptr){cout<<"树为空"<<endl;return;}queue<RBNode*>q;q.push(root);int cnt=1;int ans=0;int h= Height(size);while (!q.empty()){if(ans==h+1) break;RBNode* cur=q.front();q.pop();if(cur== nullptr){cout<<"null"<<" ";continue;}q.push(cur->left);q.push(cur->right);if(cur->color==red){cout<<"\033[31m"<<cur->data<<"\033[0m"<<" ";}else if(cur==nil) cout<<"nil"<<" ";else cout<<cur->data<<" ";if(cnt==pow(2,ans)){cout<<endl;cnt=0,ans++;}cnt++;}return;
}void RBtree::clear(RBNode* &rt) {if(rt!=nil){clear(rt->left);clear(rt->right);delete rt;rt=nil;}return;
}int main() {RBtree rBtree;rBtree.insert(30);rBtree.insert(15);rBtree.insert(65);rBtree.insert(10);rBtree.insert(20);rBtree.insert(5);rBtree.insert(60);rBtree.insert(70);rBtree.insert(50);rBtree.insert(64);rBtree.insert(66);rBtree.insert(85);rBtree.insert(40);rBtree.insert(55);rBtree.insert(63);rBtree.insert(80);rBtree.insert(90);rBtree.insert(45);rBtree.del(65);rBtree.del(50);rBtree.del(30);rBtree.print();return 0;
}

尾言

完整版笔记也就是数据结构与算法专栏完整版可到我的博客进行查看,或者在github库中自取(包含源代码)

  • 博客1: codebooks.xyz
  • 博客2:moonfordream.github.io
  • github项目地址:Data-Structure-and-Algorithms

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

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

相关文章

thread model线程模型

Concurrency并发 vs. Parallelism执行 Many-to-One GNU Portable Threads 多对一即多个用户线程映射到单个内核线程 该模式用的少 一个线程阻塞会导致所有线程阻塞 多线程可能无法在多核系统上并行运行&#xff0c;因为同一时间内核中可能只有一个线程 Concurrent executio…

探索FlowUs息流:个人和团队知识管理解决方案|FlowUs稳定保障你的笔记安全无忧

FlowUs息流&#xff1a;稳定运营保障你的笔记安全无忧 在知识管理工具的选择上&#xff0c;稳定性是用户最关心的问题之一。FlowUs息流以其稳定的运营记录&#xff0c;为用户提供了一个可靠的工作环境。我们深知&#xff0c;一个知识管理平台的稳定性直接影响到团队的生产力和…

3ds MAX 2024版资源包下载分享 3ds Max三维建模软件资源包下载安装

3DSMAX凭借其强大的功能和广泛的应用领域&#xff0c;吸引了无数创作者的青睐。 在游戏制作领域&#xff0c;3DSMAX展现出了无可比拟的优势。从细腻的角色建模到宏大的场景搭建&#xff0c;再到逼真的动画效果和渲染&#xff0c;它都能轻松应对&#xff0c;为游戏世界注入了生动…

“开放”的大模型到底有多“开放”?!

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…

java面向对象(上)

一.面向对象与面向过程 1.面向过程 面向过程(procedure Oriented Programming),简称POP,主要思想就是将问题分解成一个个步骤去解决,把这个步骤称为函数. 典型语言:C语言 优点:可以大大简化代码 缺点:当代码量过大时,不方便维护 2.面向对象 面向对象(Object Oriented Pr…

Harbor 源码编译arm版本镜像

1. 先准备一个国外的arm服务器&#xff0c;&#xff08;使用国内的也行&#xff0c;只是有时候下载依赖会断&#xff0c;需要科学上网&#xff09; 2. git clone harbor的源码包&#xff0c;切换到要编译的分支 3. 修改harbor/Makefile Makefile里面的这些字段设置为true&…

四川财谷通信息技术抖音小店信誉之选,购物新体验

在当今数字化浪潮的推动下&#xff0c;电商平台的兴起为人们的生活带来了极大的便利。而在众多电商平台中&#xff0c;抖音小店以其独特的社交属性和便捷的购物体验&#xff0c;逐渐赢得了消费者的青睐。四川财谷通信息技术有限公司旗下的抖音小店&#xff0c;更是凭借其可靠的…

记录SpringBoot启动报错解决

记录SpringBoot启动报错解决 报错现场 Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class Action: Consider the following:If you want an embedde…

33.获取入口点

上一个内容&#xff1a;32.双击列表启动目标游戏 前置知识 25.入口点注入&#xff08;查看pe头&#xff09;、32.双击列表启动目标游戏 以它的代码为基础进行修改 效果图&#xff1a; 代码实现&#xff1a;原理通过读文件流的方式把文件加载到内存中然后解析pe结构 void CWnd…

uniapp 实人认证

首先Dcloud创建云服务空间&#xff0c;开启一键登录并充值 下一步 1. 右键项目 》 创建uniCloud云开发环境 》右键uniCloud》关联云服务空间 2. cloudfunctions右键 新建云函数&#xff0c;任意命名&#xff08;例&#xff1a;veify&#xff09;&#xff0c;然后右键项目》管…

Python 基础:异常

目录 一、异常概念二、处理异常2.1 抛出异常2.2 使用 try-except 代码块2.3 使用 try-except-else 代码块2.4 静默失败 三、总结 遇到看不明白的地方&#xff0c;欢迎在评论中留言呐&#xff0c;一起讨论&#xff0c;一起进步&#xff01; 本文参考&#xff1a;《Python编程&a…

Vite文件目录结构介绍

我们通过命令create-vite shop-admin基于Vite创建vue3项目后&#xff0c;其默认的文件目录结构如下&#xff1a; shop-admin ├─ index.html ├─ package-lock.json ├─ package.json ├─ public │ └─ vite.svg ├─ src │ ├─ App.vue │ ├─ assets │ │ └…

C++多重继承,虚基类与友元

一.多重继承 就是一个类继承多个基类&#xff1b; class <派生类名>&#xff1a;<派生方式1><基类名1>,<派生方式n><基类名n> class Derived:public:Base1,public:Base2 上述形式&#xff1a;基类之间由逗号隔开&#xff0c;且必须指明继承方式…

【Python绘画】气球祝福节日快乐

本文收录于 《一起学Python趣味编程》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、代码示例三、知识点梳理四、总结 一、前言 本文介绍如何使用Python的海龟画图工具turtle&#…

示例:WPF中DataGrid简单设置合并列头

一、目的&#xff1a;应用DataGridTemplateColumn列模板&#xff0c;去拆分列头和单元格布局的方式设置列头合并样式 二、实现 效果如下 三、环境 VS2022 四、示例 应用DataGridTemplateColumn自定义列头信息和单元格信息 <DataGrid AutoGenerateColumns"False"…

【机器学习】线性回归:从基础到实践的深度解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 线性回归&#xff1a;从基础到实践的深度解析引言一、线性回归基础1.1 定义与目…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 机器人搬砖(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

怎么缩小pdf文件大小

在数字化时代&#xff0c;pdf文件已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着pdf文件内容的增多&#xff0c;其大小也会相应增加&#xff0c;这给文件的传输、存储和共享带来了诸多不便。因此&#xff0c;如何有效地压缩pdf文件大小&#xff0c;成为了…

【Kafka】Kafka生产者数据重复、数据有序、数据乱序-07

【Kafka】Kafka生产者数据重复、数据有序、数据乱序-07 1. 数据重复1.1 数据传递语义1.2 幂等性1.2.1 如何开启幂等性1.2.2 同一个消息&#xff0c;多个分区都会存在吗&#xff1f; 1.3 事务1.3.1 Kafka 事务原理1.3.2 Kafka事务的作用和意义作用具体应用场景 2. 数据有序3. 数…

Python数据可视化:直方图、核密度估计图、箱线图、累积分布函数图

本文使用数据来源自2023年数学建模国赛C题&#xff0c;以附件1、附件2数据为基础&#xff0c;通过excel的数据透视表等功能重新汇总了一份新的数据表&#xff0c;从中截取了一部分数据为例用于绘制图表。绘制的图表包括一维直方图、一维核密度估计图、二维直方图、二维核密度估…