红黑树的简单介绍

news2024/12/23 13:39:58

红黑树

红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
例如:
下图就是一个红黑树,同时也是一颗二叉搜索树
和AVL树不同的是,AVL树依靠着平衡因子的限制的平衡性比红黑树要更高
在这里插入图片描述

红黑树的性质

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

我们要尤其注意一个点:
红黑树的叶子节点是空节点,而非我们之前传统意义上的叶子节点

对于性质3的简化就是,一条路径上不能出现连续的红节点

基于上面给出的性质,大家可以思考一个问题:

那么一颗红黑树的最短路径和最长路径分别有什么特点呢?
大家仔细思考就会返现,由于每条路径必须要有相同的黑节点,且不能出现连续的红节点,就可以总结出来:
最短路径全是黑节点,最长路径是一黑一红节点相间

可能大家会有一个问题:为什么有了AVL树还有搞一个红黑树呢,而且他的平衡性还没有AVL树高

确实红黑树的搜索时间复杂度没有AVL树这么快,但是红黑树的搜索效率和AVL树可以近似看作相等,但是红黑树不需要那么多的旋转来调平衡,因为红黑树可以允许最长路径是最短路径的2倍,他的要求并没有AVL树那么严格,所以红黑树的旋转次数要比AVL树少很多,效率自然就提升了,故而实际应用中红黑树要比AVL树用的更多一些。map和set的底层实现都是用红黑树而并非AVL树所实现的。

红黑树的定义

根据上面的红黑树的性质和我们之前学习的AVL树的知识的铺垫,我们就可以很快的将红黑树的基本框架搭起来:

与AVL树的平衡因子不同,红黑树除了节点外还要枚举节点的颜色
我们将黑色和红色先进行枚举

enum Color { RED, BLACK };

然后进行树的节点的定义:

// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
    RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
        : _left(nullptr), _right(nullptr), _parent(nullptr)
        , _data(data), _color(color)
    {}
    RBTreeNode<ValueType>* _left;   // 节点的左孩子
    RBTreeNode<ValueType>* _right;  // 节点的右孩子
    RBTreeNode<ValueType>* _parent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
    ValueType _data;            // 节点的值域
    Color _color;               // 节点的颜色
};

大家可以看到我们在节点的颜色的初始化时的时候给了缺省参数是红色,这是为什么呢?

大家可以想一想,如果我们给的节点是黑色,那么无论新增到哪一颗红黑树上都会导致这颗红黑树不平衡,但是使用红节点只是可能导致不平衡,所以我们当然优先使用红节点!

例如:
下图中新增红节点不一定会导致红黑树不平衡,但是如果新增的节点颜色是黑色,那么一定要进行操作来保持这棵树的平衡
在这里插入图片描述

红黑树的插入

和AVL树一样,红黑树的插入操作可以分为两步:

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

我们先插入节点,不管平衡性

