红黑树(RBTree)认识总结

一、认识红黑树

1.1 什么是红黑树?

红黑树是一种二叉搜索树,与普通搜索树不同的是,在每个节点上增加一个“颜色”变量 —— RED / BLACK 。

通过对各个节点颜色的限制,确保从 根 到 NIL ,没有一条路径会比其他路径长出两倍。

NIL :表示叶子节点的空指针,统一设置为 BLACK

1.2 红黑树的性质
  • 根节点一定是黑色
  • 不能出现两个连续的红色节点
  • 对于同一高度而言,从根到该高度任一节点的简单路径上的黑色节点的数量相同
1.3 红黑树节点定义

二、红黑树

2.1 红黑树定义
template<class K, class V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
    
private:
    Node* _root;
};
2.2 插入

红黑树的插入是我们学习红黑树过程最重要的知识之一,它主要分为两部分:平衡二叉树的插入 和 旋转 —— 调整树形结构。

插入部分与普通搜索树没有本质区别,这里不做过多介绍。

声明一下:代码中的 grandfather 和 图中的 grandparent 为同一东西,笔者在基本结束本篇时发现这里差异。

  • 插入部分
bool Insert(const pair<K, V> kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK; // 根一定为黑色节点
        return true;
    }
    
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
        if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else if (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else 
        {
            return false; // 树中已经存在要插入的值,本次插入失败
        }
    }
    
    cur = new Node(kv);
    if (cur == parent->_left)
    {
        parent->_left = cur;
    }
    else 
    {
        parent->_right = cur;
    }
    cur->_parent = parent;
    
    _root->_col = BLACK; // 强制设定根一定为黑色!
    return true;
}
  • 旋转
2.2.1 什么时候要旋转?

我们新插入的节点默认是红色,当它的 parent 存在且为红色时,就出现了这种情况 —— 树存在两个连续的红色节点,此时我们需要对该部分子树进行旋转 —— 调整树的结构。(下图只展示了部分的子树)

判断条件:parent 存在且为红色

	while (parent && parent->_col == RED)
    { }
2.2.2 几种旋转情况
情形一:uncle 存在,且为红色节点

在这里插入图片描述

	Node* grandfather = parent->_parent;
	if (parent == grandfather->_left)
    {
        Node* uncle = grandfather->_right;
        if (uncle && uncle->_col == RED) // 叔叔存在且为红色
        {
            grandfather->_col = RED;
            parent->_col = BLACK;
            uncle->_col = BLACK;

            cur = grandfather; //  向上调整
            parent = cur->_parent;
        }
    }
	
	if (parent == grandfather->_right)
    {
        Node* uncle = grandfather->_left;
        // ... // 与上面代码一致
    }
