红黑树的概念与实现

news2024/12/30 3:38:22

目录

​一、红黑树的概念

1.什么是红黑树

2.红黑树满足的性质

3.红黑树存在的意义

二、红黑树的实现

1.类的构建

2.插入函数

(1)插入一个节点

(2)调整节点

(3)旋转

三、红黑树的检验


一、红黑树的概念

1.什么是红黑树

红黑树是一种二叉搜索树,每个结点增加一个变量表示结点的颜色,颜色只能是Red或Black。 通过对所有从根到叶子节点的路径上各个结点颜色的限制,保证没有一条路径会比其他路径长出两倍,因而红黑树是一种接近平衡的二叉搜索树。

2.红黑树满足的性质

(1)每个结点不是红色就是黑色

(2)根节点是黑色的 

(3)如果一个节点是红色的,则它的两个子结点是黑色的(可以出现连续的黑节点,但不可以出现连续的红节点)

(4)对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 

(5)每个叶子结点都是黑色的(此处的叶子结点指的是null结点)

红黑树的五个规则使得搜索树成为了一个不严格的AVL树。

3.红黑树存在的意义

对于AVL树,由于它是绝对平衡的,所以为了维持这样的结构它就需要大量地对节点进行旋转,而红黑树对平衡的要求不是很严格,需要旋转的次数就减少了。同时,AVL树相比红黑树的搜索效率也并没有明显提高,比如说在同样的多个数据中查找一个数据,AVL树查找需要10次递归,在红黑树中查找可能需要递归15次(大于等于十次),但是对于一个每秒能计算一亿次的计算机而言,这五次查找根本就没有时间的影响。

所以红黑树是一种接近平衡的二叉搜索树,它不如AVL树的结构严格平衡,也解决了普通二叉搜索树的极端情况搜索效率下降的问题。

二、红黑树的实现

1.类的构建

只要是树就一定需要节点类BRTreeNode和整棵树的类BRTree,并将它们放在一个命名空间内。

BRTreeNode类中的成员变量包括,一个pair键值对(_kv),父指针(_parent),左子节点指针(_left),右子节点指针(_right),还有一个表示颜色的变量(_col),用枚举变量实现。

这里有个小问题,我们在构造函数中_col应该初始化为红还是黑呢?如果我们把这个默认颜色设置为黑,那么只要增加一个黑节点那一定会违背第四个规则。(对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 )而设为红节点,在后来的调整中还可以改,故默认颜色为红。

#include<assert.h>
namespace MY_BRTree
{
    //节点只有黑色和红色
    enum Colour
    {
        RED,
        BLACK,
    };
    
    template<class K, class V>
    struct BRTreeNode
    {
        std::pair<K, V> _kv;
        BRTreeNode* _parent;
        BRTreeNode* _left;
        BRTreeNode* _right;
        Colour _col;
        
        BRTreeNode(const std::pair<K, V>& kv)
            :_kv(kv)
            , _left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _col(RED)
        {}
        //除了键值对直接初始化,指针为空,默认颜色设置为红
    };
    
    template<class K, class V>
    class BRTree
    {
        typedef BRTreeNode<K, V> Node;
    public:
        //成员函数
    private:
        Node* _root = nullptr;
    }                

}

2.插入函数

我们的红黑树依旧只学习插入函数

(1)插入一个节点

bool insert(std::pair<K, V>& kv)
{
    //空树特殊处理
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;//根是黑色的
        return true;
    }
    //非空树
    else
    {
        Node* parent = nullptr;
        Node* cur = _root;//父子指针查找迭代,小往左走,大往右走
        while (cur)
        {
            if (kv.first < cur->_kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (kv.first > cur->_kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                return false;
            }
        }
        
        //此时已经找到了空节点
        cur = new Node(kv);
        cur->_col = RED;//设置初始颜色为红
        
        //将新节点与其父节点连接起来
        if (kv.first < cur->_kv.first)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        
    }  
    //到这里我们完成了节点的插入,后续还需要对树进行调整,主要是对节点颜色的调整                                                                                                                                                                                                    

                    
}

(2)调整节点

红黑树的节点颜色调整参考新插入节点的祖父节点的另一棵子树的根节点,也就相当于我们辈分中的叔叔节点。情况总共分为六种:

(3)旋转

上面的六种需要旋转的情形中当然也需要六种不同的解决方式。

所有的旋转只需要使用AVL树的左右单旋函数即可,这里我们只讲解4、5、6三种情况。

首先,图4,uncle节点在右且为红。