template<class T>
class RBTree
{
    typedef RBTreeNode<T> Node;
public:
    bool insert(const T& key)
    {
        if (_root == nullptr)
        {
            _root = new Node(key);
            _root->_color = RED;
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_data > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (cur->_data < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key);
        cur->_color = RED;
        if (parent->_data > key)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
    }

private:
    Node* _root = nullptr;
    
};

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

由于需要双亲节点的兄弟和父亲节点,我们制定了简写:
cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

注意,下面所有图中的树可能是一整棵树,也有可能是一颗子树

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

将u和p变黑,g变红,如果g是根节点的话,调整完成后g节点必须变黑

如果g还有双亲节点的话,并且g的parent是红色的话就继续向上调整
在这里插入图片描述
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

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

在这里插入图片描述
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
在这里插入图片描述

完整插入代码如下:

bool insert(const T& key)
{
    if (_root == nullptr)
    {
        _root = new Node(key);
        _root->_color = RED;
        return true;
    }
    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_data > key)
        {
            parent = cur;
            cur = cur->_left;
        }
        else if (cur->_data < key)
        {
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            return false;
        }
    }
    cur = new Node(key);
    cur->_color = RED;
    if (parent->_data > key)
    {
        parent->_left = cur;
        cur->_parent = parent;
    }
    else
    {
        parent->_right = cur;
        cur->_parent = parent;
    }

    while (parent && parent->_color == RED)
    {
        Node* grandfather = parent->_parent;
        //parent是g的左孩子
        if (parent->_left == grandfather->_left)
        {
            //结构
            //     g
            //  p     u
            //c
            Node* uncle = grandfather->_right;
            if (uncle && uncle->_color == RED)
            {
                //进行变色
                parent->_color = uncle->_color = BLACK;
                grandfather->_color = RED;
                cur = grandfather;
                parent = cur->_parent;
            }
            //uncle不存在
            else
            {
                if (cur == parent->_left)
                {
                    //单旋
                    //    g
                    //  p
                    //c
                    RotateR(grandfather);
                    parent->_color = BLACK;
                    grandfather->_color = RED;
                }
                else
                {
                    //双旋
                    //     g
                    //  p
                    //     c
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_color = BLACK;
                    grandfather->_color = RED;
                }
                break;
            }
        }
        else // parent == grandfather->_right 
        {
            // g 
            // u p
            // c 
            Node* uncle = grandfather->_left;

            if (uncle && uncle->_color == RED)
            {
                // 变色 
                parent->_color = uncle->_color = BLACK;
                grandfather->_color = RED;
                // 继续往上处理 
                cur = grandfather;
                parent = cur->_parent;
            }
            else
            {
                if (cur == parent->_right)
                {
                    RotateL(grandfather);
                    parent->_color = BLACK;
                    grandfather->_color = RED;
                }
                else
                {
                    // g 
                    // u p 
                    // c 
                    // 
                    RotateR(parent);
                    RotateL(grandfather);
                    cur->_color = BLACK;
                    grandfather->_color = RED;
                }
                break;
            }
        }
        _root->_color = BLACK;
        return true;
    }
}
红黑树的迭代器

迭代器是很重要的一个部分,没有迭代器一切容器都不好用

template<class T>
struct _treeiterator
{
    typedef RBTreeNode<T> Node;
    typedef _treeiterator<T> Self;
    Node* _node;
    _treeiterator(Node* node)
        :_node(node)
    { }

    T& operator*()
    {
        return _node->_data;
    }

    T* operator&()
    {
        return &_node->_data;
    }