情形二:uncle 不存在 或 存在且为黑色
  • parent 在 grandfather 左侧的两种情况
	if (parent == grandfather->_left) // parent 在 grandfather 左侧的两种情况
    {
        if (!uncle || uncle->_col == BLACK)
        {
            if (cur == parent->_left) // cur 在 parent 左侧
            {
                RotateR(grandfather);
                
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            else // cur 在 parent 右侧
            {
                RotateL(parent);
                RotateR(grandfather);
                
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            break; // 旋转结束后,一定要 break 
            
        }
    }

旋转结束后,树的结构已经满足了红黑树的标准,如果不跳出循环、继续调整,会出现各种奇怪的问题。

  • parent 在 grandfather 右侧
	if (parent == grandfather->_right) // parent 在 grandfather 右侧
    {
        if (!uncle || uncle->_col == BLACK) // uncle 不存在 或 uncle存在且为黑色节点
        {
            if (cur == parent->_right) // cur 在 parent 右侧
            {
                RotateL(grandfather);
                
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            else // cur 在 parent 左侧
            {
                RotateR(parent);
                RotateL(grandfather);
                
                parent->_col = BLACK;
                grandfather->_col = RED;
            }
            break;
        }
    }

在这里插入图片描述

2.3 红黑树的验证

红黑树的验证,顾名思义,就是验证 你的“红黑树” 是否能满足红黑树的三条性质。

  • 根节点一定是黑色
  • 不能出现两个连续的红色节点
  • 对于同一高度而言,从根到该高度任一节点的简单路径上的黑色节点的数量相同
	bool IsBalance()
    {
        if (_root && _root->_col == RED) // 验证第一条性质
        {
            cout << "根节点为红色" << endl;
            return false;
        }
        
        // 要判断是否每一条路径上的黑色节点数相同,首先要找一个标杆 —— 这里旋转树最左路径的黑色节点个数
        Node* cur = _root;
        int RefBlackNum = 0;
        while (cur)
        {
            if (cur->_col == BLACK)
                ++RefBlackNum;
            
            cur = cur->_left;
        }
        
        return Check(_root, 0, RefBlackNum);
    }
	bool Check(Node* cur, int BlackNum, int RefBlackNum)
    {
        if (cur == nullptr) //  走到 NIL 时,判断该路径黑色节点个数是否与标杆相同
        {
            if (BlackNum != RefBlackNum)
            {
                cout << "路径黑色节点的个数不相同" << endl; // 验证第三条性质
                return false;
            }
            return true;
        }
        
        if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED)
        {
            cout << "存在两个连续的红色节点" << endl; // 验证第二条性质
            return false;
        }
        
        if (cur->_col == BLACK)
            ++BlackNum;
        
        return Check(cur->_left, BlackNum, RefBlackNum) // 递归判断当前节点的左右子树是否合法
            && Check(cur->_right, BlackNum, RefBlackNum);
    }

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

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

相关文章

R语言软件安装及配置

1、下载 网址&#xff1a;www.r-project.org 1.1 下载R 选择download R 选择清华源进行下载 根据自己系统情况下载&#xff0c;我选择windows系统。 先选择base。 选择最新的版本下载。 1.2 下载RTools 下载好后&#xff0c;返回&#xff0c;选择RTools进入后&#xff0c;选…

替换spring-boot中的组件版本

spring-boot是一个用于简化开发的框架&#xff0c;引入spring-boot后会自动包含spring框架&#xff0c;通过引入xxx-start来完成指定组件的功能。比如&#xff1a; spring-boot-starter-web(嵌入 Tomcat 和 web 开发需要的 servlet 和 jsp 支持)spring-boot-starter-data-jpa(…

python EEL应用程序的启动过程

EEL 启动流程 初始化 EEL (eel.init()): 设定静态文件目录&#xff0c;通常是包含 HTML、CSS、JavaScript 等文件的目录。扫描指定目录下的 JavaScript 文件&#xff0c;寻找通过 eel.expose() 暴露的函数。 启动 Web 服务器 (eel.start()): 基于 Bottle 框架启动一个轻量级的 …

2024年3月 青少年等级考试机器人理论真题四级

202403 青少年等级考试机器人理论真题四级 第 1 题 Arduino UNO/Nano主控板&#xff0c;通过按键开关切换高低电平&#xff0c;电路搭设如下&#xff0c;该电路属于&#xff1f;&#xff08; &#xff09; A&#xff1a;外部上拉电阻电路 B&#xff1a;外部下拉电阻电路 C&a…

防火墙远程桌面端口号修改,通过防火墙修改远程桌面的端口号详细操作步骤

使用防火墙修改远程桌面的端口号是一项涉及系统安全和网络配置的重要任务。 以下是详细的操作步骤&#xff0c;旨在确保您能够安全、有效地完成此操作&#xff1a; 一、准备阶段 1. 了解默认端口号&#xff1a;远程桌面端口号通常是3389&#xff0c;这是一个用于远程访问和控…

五款商用加密软件推荐 | 商用加密软件排行榜

没有网络安全就没有国家安全。信息安全是国家经济社会稳定运行&#xff0c;广大人民群众利益的保障。 对于公司来讲&#xff0c;数据安全同样是企业可持续发展的重要保障&#xff0c;防止内部核心数据、知识产权的泄露是企业数据安全的重要工作。下面是五款企业常用的加密软件…

如何查看centos7是否安装nginx

要查看 CentOS 7 系统上是否安装了 Nginx&#xff0c;您可以使用多种方法来检查。以下是一些常见的方法&#xff1a; 通过 RPM 包管理器查询 在 CentOS 系统上&#xff0c;可以使用 RPM 包管理器来查询已安装的软件包。要查看是否安装了 Nginx&#xff0c;您可以在终端中运行以…

Spring框架概述

目录 1. Spring框架的起源 2. Spring框架的构成 3. Spring的发展历程 4. Spring的开发环境 4.1. Maven安装与配置 &#xff08;1&#xff09;Maven的下载与安装 &#xff08;2&#xff09;配置Maven的环境变量 &#xff08;3&#xff09;本地仓库的配置 &#xff08;4…

使用Baidu Comate五分钟 , 工作时间摸鱼8小时

Baidu Comate&#xff1a;引领智能编码新时代 文章目录 Baidu Comate&#xff1a;引领智能编码新时代一、明日工具&#xff0c;今日领先——百度Comate智能编码助手二、万变不离其宗——适配场景需求三、功能研究3.1 指挥如指掌——指令功能3.2 助手增援——插件功能使用3.3 实…

Raft论文阅读笔记+翻译:In Search of Understandable Consensus Algorithm

In Search of Understandable Consensus Algorithm 摘要 Raft是一种管理复制日志的共识算法。它产生与&#xff08;多&#xff09;Paxos等效的结果&#xff0c;并且与Paxos一样高效&#xff0c;但其结构与Paxos不同。这使得Raft比Paxos更易理解&#xff0c;也为构建实际系统提供…

VS2022 错误 LNK2001 无法解析的外部符号

错误 LNK2001 无法解析的外部符号 “private: static struct std::once_flag ThreadPool::flag_” (?flag_ThreadPool0Uonce_flagstdA) STL D:\VS2019\STL\源.obj 1 错误原因 &#xff1a;链接器无法解析 ThreadPool::flag_ 这个静态成员变量。这通常是因为静态成员变量在声明…

宁夏银川市起名专家的老师颜廷利:死神(死亡)并不可怕,可怕的是...

在中国优秀传统文化之中&#xff0c;汉语‘巳’字与‘四’同音&#xff0c;在阿拉伯数字里面&#xff0c;通常用‘4’来表示&#xff1b; 湖南长沙、四川成都、重庆、宁夏银川最靠谱最厉害的起名大师的老师颜廷利教授指出&#xff0c;作为汉语‘九’字&#xff0c;倘若是换一个…

《中国应急管理》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答&#xff1a; 问&#xff1a;《中国应急管理》杂志是核心期刊吗&#xff1f; 答&#xff1a;不是核心期刊&#xff0c;是正规学术期刊 问&#xff1a;《中国应急管理》杂志是电子版期刊吗&#xff1f; 答&#xff1a;不是&#xff0c;是纸质期刊 问&#xff1a;《…

IMDB的电影评论数据pytorh使用lstm

使用lstm对IMDB的电影评论数据进行情感分析&#xff08;pytorch代码&#xff09; 接下来让我们看看如何使用pytorch实现一个基于长短时记忆网络的情感分析模型。在飞桨中&#xff0c;不同深度学习模型的训练过程基本一致&#xff0c;流程如下&#xff1a; 数据处理&#xff1…

d17(154-168)-勇敢开始Java,咖啡拯救人生

目录 方法递归 字符集 编码-解码 IO流 字节流 字节输入流 InputSream FileInputStream 字节输出流 OutputSream FileOutputSream 释放资源的方式 try-catch-finallly try-with-resource 字符流 字符输入流 Reader FileReader 文件字符输出流 Writer FileWriter …

大数据分析案例-基于随机森林算法构建银行贷款审批预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

C#之partial关键字

在C#中&#xff0c;partial关键字用于声明一个类、结构体、接口或方法的分部定义。这意味着可以将一个类或其他类型的定义分成多个部分&#xff0c;这些部分可以在同一个命名空间或程序集中的多个源文件中进行定义。当编译器编译这些部分时&#xff0c;会将它们合并成一个单独的…

【RAG 论文】BGM:为 LLM 和 Retriever 的偏好 gap 搭建一个 Bridge

论文&#xff1a;Bridging the Preference Gap between Retrievers and LLMs ⭐⭐⭐ Google Research, arXiv:2401.06954 论文速读 LLM 与 Retriever 之间存在一个 preference gap&#xff1a;大多数 retriever 被设计为 human-friendly&#xff0c;但是 LLM 的偏好与人类的却…

网络配置的加密存储

随着数据泄露事件的增加&#xff0c;扰乱了公司的正常工作周期&#xff0c;企业遭受了损失。事实上&#xff0c;数据泄露可以通过存储加密来控制&#xff0c;存储加密是防止黑客对网络数据库造成严重破坏的最有效方法之一。在网络配置管理器中&#xff0c;存储加密可用于存储设…

C++数据结构——AVL树

目录 一、引言 1.1 二叉搜索树 1.2 二叉搜索树的缺陷 1.3 AVL数的引入 二、AVL树结点的定义 三、AVL树的操作 3.1 插入 3.1.1 基本步骤 3.1.2 平衡因子的调整 3.2 旋转操作(重点&#xff01;) 3.2.1 右单旋 3.2.2 左单旋 3.2.3 左右双旋 3.2.4 右左双旋 3.3 插入…