将parent和uncle节点变红且grandfather节点变红,如果grandfather的父节点也为红就继续向上更新至不会出现连续红节点即可。

其次,uncle在右为黑或者为空(空也为黑,这里仅以存在举例)而且cur、parent、grandfather在一条直线上。先对grandfather节点进行右单旋,然后改cur和grandfather为红节点,parent为黑节点即可。

然后,uncle在右为黑或者为空(空也为黑,这里仅以存在举例)而且cur、parent、grandfather不在一条直线上。先对parent节点进行左单旋变成上面的情况,然后再对grandfather右单旋再正确改变节点的颜色即可。

最后,uncle在左边的情况沿用上面的解法,将旋转方向改变一下,以同样的方式改变颜色就可以了。

最后的代码实现:

bool insert(std::pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;//根是黑色的
        return true;
    }
    else
    {
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (kv.first < cur->_kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (kv.first > cur->_kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                return false;
            }
        }
        
        cur = new Node(kv);
        cur->_col = RED;

        if (kv.first < cur->_kv.first)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }

        while (parent && parent->_col == RED)
        {
            //可以存在多个连续的黑节点,单不能出现连续的红节点
            //红黑树对于树的处理只取决于插入节点的祖父节点的另一个子节点,相当于叔叔节点

            Node* grandfater = parent->_parent;
            //叔叔节点在右侧
            if (parent == grandfater->_left)
            {
                Node* uncle = grandfater->_right;
                //uncle存在且为红
                if (uncle && uncle->_col == RED)
                {
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandfater->_col = RED;

                    cur = grandfater;
                    parent = cur->_parent;
                }
            }
            else
            {
            //uncle存在且为黑或者uncle不存在
            //cur是parent的左子节点
            if (cur == parent->_left)
            {
                RotateR(grandfater);
            parent->_col = BLACK;
            grandfater->_col = RED;
            }
            //cur是parent的右子节点
            else
            {
                RotateL(parent);
            RotateR(grandfater);
            cur->_col = BLACK;
            grandfater->_col = RED;
            }
            break;
            }
            }
            //叔叔节点在左侧
            else
            {
                Node* uncle = grandfater->_right;
                //uncle存在且为红
                if (uncle && uncle->_col == RED)
                {
                    uncle->_col = BLACK;
                    parent->_col = BLACK;
                    grandfater->_col = RED;

                    cur = grandfater;
                    parent = cur->_parent;
                }
                else
                {
                    //uncle存在且为黑或者uncle不存在
                    //cur是parent的右子节点
                    if (cur == parent->_left)
                    {
                        RotateL(grandfater);
                        parent->_col = BLACK;
                        grandfater->_col = RED;
                    }
                    //cur是parent的左子节点
                    else
                    {
                        RotateR(parent);
                        RotateL(grandfater);
                        cur->_col = BLACK;
                        grandfater->_col = RED;
                    }
                    break;
                }    
            }
         }
        _root->_col = BLACK;//不管我改没改根,我都把根置为黑,保证根必为黑
        return true;
    }
}

三、红黑树的检验

我们之前学过AVL树的检测(平衡一因子正确且为-1,0,1其一),红黑树的检验就比较复杂了。

红黑树必须满足红黑树的四个必要条件:

  • 根节点是黑色的 
  • 如果一个节点是红色的,则它的两个子结点是黑色的(可以出现连续的黑节点,但不可以出现连续的红节点)
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
  • 每个叶子结点都是黑色的(此处的叶子结点指的是null结点)

所以我们可以从这四条的方向考虑。

其中,由于所有的叶子节点都是nullptr,它一定为黑节点,所以第四条不需要写入判断。

bool IsBalance()
{
    if (_root == nullptr)//空树不是红黑树
    {
        return true;
    }

    if (_root->_col != BLACK)//根节点不为黑一定不是红黑树,条件1
    {
        return false;
    }
    
    int ref = 0;//ref用于储存最左侧路径上的黑节点数目
    Node* left = _root;
    while (left)
    {
        if (left->_col == BLACK)
        {
            ++ref;
        }
        left = left->_left;
    }
    return Check(_root, 0, ref);
    //Check函数用于检验其他路径上黑节点数是否相同以及是否有连续红节点,条件2和3
}

再来看看Check,Check会一直向下通过黑节点的个数检测各个子树是否为红黑树。