    Self operator++()
    {
        if (_node->_right)
        {
            Node* cur = _node->_right;
            while (cur->_left)
            {
                cur = cur->_left;
            }
            _node = cur;
        }
        else
        {
            Node* cur = _node->_left;
            Node* parent = cur->_parent;
            while (parent && cur == parent->_right)
            {
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent;
        }
        return *this;
    }

    bool operator != (const Self& s)
    {
        return _node != s._node;
    }

    bool operator == (const Self & s)
    {
        return _node == s._node;
    }
};

typedef treeiterator<T> iterator;

iterator begin()
{
    Node* cur = _root;
    while (cur && cur->_left)
    {
        cur = cur->_left;
    }
    return iterator(cur);
}

iterator end()
{
    return iterator(nullptr);
}
红黑树的验证

红黑树的检测分为两步:

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

由于博主的能力有限,本篇博文的分享到这里就结束了,感谢大家的支持!

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

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

相关文章

爬虫案例二

第一步电影天堂_免费在线观看_迅雷电影下载_电影天堂网 (dytt28.com)电影天堂_电影下载_高清首发 (dytt89.com)电影天堂_免费在线观看_迅雷电影下载_电影天堂网 (dytt28.com) 打开这个网站 我直接打开 requests.exceptions.SSLError: HTTPSConnectionPool(hostwww.dytt28.com…

99.qt qml-单例程序实现

在之前讲过: 58.qt quick-qml系统托盘实现https://nuoqian.blog.csdn.net/article/details/121855993 由于,该示例只是简单讲解了系统托盘实现,并没有实现单例程序,所以多次打开后就会出现多个exe出现的可能,本章出一章QML单例程序实现, 多次打开始终只显示出第一个打开…

论文阅读:2022Decoupled Knowledge Distillation解耦知识蒸馏

SOTA的蒸馏方法往往是基于feature蒸馏的&#xff0c;而基于logit蒸馏的研究被忽视了。为了找到一个新的切入点去分析并提高logit蒸馏&#xff0c;我们将传统的KD分成了两个部分&#xff1a;TCKD和NCKD。实验表明&#xff1a;TCKD在传递和样本难度有关的知识&#xff0c;同时NCK…

【RT-DETR有效改进】全新的SOATA轻量化下采样操作ADown(轻量又涨点,附手撕结构图)

一、本文介绍 本文给大家带来的改进机制是利用2024/02/21号最新发布的YOLOv9其中提出的ADown模块来改进我们的Conv模块&#xff0c;其中YOLOv9针对于这个模块并没有介绍&#xff0c;只是在其项目文件中用到了&#xff0c;我将其整理出来用于我们的YOLOv8的项目&#xff0c;经…

动态之美:Motion 5特效,让视频栩栩如生 mac版

Motion 5&#xff0c;一款强大的视频后期特效软件&#xff0c;凭借其丰富的功能和出色的性能&#xff0c;成为了众多影视制作人员的首 选工具。它不仅能够满足专业级影视制作的需求&#xff0c;也适合初学者探索和实践视频特效的魅力。 Motion 5软件获取 Motion 5拥有丰富多样…

SpringBoot源码解读与原理分析(四十)基于jar/war包的运行机制

文章目录 前言第14章 运行SpringBoot应用14.1 部署打包的两种方式14.1.1 以可独立运行jar包的方式14.1.2 以war包的方式 14.2 基于jar包的独立运行机制14.2.1 可独立运行jar包的相关知识14.2.2 SpringBoot的可独立运行jar包结构14.2.3 JarLauncher的设计及工作原理14.2.3.1 Jar…

怎样获得CNVD原创漏洞证书

1. 前言 因为工作变动&#xff0c;我最近把这一年多的工作挖漏洞的一些工作成果提交到了CNVD漏洞平台&#xff08;https://www.cnvd.org.cn/&#xff09;&#xff0c;获得了多张CNVD原创漏洞证书。本篇博客讲下怎么获得CNVD原创漏洞证书&#xff0c;以供大家参考。 2. CNVD原创…

【设计模式 03】抽象工厂模式

一个具体的工厂&#xff0c;可以专门生产单一某一种东西&#xff0c;比如说只生产手机。但是一个品牌的手机有高端机、中端机之分&#xff0c;这些具体的属于某一档次的产品都需要单独建立一个工厂类&#xff0c;但是它们之间又彼此关联&#xff0c;因为都共同属于一个品牌。我…

数据可视化原理-腾讯-散点图

在做数据分析类的产品功能设计时&#xff0c;经常用到可视化方式&#xff0c;挖掘数据价值&#xff0c;表达数据的内在规律与特征展示给客户。 可是作为一个产品经理&#xff0c;&#xff08;1&#xff09;如果不能够掌握各类可视化图形的含义&#xff0c;就不知道哪类数据该用…

白银期货开户交割规则有哪些?

白银期货交割是指期货合约到期时&#xff0c;交易双方通过该期货合约所载商品所有权的转移&#xff0c;了结到期未平仓合约的过程。小编在此为大家详细介绍白银期货的交割规则有哪些。白银期货的交割规则有哪些&#xff1f;白银期货的交割规则主要有&#xff1a; 一、交割商品…

echarts如何实现3D饼图(环形图)?

一、实现的效果 二、具体步骤 1.安装依赖 npm install echarts 2.引入echarts import * as echarts from echarts; 注意&#xff1a;这里需要用到echarts-gl&#xff0c;必须单独引入才可以 import echarts-gl; 3.echarts部分代码 我知道这部分内容很多&#xff0c;但只要cv…

【ES入门一:基础概念】

集群层面上的基础概念 集群 由多个es实例组成的叫做集群 节点 单个ES的服务实例叫做节点。每个实例都有自己的名字&#xff0c;就是在配置文件中配置的‘node.name’中的内容。为了标识每个节点&#xff0c;每个节点启动后都会分配一个UID&#xff0c;存储在data目录。每个…

第七十四天漏洞发现-Web框架中间件插件BurpSuite浏览器被动主动探针

第74天 漏洞发现-Web框架中间件插件&BurpSuite&浏览器&被动&主动探针 最近几天都是演示工具如何使用如&#xff1a;AWVS、Nessus、nexpose等综合性利用工具。 Burp插件和漏扫工具的区别 知识点&#xff1a; 1、浏览器插件&BurpSuite插件 2、Hack-Tools&…

基于协同过滤的旅游推荐系统设计与实现

基于协同过滤的旅游推荐系统设计与实现 在当今旅游业蓬勃发展的背景下&#xff0c;人们对于旅游体验的需求日益增加&#xff0c;如何为用户提供更加个性化、精准的旅游推荐成为了旅游行业的一个重要课题。为解决这一问题&#xff0c;我们设计并实现了一个基于协同过滤的旅游推…

“互动+消费”时代,借助华为云GaussDB重构新零售中消费逻辑

场与人的关系 “人—货—场”是零售中重要的三要素&#xff0c;我们一直在追求&#xff0c;将零售中的人、货、场进行数字化并在云端进行整合&#xff0c;形成属于我们自己的云平台。 随着互联网技术为信息提供的便利&#xff0c;消费者的集体力量正在逐渐形成一股强大的反向…

RabbitMQ的整体架构是怎么样的?

RabbitMQ是一个开源的消息中间件&#xff0c;用于在应用程序之间传递消息。它实现了AMQP(高级消息队列协议)并支持其他消息传递协议&#xff0c;例如STOMP(简单文本定向消息协议)和MQTT&#xff08;物联网协议&#xff09; 他的整体架构大致如下&#xff1a; Producer&#xf…

【NVCC,CUDA,NVIDIA驱动】装了pytorch,nvcc -V不能用,但能正常使用gpu

这里写目录标题 问题描述问题原理为什么anaconda安装的Pytorch&#xff0c;其能够直接在gpu上运行NVCC是什么&#xff0c;怎么查看装没装 如果没有NVCC文件夹&#xff0c;应该如何安装NVCC&#xff1f;CUDNN&#xff1a;Local Installer for Linux x86_64和Local Installer for…

小程序开通流量主

开发小程序有一段时间了&#xff0c;误打误撞开通了流量主。到现在有2400人访问了&#xff0c;当然这是累计的&#xff0c;每天访问人数也就是平均七八十左右。 当然&#xff0c;每日还是有一些收入的&#xff0c;虽然比较低&#xff0c;一块钱上下&#xff1a; 感觉做小程序&…

hnust 湖南科技大学 2022 数据挖掘课设 完整代码+报告+图源文件+指导书

hnust 湖南科技大学 2022 数据挖掘课设 完整代码报告图源文件指导书 目录 实验一 Apriori算法设计与应用 - 1 - 一、 背景介绍 - 1 - 二、 实验内容 - 1 - 三、 实验结果与分析 - 2 - 四、 小结与心得体会 - 3 - 实验二 KNN算法设计与应用 - 4 - 一、 背景介绍 - 4 - 二、 实…

NLP评价指标

一、分类任务常见评估&#xff1a; 准确度(Accuracy) 评估预测正确的比例&#xff0c;精确率(Precision) 评估预测正例的查准率&#xff0c;召回率(Recall) 评估真实正例的查全率。如果是多分类&#xff0c;则每个类别各自求P、R最终求平均值。 TP&#xff08;True Positives…