红黑树的学习

红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

image.png

对比一下:
AVLTree ,是严格平衡,左右的高度差 不超过 1
红黑树,是近似平衡,最长路径不会超过最短路径的 2倍

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

就是说,如果父节点是红色的,他的两个孩子也必须是红色的,不能出现连续的红色节点,即:父子节点的颜色只有这几种可能:父(黑) + 子(黑),父(黑)+ 子(红),父(红)+ 子(黑),

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

意思就是每条路径上都有相同数量的黑色节点

  1. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

这里的叶子节点不是指的传统意义上的叶子节点,而是指的空结点,也称NIL结点,这个NIL 节点的作用是方便我们数路劲,就是数这棵树有几条路径,比如下面的例子:
image.png

思考:为什么满足上
我们来分析一下,什k面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍? 🔎

我们来分析一下,什么情况下,路径是最短的?

根据性质4每条路径上都包含相同数目的黑色节点 , 那就说明如果一条路径上只有黑色节点,没有红色节点的话,更其他那些有红色节点的路劲相比,它就是最短的。
所以:

路劲最短 : 只有黑色节点的情况

我们再来分析一下,什么情况下,路径是最长的呢?

在相同黑色节点的条件下,红色节点的个数越多,路劲就越长,根据性质3,要尽可能的增加红色节点的个数的的话,它们之间的搭配条件要尽可能满足:父(黑)+ 子(红)+ 孙子(黑)+ 重孙子(红),这样的红黑红黑的交替的情况,所以最长的路径就是最短路径的2倍

路径最长 : 在黑色节点数目一定的情况下,红色节点的数目最多

综上所述,假设有N个黑色的节点,每条路径的的节点的个数的范围是[N,2N]

📌红黑树节点的定义

的定义:

enum Color{RED,BLACK};
// ekkkkk
kkkkk:
```c+num 是一个枚举类型,第一个RED是第一kkkkkk'k'k'k+
enum Color{RED,BLACKk'k'k'k;
// enum 是一个枚举类型,第一个RED是第一个枚举值,默认是 0、
// 第二个BLACK 是第二个枚举值,kkkkk
k'k'k'k
红黑树节点的定义:
```c++
template <class K, class V>
struct RBTreeNode
{RBTreeNode<K, V> *_left;RBTreeNode<K, V> *_right;RBTreeNode<K, V> *_parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V> &kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

🍉 上面的构造函数中,为什么默认节点的颜色要给成 红色呢?

  1. 保持树的黑色高度不变。
    因为红黑数的性质4: 对于每个结点,从该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点
  2. 减少插入时的调整操作:
    如果插入的是黑色节点,需要进行更多的调整操作来恢复红黑树的性质

📌红黑树的插入操作


template <class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;public:bool Inster(const pair<K, V> &kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK; // 如果是空的,那我们要插入一个节点,这个节点的颜色是黑色,因为性质2,跟节点的颜色是黑色的。return true;}Node *parent = nullptr;Node *cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_kv.first < kv.first;cur->_parent = parent;}else if{parent->_left = cur;cur->parent = parent;}return true;}private:Node *_root = nullptr;
};

我们插入节点的的时候,如果插入的是一个红色的节点,但是这个新增节点的父亲是黑色的,那么插入结束,我们可以不做任何处理。

image.png
但是,如果我们插入一个红色的节点,但是他的父亲也是红色的,这个时候该怎么处理呢?

image.png
以上的这种情况是违反规则 3 的,所以,我们必然要做的一步就是:把新增节点的父亲变成黑色 节点,然后,我们在根据规则对其他节点颜色的颜色进行相应的调整以维护红树黑树的性质

image.png

这么看来红黑树的插入操作挺麻烦的呀,那为之奈何呀?
其实也有一定的规律在里面,下面我们细细的分析

首先,我们新增一个节点,我们选择的是新增红色的节点,这样对整个树的影响会小一些

如果新增节点的父亲是黑色的,那我们不需要处理。
如果新增节点的父亲是红色的,那我们需要处理,处理的方式可能是:1. 旋转 2. 变色
有可能只变色就可以了,也有可能既要旋转又要变色。

第一种情况:

新增节点是红色的,新增节点的父亲也是红色的,祖父是黑色节点。并且叔叔存在而且叔叔也是红色的;

image.png

我们的解决办法是,
将父亲节点和叔叔节点变成红色,
祖父节点:

  1. 如果祖父节点是这棵树的根节点,那就必须是黑色,所以保持颜色不变。
  2. 如果祖父节点也只是这棵树的一个局部,那我们要把祖父节点变成红色的,这样才能保持这条路径中黑色节点的个数不变。

image.png
但是,这样我们忽略了一个问题:如果祖父节点的父亲(曾祖父)是红色的咋办?如下图:

image.png

遇到这种情况,首先,如果曾祖父是红色的,说明曾祖父不是根节点,因为根节点一定是黑色的,所以我们要往上走,把曾祖父当成当前节点,然后继续的看曾祖父的父节点和祖父节点,这样循环的往上处理。

第二种情况:

当前的节点是红色,父亲节点是红色,祖父节点是黑色,叔叔节点要么不存在,要么存在是黑色

cur : 表示当前节点
p : parent 表示父亲节点
g : grandpa 表示祖父节点
u : uncle 表示叔叔节点