bool Check(Node* root, int blackNum, const int ref)
{
    if (root == nullptr)//找到了空树,此时已经统计完了一条路径上的黑节点数
    {
        if (blackNum != ref)//黑节点数与当前路径不同就不是红黑树
        {
            std::cout << "该路径上的黑节点数不等于最左侧路径的黑节点数" << std::endl;
        return false;
        }
        return true;
    }
    
    if (root->_col == RED && root->_parent->_col == RED)//不能有连续红节点
    {
        std::cout << "出现连续的红节点,不符合规范" << std::endl;
        return false;
    }

    if (root->_col == BLACK)//走到这里就一定没统计完整个路径,继续统计
    {
        ++blackNum;
    }

    return Check(root->_left, blackNum, ref) && Check(root->_left, blackNum, ref);
    //继续递归左树和右树
}

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

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

相关文章

okio篇2-RealBufferedSource

上一篇讲过&#xff0c;okio只有两个概念&#xff0c;source和sink。source对应InputStream&#xff0c;即负责将数据读出&#xff0c;是一个输出方&#xff08;所以只有source.read方法&#xff09;。sink对应outputStream&#xff0c;负责获取数据写入&#xff0c;是一个写入…

RT-Thread Nano在keil Simulator中的仿真

目的&#xff1a;使用STM32CubeMX生成包含RT-Thread Nano内核和FinSH控制台的keil工程&#xff0c;在没有硬件开发板的情况下&#xff0c;通过keil Simulator来运行系统&#xff0c;并通过SHELL来与系统进行交互。 一、使用STM32CubeMX生成RT-Thread Nano工程 官方文档已经说…

C++标准库 -- 动态内存 (Primer C++ 第五版 · 阅读笔记)

