红黑树(数据结构篇)

news2024/10/6 18:20:48

数据结构之红黑树

红黑树(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.coloradmin.cn/o/1842025.html

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

相关文章

1分钟解决海康威视摄像头网页预览失败显示纯灰色问题

先用老IE浏览器登录&#xff0c;会提醒下载插件 下载这个Web的插件安装后&#xff0c;重开网页就都能看了

MacBook Air M3的电脑怎么样 新买MacBook Air提示内存不足 苹果电脑内存不够用怎么办

Apple的MacBook Air系列一直是轻薄便携笔记本电脑的代表&#xff0c;最新推出的MacBook Air M3因其出色的性能和优雅的设计而受到广泛关注。然而&#xff0c;许多用户在购买全新的MacBook Air后反应他们遇到了内存不足的提示。 本文将探讨MacBook Air M3的电脑怎么样&#xff0…

k8s自动补全工具和UI管理界面

分享两个有利于K8S的工具 目录 分享两个有利于K8S的工具 一、部署Dashboard&#xff08;主节点&#xff09; 介绍 1.1、查看集群状态 1.2、下载yaml文件并运行Dashboard 1.3、部署服务 1.4、创建访问账户、获取token&#xff08;令牌&#xff09; 1.5、浏览器访问Dash…

【K8s】专题五(5):Kubernetes 配置之热更新工具 Reloader

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、基本介绍 二、工作原理 三、部署方法 四、使用方法 一、基本介绍 Reloader 是一个用…

Git 学习笔记(超详细注释,从0到1)

Git学习笔记 1.1 关键词 Fork、pull requests、pull、fetch、push、diff、merge、commit、add、checkout 1.2 原理&#xff08;看图学习&#xff09; 1.3 Fork别人仓库到自己仓库中 记住2个地址 1&#xff09;上游地址&#xff08;upstream地址&#xff09;&#xff1a;http…

PFA方桶聚四氟乙烯溢流槽PFA酸洗槽耐腐蚀浸泡桶15L

PFA浸泡桶又叫PFA酸缸、PFA清洗槽、PFA方槽。 主要用于浸泡、清洗带芯片硅片电池片的花篮。由于PFA的特点它能耐受清洗溶液的腐蚀性&#xff0c;同时金属元素值低&#xff0c;无溶出无析出&#xff0c;不会污染芯片晶圆等。 半导体晶圆清洗槽尺寸可按要求定做。同时&#xff0…

ATA-4052C高压功率放大器在新能源汽车安全测试中的应用

新能源汽车的崛起已经改变了汽车行业的格局&#xff0c;为环境友好型交通方式提供了更多的选择。为了确保这些新型汽车的安全性和可靠性&#xff0c;进行全面的安全测试是至关重要的。高压功率放大器在新能源汽车的安全测试中发挥着重要的作用&#xff0c;本文将介绍其应用以及…

大模型什么时候应该进行微调

经常会遇到一个问题——LinkedIn 上的人们问我如何微调 LLaMA 等开源模型&#xff0c;试图找出销售 LLM 托管和部署解决方案的业务案例的公司&#xff0c;以及试图利用人工智能和大模型应用于他们的产品。但当我问他们为什么不想使用像 ChatGPT 这样的闭源模型时&#xff0c;他…

真空玻璃可见光透射比检测 玻璃制品检测 玻璃器皿检测

建筑玻璃检测 防火玻璃、钢化玻璃、夹层玻璃、均质钢化玻璃、平板玻璃、中空玻璃、真空玻璃、镀膜玻璃夹丝玻璃、光栅玻璃、压花玻璃、建筑用U形玻璃、镶嵌玻璃、玻璃幕墙等 工业玻璃检测 钢化安全玻璃、电加温玻璃、玻璃、半钢化玻璃、视镜玻璃、汽车安全玻璃、汽车后窗电热…

智慧油品营销调度大屏可视化应用

图扑应用自研 HT 搭建的 2D 智慧油品营销调度中心大屏展示模块主要以综合业务支撑平台为架构&#xff0c;全方位展示公司主要概况、业务运行、管理服务等多项内容&#xff0c;在内外部交流和品牌管理提升等方面发挥了积极作用。

接口提示信息国际化, 调用LibreTranslate 离线翻译, 国际化支持

文章目录 背景实现方式步骤下载并部署离线翻译服务;前端接入 背景 将接口返回内容进行翻译, 以适配多语言需求; 实现方式 前端拦截接口返回内容, 调用离线翻译服务进行翻译, 翻译之后再进行相应的提示 参考资料: 离线翻译服务: https://github.com/LibreTranslate/LibreTra…

Spring Boot 3 整合 SpringDoc OpenAPI 生成接口文档

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Go 内存模型与分配机制

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

亚马逊测评怎么赚钱,其他跨境电商平台也可以测评吗?

跨境电商平台我们应该都知道&#xff0c;有Amazon&#xff08;亚马逊&#xff09;、eBay、全球速卖通&#xff08;AliExpress&#xff09;、Wish、Shopee、Lazada、阿里巴巴国际站、沃尔玛、敦煌、希音、temu、独立站等 近几年国内电商行业市场饱和&#xff0c;竞争大利润低&a…

【机器学习】使用Python实现图神经网络(GNN):图结构数据的分析与应用

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、图神经网络的基础知识1. 图的基本概念和术语2. 传统的图分析方法3. 图神经网络的基本原理4. GNN的基本模型 三、主要的图神经网络模型1. 图卷积网络&#xff08;Graph Convolutional Network, GCN&#xff09;2…

“拥堵的6·18”一去不返,快递业终于“松了一口气”?

一年一度的电商“618”大促已然步入尾声。 与往年不同的是&#xff0c;今年自4月起&#xff0c;天猫、京东、快手等主流平台相继官宣取消预售。自此&#xff0c;今年的“618”成了首个取消预售的大促节。只是&#xff0c;有的平台取消了“预售制”&#xff0c;却新增了“仅退款…

巡检机器人智能联网,促进工厂自动化

随着工业4.0和智能制造的快速发展&#xff0c;企业引入自动化设备和智能机器人以提高生产效率和降低人工成本已成为大势所趋。其中&#xff0c;巡检机器人作为一种能够在复杂和危险环境中进行自动巡检的设备&#xff0c;受到了广泛关注。如何实现巡检机器人稳定、安全的联网是每…

Nature将大罢工!或将致Nature创刊155年首次发生缺刊!

Nature要罢工了&#xff01; 这两天一则爆炸性新闻袭击了学术界&#xff0c;根据英国National Union of Journalists&#xff08;NUJ&#xff0c;全国记者工会&#xff09;发布的信息。Nature期刊的编辑们将于2024年6月20日起举行罢工。 而那一天正是Nature最新一期发布的日子…

Wireshark v4 修改版安装教程(免费开源的网络嗅探抓包工具)

前言 Wireshark&#xff08;前称Ethereal&#xff09;是一款免费开源的网络嗅探抓包工具&#xff0c;世界上最流行的网络协议分析器&#xff01;网络封包分析软件的功能是撷取网络封包&#xff0c;并尽可能显示出最为详细的网络封包资料。Wireshark网络抓包工具使用WinPCAP作为…

调教NewspaceGPT之GPT4o实战

NewspaceGPT地址&#xff1a;https://newspace.ai0.cn 需求一&#xff1a;我需要一个创意logo 我的问题 我觉得我的描述对一个设计人员来说时精准的&#xff0c;但是不具体的。 需求描述&#xff1a;我需要一个logo。 表现司法公正和司法数字化&#xff0c;人工智能化 。 Ne…