  1. 当前节点是红色,父亲节点是红色,祖父节点是黑色,叔叔节点不存在:

image.png

image.png

解决方案:
parent 是 grandpa 的左孩子,cur 是parent 的左孩子,进行右单旋转
parent 是 grandpa 的右孩子,cur 是 parent的右边孩子,进行左单旋转
parent 和 grandpa 变色

  1. 如果叔叔存在且为黑色:

image.png

这种情况,不止需要变色,还需要旋转

假设每条路径黑色节点的数量h 个,
h <= 红黑树中任意一条路径长度 <= 2h
最短路径:h
最长的路径:2h
最长路径和最短路径是不一定存在的,红黑树是一种近似平衡

完整代码:

RBTree.h

#pragma once
#pragma onceenum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 新增节点给红色cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){//     g//   p   u// cNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上更新处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){// 单旋//     g//   p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 双旋//     g//   p//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else  // parent == grandfather->_right{//     g//   u   p //          c//Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   u   p //     c//RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}// 根节点->当前节点这条路径的黑色节点的数量bool Check(Node* root, int blacknum, const int refVal){if (root == nullptr){//cout << balcknum << endl;if (blacknum != refVal){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){++blacknum;}return Check(root->_left, blacknum, refVal)&& Check(root->_right, blacknum, refVal);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;//参考值int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refVal;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal);}private:Node* _root = nullptr;
};

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

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

相关文章

2025年01月31日Github流行趋势

项目名称&#xff1a;Qwen2.5项目地址url&#xff1a;https://github.com/QwenLM/Qwen2.5项目语言&#xff1a;Shell历史star数&#xff1a;13199今日star数&#xff1a;459项目维护者&#xff1a;jklj077, JustinLin610, bug-orz, huybery, JianxinMa项目简介&#xff1a;Qwen…

人工智能|基本概念|人工智能相关重要概念---AI定义以及模型相关知识

一、 前言&#xff1a; 最近deepseek&#xff08;深度求索&#xff09;公司的开源自然语言处理模型非常火爆。 本人很早就对人工智能比较感兴趣&#xff0c;但由于种种原因没有过多的深入此领域&#xff0c;仅仅是做了一点初步的了解&#xff0c;借着这个deepseek&#xff0…

Python GIL(全局解释器锁)机制对多线程性能影响的深度分析

在Python开发领域&#xff0c;GIL&#xff08;Global Interpreter Lock&#xff09;一直是一个广受关注的技术话题。在3.13已经默认将GIL去除&#xff0c;在详细介绍3.13的更亲前&#xff0c;我们先要留了解GIL的技术本质、其对Python程序性能的影响。本文将主要基于CPython&am…

Git 版本控制:基础介绍与常用操作

目录 Git 的基本概念 Git 安装与配置 Git 常用命令与操作 1. 初始化本地仓库 2. 版本控制工作流程 3. 分支管理 4. 解决冲突 5. 回退和撤销 6. 查看提交日志 前言 在软件开发过程中&#xff0c;开发者常常需要在现有程序的基础上进行修改和扩展。但如果不加以管理&am…

(笔记+作业)书生大模型实战营春节卷王班---L0G2000 Python 基础知识

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/QtJnweAW1iFl8LkoMKGcsUS9nld 课程视频&#xff1a;https://www.bilibili.com/video/BV13U1VYmEUr/ 课程文档&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L0/Python 关卡作业&#xff1a;htt…

仿真设计|基于51单片机的高速路口货车称重系统仿真

目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现&#xff08;protues8.7&#xff09; 程序&#xff08;Keil5&#xff09; 全部内容 资料获取 具体实现功能 &#xff08;1&#xff09;LCD1602液晶第一行显示当前的车辆重量&#xff0c;第二行显示车辆重量…

Ubuntu Server 安装 XFCE4桌面

Ubuntu Server没有桌面环境&#xff0c;一些软件有桌面环境使用起来才更加方便&#xff0c;所以我尝试安装桌面环境。常用的桌面环境有&#xff1a;GNOME、KDE Plasma、XFCE4等。这里我选择安装XFCE4桌面环境&#xff0c;主要因为它是一个极轻量级的桌面环境&#xff0c;适合内…

2025:影刀RPA使用新实践--CSDN博客下载

文章目录 一键CSDN博客下载器程序说明指导说明使用步骤 获取方法 一键CSDN博客下载器 程序说明 配置信息&#xff1a;CSDN账号&#xff08;手机号/邮箱/用户名&#xff09;、密码、博客文件类型支持markdown格式、html格式&#xff08;默认值markdown格式&#xff09;、博客保…