C标准库 --动态内存 (Primer C 第五版 阅读笔记&#xff09; 第12章 动态内存------(持续更新)12.1、动态内存与智能指针12.1.1、shared_ptr类12.1.2、直接管理内存12.1.3、shared_ptr和new结合使用12.1.4、智能指针和异常12.1.5、unique_ptr12.1.6、weak_ptr 12.2、动态数组1…

网络通信之网络层与数据链路层

文章目录 讲在前面网络层网络层概述IP协议格式网段划分公有IP、私有IP、特殊IP理解路由 数据链路层MAC地址以及MAC帧&#xff08;以太网帧&#xff09;MTU协议MTU对IP和TCP协议的影响ARP协议及其作用 涉及到的相关协议DNS协议&#xff08;应用层&#xff09;NAT与NAPT协议 总结…

BEV (0)---DETR

1 DETR 1.1 DETR处理流程 1.1.1 将图像输入给Backbone获得图像特征与位置编码 ①. 对给定的输入图像通过resnet进行特征提取&#xff0c;最终得到特征图C5∈RBx2048xhxw,其中h、w为输入图像尺寸得1/32。随后再用一层11卷积压缩一下通道&#xff0c;得到特征图P5∈RBx256xhxw。…

jvm调优策略

jvm调优主要是内存管理方面的调优&#xff0c;包括各个代的大小&#xff0c;GC策略等。 代大小调优 JVM 中最大堆大小有三方面限制&#xff1a;相关操作系统的数据模型&#xff08;32-bt还是64-bit&#xff09;限制&#xff1b;系统的可用虚拟内存限制&#xff1b;系统的可用物…

数据结构学习记录——什么是堆(优先队列、堆的概念、最大堆最小堆、优先队列的完全二叉树表示、堆的特性、堆的抽象数据类型描述)

目录 优先队列 若采用数组或链表实现优先队列 数组 链表 有序数组 有序链表 总结 若采用二叉搜索树来实现优先队列 最大堆 堆的概念 优先队列的完全二叉树表示 堆的两个特性 结构性 有序性 【例】最大堆和最小堆 【例】不是堆 堆的抽象数据类型描述 优先队列…

安排超市 -- BFS分割搜索

4.安排超市 给定一个n*n的地图。地图是上下左右四联通的&#xff0c;不能斜向行走&#xff1a; *代表障碍&#xff0c;不可通行。 .代表路&#xff0c;可以通行。 #代表房子。房子也是可以通行的。 小红现在需要在一些地方安排一些超市&#xff08;不能安排在障碍物上&#xf…

山东专升本计算机第七章-计算机网络基础

计算机网络基础 计算机网络系统 考点 6 计算机网络硬件 主体设备 • 称为主机 • 一般可分为中心站&#xff08;又称服务器&#xff09;和工作站&#xff08;客户机&#xff09; 连接设备 • 网卡 • 工作在数据链路层 • 网卡又称网络适配器&#xff0c;是连接主机和网…

【C++初阶】引用

一.概念 引用就是取别名&#xff0c;在语法上它不会开空间&#xff0c;而是和它引用的变量共用同一块空间。对引用的操作也就是对原来变量的操作。就像现实生活中给人取外号一样&#xff0c;不管是喊外号还是本名&#xff0c;指的都是那个人。 二.引用特性 1.引用类型必须和引用…

Java8 新特性讲解

一、Lambda表达式 Lambda 是一个匿名函数&#xff0c;我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格&#xff0c;使Java的语言表达能力得到了提升。 二、函数式接口 &#…

【网课平台】Day15.Devops:持续集成与持续交付

文章目录 一、Devops1、什么是Devops2、什么是CI/CD3、Devops方案参考 二、人工部署1、项目打jar包2、生成镜像、创建容器 三、自动化部署1、代码提交到git2、修改pom.xml文件3、前端部署 一、Devops 1、什么是Devops 一个软件的生命周期包括&#xff1a;需求分析阶、设计、开…

SpringCloud:ElasticSearch之集群

单机的elasticsearch做数据存储&#xff0c;必然面临两个问题&#xff1a;海量数据存储问题、单点故障问题。 海量数据存储问题&#xff1a;将索引库从逻辑上拆分为N个分片&#xff08;shard&#xff09;&#xff0c;存储到多个节点单点故障问题&#xff1a;将分片数据在不同节…

【原创】运维的终点是开发~chatGPT告诉你真相

文章目录 软件技术岗位鄙视链&#xff0c;你在哪层呢&#xff1f;让chatGPT告诉你运维工作好&#xff0c;还是开发工作好问它几个问题1. 一个三年运维成长的案例和薪资2. 一个三年开发成长的案例和薪资3. 一个五年运维成长的案例和薪资4. 一个五年开发成长的案例和薪资5. 一个十…

云分析迁移:顺应需求

云提供了对新分析功能、工具和生态系统的访问&#xff0c;可以快速利用这些功能、工具和生态系统来测试、试点和推出新产品。然而&#xff0c;尽管迫在眉睫&#xff0c;但企业在将分析迁移到云时仍感到担忧。组织正在寻找能够帮助他们分配资源和集成业务流程的服务提供商&#…

Linux 服务器上安装和使用 Redis,只需这 4 步!

一、使用 yum 安装 Redis 使用以下命令&#xff0c;直接将 redis 安装到 linux 服务器&#xff1a; yum -y install redis 二、配置远程连接 a&#xff09;首先第一步&#xff0c;将 redis 配置文件下载到本地&#xff08;如果你熟悉 vim 操作&#xff0c;直接用 vim 编辑即可…

论文阅读《PIDNet: A Real-time Semantic Segmentation Network Inspired by PID》

论文地址&#xff1a;https://arxiv.org/pdf/2206.02066.pdf 源码地址&#xff1a;https://github.com/XuJiacong/PIDNet 概述 针对双分支模型在语义分割任务上直接融合高分辨率的细节信息与低频的上下文信息过程中细节特征会被上下文信息掩盖的问题&#xff0c;提出了一种新的…

【五一创作】Springboot+多环境+多数据源(MySQL+Phoenix)配置及查询(多知识点)

文章目录 1. 背景2. 技术点3 子模块依赖SpringBoot设置4. 多环境配置4.1 application.yml4.2 application-pro.yml 5. 多数据源配置5.1 yml配置5.2 自定义数据源在Java中配置5.2.1 PhoenixDataSourceConfig5.2.2 MysqlDataSourceConfig 6. 完整的Pom6. 测试6.1 Mapper配置6.2 方…

字符、块、网络设备

设备模型&#xff08;的意义&#xff09; 降低设备多样性带来的Linux驱动开发的复杂度&#xff0c;以及设备热拔插处理、电源管理等&#xff0c;Linux内核提出了设备模型概念。设备模型将硬件设备归纳、分类&#xff0c;然后抽象出一套标准的数据结构和接口。驱动的开发&#…

Java项目上线之云服务器环境篇(四)——Redis的安装与配置

Java项目上线之云服务器环境篇&#xff08;四&#xff09;——Redis的安装与配置 在我们的项目里可能会用到Redis缓存&#xff0c;需要对Redis进行简单的配置。 1、我们的redis最好放在一个事先安装好的文件夹里&#xff0c;这样更方便于管理。 例如&#xff1a; redis我是放在…