红黑树的简单介绍

简介: 红黑树的简单介绍

红黑树

红黑树的概念

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

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

相关文章
|
Java 测试技术 数据库
【达梦数据库】达梦数据库windows安装
【达梦数据库】达梦数据库windows安装
835 0
|
10月前
|
人工智能 弹性计算 安全
2026年阿里云最新活动内容解读,个人和企业优惠权益及云产品特惠和免费试用等规则介绍
2026年阿里云推出多重优惠活动,覆盖权益中心、企业上云、中小企业扶持、云服务器爆款直降及免费试用等领域。活动提供AI焕新券(满20减10元)、GPU算力1折起、160+款云产品免费试用等权益,支持个人开发者、中小企业及企业级用户。通过低价域名(1元起)、高性能云服务器、协同办公解决方案及专项补贴(最高10万元出海补贴)。
1443 8
|
Shell 数据安全/隐私保护 Python
微信虚拟摄像头插件,QQ虚拟相机拍摄录像工具,替换虚拟视频聊天软件
完整的虚拟摄像头实现方案,包含多个模块的代码实现。这个项目可以模拟摄像头设备,并在微信/QQ视频
|
7月前
|
编解码 安全 网络安全
【部署一步到位】OpenClaw 2.6.4 本地部署常见问题与解决方法(内含安装包)
OpenClaw 2.6.4 部署不用反复折腾,本文汇总安装、启动、配置全流程问题,简单步骤直接解决,附带完整安装包,新手也能一步到位完成部署。
|
消息中间件 监控 数据可视化
实时看板数据分析的力量:推动高效决策的17个关键维度
实时看板数据分析通过可视化工具将企业运营数据实时整合展示,助力快速决策。它结合数据收集、处理与交互分析,广泛应用于制造、零售、金融等领域,提升响应速度与协作效率。技术上依赖流数据处理与可视化工具,如Power BI、Tableau等,同时关注数据安全与用户体验优化。
814 1
|
8月前
|
人工智能 物联网 C++
深耕多领域,赋能新科技——C++的核心运用与时代价值
自1983年诞生以来,C++语言凭借高效的执行性能、灵活的内存控制和强大的兼容性,历经四十余年迭代,始终在编程语言领域占据核心地位。
501 0
|
9月前
|
数据采集 人工智能 监控
让大模型“开小灶”:手把手教你打造能聊业务的专属AI
本文深入浅出解析AI微调(Fine-tuning)技术:揭示通用大模型“懂常识却不懂行”的根源,详解LoRA等高效微调原理,对比RAG适用场景,并提供数据准备、LoRA训练、效果评估到部署的四步实践指南。助力业务人员低成本打造专属行业AI助手。(239字)
816 1
|
Java Unix Linux
Java “SocketException” 错误怎么处理
Java 中的 &quot;SocketException&quot; 错误通常发生在网络通信过程中,如连接失败、断开连接或数据传输异常。处理方法包括检查网络配置、确保服务器正常运行、使用超时设置和重试机制,以及捕获并处理异常。
3238 6
|
存储 算法 API
还社交一个自由的未来:去中心化社交网络,会是下一个“推特”吗?
还社交一个自由的未来:去中心化社交网络,会是下一个“推特”吗?
538 5
|
数据安全/隐私保护 Windows
LabVIEW项目中使用库
LabVIEW项目中使用库
564 1