红黑树的简单介绍

红黑树

红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是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. 检测其是否满足红黑树的性质

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务器出现故障如何恢复数据?

服务器数据恢复案例之服务器raid6中3块硬盘离线导致阵列崩溃的数据恢复案例 服务器故障&#xff1a; 服务器中有一组由6块盘组建的 RAID6&#xff0c;这台网站服务器上运行MYSQL数据库和存放其它类型的文件。该组raid中有两块磁盘离线&#xff0c;管理员没有及时更换磁盘&#…

#QT(智能家居界面-界面切换)

1.IDE&#xff1a;QTCreator 2.实验 3.记录 &#xff08;1&#xff09;创建一个新界面&#xff08;UI界面&#xff09; &#xff08;2&#xff09;可以看到新加入一个ui文件&#xff0c;双击打开&#xff0c;设置窗口大小与登录界面一致 &#xff08;3&#xff09;加入几个PUS…

Linux 运维:CentOS/RHEL防火墙和selinux设置

Linux 运维&#xff1a;CentOS/RHEL防火墙和selinux设置 一、防火墙常用管理命令1.1 CentOS/RHEL 7系统1.2 CentOS/RHEL 6系统 二、临时/永久关闭SELinux2.1 临时更改SELinux的执行模式2.2 永久更改SELinux的执行模式 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;…

【CSP试题回顾】201312-3-最大的矩形

CSP-201312-3-最大的矩形 解题思路 1. 遍历所有可能的矩形高度&#xff1a; 通过遍历所有矩形高度来找到最大的矩形&#xff0c;即对每个可能的高度 it&#xff08;从直方图中的最小高度到最大高度 heightMax&#xff09;&#xff0c;代码将尝试找到在这个高度或以上的最长连…

Linux常用命令(超详细)

一、基本命令 1.1 关机和重启 关机 shutdown -h now 立刻关机 shutdown -h 5 5分钟后关机 poweroff 立刻关机 重启 shutdown -r now 立刻重启 shutdown -r 5 5分钟后重启 reboot 立刻重启 1.2 帮助命令 –help命令 shutdown --help&#xff1a; ifconfig --help&#xff1a;查看…

Unity 协程(Coroutine)到底是什么?

参考链接&#xff1a;Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中&#xff0c;只能在主线程中获取物体的组件、方法、对象&#xff0c;如果脱离这些&#xff0c;Unity的很多功能无法实现&#xff0c;那么多线程…

(MATLAB)第十二章-数列与极限

目录 12.1 数列 12.1.1 数列求和 1. 累计求和函数sum() 2. 忽略NaN累计求和函数 nansum() 3. 求此元素位置之前的元素和函数cumsum() 4. 求梯形累计和函数cumtrapz() 12.1.2 数列求积 1. 元素连续相乘函数 prod() 2. 求累计积函数 cumprod() 3. 阶乘函数 ffactorial(n…

bun build

Bun 的快速原生打包器现已进入测试版阶段。可通过 bun build CLI 命令或 Bun.build() JavaScript API 使用。 bun build ./index.tsx --outdir ./build await Bun.build({entrypoints: [./index.tsx],outdir: ./build, }); 速度很快。下面的数字代表 esbuild 在 three.js 基…

Crossbar阵列的电路结构及其基本原理

忆阻器Crossbar阵列是一种先进的神经网络硬件实现技术&#xff0c;它利用忆阻器的物理特性来模拟神经网络中的突触连接&#xff0c;为人工智能和机器学习应用提供了一种高效、低能耗的计算平台。本文将深入探讨忆阻器Crossbar阵列的基本原理及其在Read&#xff08;读取&#xf…

Studio One 6永久激活版 附完整图文安装破解教程

Studio One 6是一款功能强大的音乐制作和录音软件&#xff0c;专为Mac操作系统设计。它提供了多轨录音和混音、MIDI音乐制作、实时效果和处理、VST插件支持以及高级编辑和编排等丰富的功能。无论是专业音乐制作人还是音乐爱好者&#xff0c;都可以使用Studio One 6来创建和编辑…

Android m/mm/mmm/make编译模块

一.编译成模块的前置条件 Android编译环境初始化完成后&#xff0c;我们就可以用m/mm/mmm/make命令编译源代码了。lunch命令其实是定义在build/envsetup.sh文件中的函数lunch提供的。与lunch命令一样&#xff0c;m、mm和mmm命令也分别是由定义在build/envsetup.sh文件中的函数…

istio pod不启动及访问报RBAC错误问题解决

istio pod不启动问题解决 在kubernetes集群中安装istio之后&#xff0c;在创建的depoyment中已经使用了注入注解sidecar.istio.io/inject: true’配置&#xff0c;但是istio pod不创建&#xff0c;代码示例如下 kind: Deployment apiVersion: apps/v1 metadata:name: name-an…

Linux 操作系统概述

GNU计划 GNU --"GNUs Not UNIX" 建立一个自由、开放的UNIX操作系统&#xff08;Free UNIX&#xff09; GNU 通用公共许可证&#xff08;General Public License&#xff0c;GPL&#xff09; ”四项基本自由“ 按照自己的意愿自由地运行该软件自由地学习并根据…

高级大数据技术 实验一 scala编程

​ 高级大数据技术 实验一 scala编程 写的不是很好&#xff0c;大家多见谅&#xff01; 1. 计算水仙花数 实验目标; &#xff08;1&#xff09; 掌握scala的数组&#xff0c;列表&#xff0c;映射的定义与使用 &#xff08;2&#xff09; 掌握scala的基本编程 实验说明 …

【系统需求分析报告-项目案例直接套用】

软件需求分析报告 软件开发要求项目建设内容物理设计安全系统设计安全网络安全设计应用安全设计用户安全管理性能设计稳定性设计安全性设计兼容性设计易操作性设计可维护行设计 软件开发全套精华资料过去进主页领取。

博弈论实用原理浅谈及题目实战【算法竞赛】

一、前言 本篇记录博弈论一些常见原理、做题技巧。 之前没有了解学习过博弈论&#xff0c;这篇文章可以当作记录学习笔记了。 二、初识博弈论 博弈论题目在竞赛中我感觉其实并不少见&#xff0c;只是需要技巧性很强&#xff0c;找到规律打代码很简单&#xff0c;而找不到基本上…

go语言基础 -- 面向对象 -- 接口与多态

接口定义与基本使用 - interface go语言中&#xff0c;接口类型可以定义一组方法&#xff0c;不需要在接口定义中实现方法&#xff0c;并且interface中不能含有变量&#xff0c;如果某个自定义类型要使用时再实现接口的方法。 golang中的接口不需要显式地实现&#xff0c;只要…

秘密共享差分隐私原理解析

1. 隐私计算全貌 &#xfffc;&#xfffc; 可以看到&#xff0c;隐私计算技术从1979年就开始了&#xff0c;历经四代从安全多方计算(MPC)、到差分隐私(DP)、到集中加密技术(TEE)&#xff0c;再到联邦学习(FL)。 2. 秘密共享 secret Sharing 就是“秘密分享”或者“秘密共享”…

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

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

Applied Energy+C论文复现:考虑泊位分配灵活性的港口综合能源系统优化调度程序代码!

程序结合了港口独特的工作属性&#xff0c;构建了泊位优化分配的模型&#xff0c;提出了考虑泊位优化和多能协同的港口综合能源运行优化模型。港口运营商根据多种能源供应的成本特性决策船舶停泊的开始&#xff0f;结束时间&#xff0c;改变港口的总负荷需求曲线。程序算例丰富…