深度学习的应用

目录 一、机器视觉 1.1 应用场景 1.2 常见的计算机视觉任务 1.2.1 图像分类 1.2.2 目标检测 1.2.3 图像分割 二、自然语言处理 三、推荐系统 3.1 常用的推荐系统算法实现方案 四、图像分类实验补充 4.1 CIFAR-100 数据集实验 实验代码 4.2 CIFAR-10 实验代码 深…

前端js高级25.1.30

原型&#xff1a;函数的组成结构 通过这个图我们需要知道。 假设我们创建了一个Foo函数。 规则&#xff1a;Function.protoType是函数显示原型。__proto__是隐式对象。 Function、Object、Foo函数的__proto__指向了Function.protoType说明。这三个都依托function函数来创建。…

为AI聊天工具添加一个知识系统 之80 详细设计之21 符号逻辑 之1

本文要点 要点 前面我们讨论了本项目中的正则表达式。现在我们将前面讨论的正则表达式视为狭义的符号文本及其符号规则rule&#xff08;认识的原则--认识上认识对象的约束&#xff09;&#xff0c;进而在更广泛的视角下将其视为符号逻辑及其符号原则principle&#xff08;知识…

.NET Core缓存

目录 缓存的概念 客户端响应缓存 cache-control 服务器端响应缓存 内存缓存&#xff08;In-memory cache&#xff09; 用法 GetOrCreateAsync 缓存过期时间策略 缓存的过期时间 解决方法&#xff1a; 两种过期时间策略&#xff1a; 绝对过期时间 滑动过期时间 两…

自动驾驶---苏箐对智驾产品的思考

1 前言 对于更高级别的自动驾驶&#xff0c;很多人都有不同的思考&#xff0c;方案也好&#xff0c;产品也罢。最近在圈内一位知名的自动驾驶专家苏箐发表了他自己对于自动驾驶未来的思考。 苏箐是地平线的副总裁兼首席架构师&#xff0c;同时也是高阶智能驾驶解决方案SuperDri…

Sklearn 中的逻辑回归

逻辑回归的数学模型 基本模型 逻辑回归主要用于处理二分类问题。二分类问题对于模型的输出包含 0 和 1&#xff0c;是一个不连续的值。分类问题的结果一般不能由线性函数求出。这里就需要一个特别的函数来求解&#xff0c;这里引入一个新的函数 Sigmoid 函数&#xff0c;也成…

FPGA|使用quartus II通过AS下载POF固件

1、将开发板设置到AS下载挡位&#xff0c;或者把下载线插入到AS端口 2、打开quartus II&#xff0c;选择Tools→Programmer→ Mode选择Active Serial Programming 3、点击左侧Add file…&#xff0c;选择 .pof 文件 →start 4、勾选program和verify&#xff08;可选&#xff0…

浅谈网络 | 容器网络之Flannel

目录 云原生网络架构深度解构&#xff1a;Flannel的设计哲学与实现机制Flannel架构解析&#xff1a;三层核心设计原则UDP模式&#xff08;用户态隧道&#xff09;VXLAN模式&#xff08;内核态隧道&#xff09;Host-GW模式&#xff08;直连路由&#xff09; 生产环境架构选型与调…

hive:基本数据类型,关于表和列语法

基本数据类型 Hive 的数据类型分为基本数据类型和复杂数据类型 加粗的是常用数据类型 BOOLEAN出现ture和false外的其他值会变成NULL值 没有number,decimal类似number 如果输入的数据不符合数据类型, 映射时会变成NULL, 但是数据本身并没有被修改 创建表 创建表的本质其实就是在…

2025创业思路和方向有哪些?

创业思路和方向是决定创业成功与否的关键因素。以下是一些基于找到的参考内容的创业思路和方向&#xff0c;旨在激发创业灵感&#xff1a; 一、技术创新与融合&#xff1a; 1、智能手机与云电视结合&#xff1a;开发集成智能手机功能的云电视&#xff0c;提供通讯、娱乐一体化体…

Three.js实战项目02:vue3+three.js实现汽车展厅项目

文章目录 实战项目02项目预览项目创建初始化项目模型加载与展厅灯光加载汽车模型设置灯光材质设置完整项目下载实战项目02 项目预览 完整项目效果: 项目创建 创建项目: pnpm create vue安装包: pnpm add three@0.153.0 pnpm add gsap初始化项目 修改App.js代码&#x…

年化19.3%策略集|ctpbee_api替换成openctp整合backtrader实盘方案(代码+数据)

原创内容第782篇&#xff0c;专注量化投资、个人成长与财富自由。 昨天我们把backtraderctpbee的实盘整合代码跑通了&#xff0c;年化19.3%&#xff0c;回撤仅8%的实盘策略&#xff0c;以及backtrader整合CTPBee做实盘&#xff08;附python代码和数据&#xff09; 这两周我们加…