当前位置: 首页 > news >正文

C++模拟实现红黑树

目录

介绍----什么是红黑树

 甲鱼的臀部----规定

分析思考

绘图解析+代码实现

节点部分

插入部分+分步解析

●父亲在祖父的左,叔叔在祖父的右:

●父亲在祖父的右,叔叔在祖父的左:

测试部分

整体代码


介绍----什么是红黑树

红黑树基于二叉搜索树,它和AVL树一样避免了二叉搜索树中极端场景单边树的情况,保证了检索的效率。红黑树允许最长路径是最短路径的二倍,相较于AVL树而言是近似于平衡的状态,但是减少了旋转的次数。

 甲鱼的臀部----规定

●每个节点都有颜色,不是红色就是黑色。

●根节点一定是黑色的。

●不能连续出现两个红色的节点。

●每条路径上的黑色节点数量相同。

●每个空节点都是黑色的。

●最多允许一条路径上的节点是另一条路径上节点的二倍。

分析思考

节点默认颜色应该选红色还是黑色,为什么?

答:插入红色可能会出现连续的红色节点,插入黑色会改变当前路径上的黑色节点数。选择默认插入节点为红色的原因是插入后的影响会小一些,当父节点是黑色时不用调整。相反的,黑色节点的插入一定会违反红黑树的规则。

满足上述特性为什么能保证最长路径不会超过最短路径的2倍?

答:这里要关注红黑树的两个特性,红色节点的孩子节点一定是黑色,每条路径的黑色节点树相同。也就说最长路径是红色节点和黑色交替,最短路径是全黑节点。这样一来,最长路径和最短路径间的差距就控制在了规定范围内。

绘图解析+代码实现

节点部分

三叉链结构分别指向左右孩子和父亲节点,数据方面存储键值对,除此之外还需要一个记录节点颜色的变量。

enum Color
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V> _kv;Color _color;RBTreeNode(pair<K, V> kv, Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(color){}
};

插入部分+分步解析

红黑树是二叉搜索树,插入节点的规则和二叉搜索的特点一样:

   typedef RBTreeNode<K,V> Node;bool inster(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_color = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;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);cur->_color = RED;if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//节点插入后,需要检查红黑树的特性有没有被破坏掉//......}

上述我们谈论过新节点的颜色默认为红色的原因,那么接下来的插入如果新节点插入后其父节点的颜色是黑色,那么直接插入即可。如果其父节点的颜色为红色,那么就出现了两个红色节点出现的情况,此时就需要进行调整:

 

关注:红黑树的处理方法重点关注这样的几个节点:cur(新增节点)、parent(父亲节点)、uncle(叔叔节点)、grandfather(祖父节点)。

当节点的插入违反了红黑树“龟腚”时,对应不同的情况分别处理:

●父亲在祖父的左,叔叔在祖父的右:

情况1:cur为红,parent红色,叔叔节点存在且是红色,祖父是黑色;

 

如上图所示,情况1的处理方法就是将父亲和叔叔的节点变为黑色,祖父的节点变为红色。对于祖父为什么要变红是基于这样的考虑,我们当前的处理很可能只是在子树当中,如果祖父节点保持黑色不变,对于子树这两条路径而言,增加了黑色节点,这样一来就“拆东墙补了西墙”,又违反了另一条规则。如果祖父节点是根节点,改为红色当然是不合理的,我们在最后做下强制处理即可。

注意:这种情况可能是局部的处理,当祖父节点变红且不是根节点时,可能会破坏规则,所以针对情况1需要继续向上查找,也就是将祖父节点看成新增节点,祖父的父亲看做新增节点的父节点,继续向上更新检查树结构。

                if (uncle && uncle->_color == RED){//情况1,叔叔节点存在,且是红色的parent->_color = uncle->_color = BLACK;grandfater->_color = RED;//更新cur和parent的位置cur = grandfater;parent = cur->_parent;}

情况2:cur为红(插入或者调整后和父亲祖父为一条直线),parent红色,叔叔不存在或者叔叔是黑色,祖父是红色。

当叔叔不存在时:这种情况较好分析,新节点插入后左边高,左边高度超过右边的2倍,进行一次右单旋,祖父变成红色,父亲变为黑色,这颗树调整完毕,不管它是不是子树都不会向上影响。

叔叔存在且为黑色,观察下图,每条路径上的黑色节点数量不相等,所以cur的位置应该是从黑色调整为的红色。

注意:这里不要受图示的影响,感觉uncle路径上的黑节点好像是多一个。调整前后的每条路径数量都是相等的,想象小三角中有着不同的结构就可以了。

叔叔的存在与否只是分析的过程不同,它们的代码处理方式是一样的: 

                    //在一条线上if (parent->_left == cur){//情况2,叔叔节点不存在或者存在是黑色的//右单旋RotaR(grandfater);parent->_color = BLACK;grandfater->_color = RED;}

 

情况3:cur为红(插入或者调整后和父亲祖父为线一条折),parent红色,叔叔不存在或者叔叔是黑色,祖父是红色。

uncle不存在:

 uncle存在:

                      if(cur == parent->_left){//情况3,叔叔节点存在是黑色的,在一条折线上RotaL(parent);RotaR(grandfater);cur->_color = BLACK;grandfater->_color = RED;}

●父亲在祖父的右,叔叔在祖父的左:

处理大思路和上述一致,只是叔叔和父亲交换了位置,不在画图分析,列举处理要点:
情况1:叔叔存在且为红色,叔叔和父亲变黑,祖父变红。向上继续查找。

                if (uncle && uncle->_color == RED){//情况1,叔叔存在且是红色parent->_color = uncle->_color = BLACK;grandfater->_color = RED;//继续向上更新cur = grandfater;parent = cur->_parent;}

情况2:叔叔不存在或者存在为黑色,cur的位置和父亲祖父是一条直线,右边高,以祖父为轴点左单旋。祖父变红,父亲变黑。

                   //在右边插入if (parent->_right == cur){//进行一个左单旋RotaL(grandfater);//父亲的颜色变成黑色parent->_color = BLACK;//祖父的的颜色变成红色grandfater->_color = RED;}

情况3:叔叔不存在或者存在为黑色,cur的位置和父亲祖父是一条折线,先以父亲为轴右单旋,以祖父为轴点左单旋祖父变红,cur变黑。

                    if(cur == parent->_left)//左边插入{//右单旋RotaR(parent);//左单旋RotaL(grandfater);//改变颜色grandfater->_color = RED;cur->_color = BLACK;}

测试部分

因为红黑树是二叉搜索树,在测试时很可能会有隐式的错误,比较难发现,所以需要下面的测试接口对红黑树的特性进行检查,检查是否有连续的红色节点出现,每条路径上的黑色节点是否相等

bool check(Node* root,int num,int BLACKNUM){if (root == nullptr){if (num != BLACKNUM){cout << "某条路径上的黑色节点和其他路径不相等" << endl;return false;}return true;}if (root->_color == RED && root->_parent->_color == RED){cout << "连续出现两个红色节点" << endl;return false;}if (root->_color == BLACK){num++;}return check(root->_left, num, BLACKNUM)&&check(root->_right, num, BLACKNUM);}bool IsBalance(){if (_root == nullptr){return false;}//根节点的颜色一定要是黑色if (_root->_color != BLACK){return false;}Node* ret = _root;int _blacknum = 0;while (ret){if (ret->_color == BLACK){_blacknum++;}ret = ret->_left;}return check(_root,0,_blacknum);}

整体代码

#include <time.h>
enum Color
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V> _kv;Color _color;RBTreeNode(pair<K, V> kv, Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(color){}
};template <class K,class V>
class RBTee
{
public:typedef RBTreeNode<K,V> Node;bool inster(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_color = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;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);cur->_color = RED;if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//插入的节点默认是红色的while (parent && parent->_color == RED){Node* grandfater = parent->_parent;if (parent == grandfater->_left){Node* uncle = grandfater->_right;if (uncle && uncle->_color == RED){//情况1,叔叔节点存在,且是红色的parent->_color = uncle->_color = BLACK;grandfater->_color = RED;//更新cur和parent的位置cur = grandfater;parent = cur->_parent;}else {//左边新增if (parent->_left == cur){//情况2,叔叔节点存在是黑色的,在一条线上//右单旋RotaR(grandfater);parent->_color = BLACK;grandfater->_color = RED;}//右边新增else{//情况3,叔叔节点存在是黑色的,在一条折线上RotaL(parent);RotaR(grandfater);cur->_color = BLACK;grandfater->_color = RED;}//树进行了旋转调整,已经平衡,跳出循环break;}}else //parent在grandfater的右边{Node* uncle = grandfater->_left;if (uncle && uncle->_color == RED){//情况1,叔叔存在且是黑色parent->_color = uncle->_color = BLACK;grandfater->_color = RED;//继续向上更新cur = grandfater;parent = cur->_parent;}else//uncle的颜色是黑色{//在右边插入if (parent->_right == cur){//进行一个左单旋RotaL(grandfater);//父亲的颜色变成黑色parent->_color = BLACK;//祖父的的颜色变成红色grandfater->_color = RED;}else//左边插入{//右单旋RotaR(parent);//左单旋RotaL(grandfater);//改变颜色grandfater->_color = RED;cur->_color = BLACK;}break;}}}_root->_color = BLACK;return true;}void Inorder(){_Inorder(_root);}bool check(Node* root,int num,int BLACKNUM){if (root == nullptr){if (num != BLACKNUM){cout << "某条路径上的黑色节点和其他路径不相等" << endl;return false;}return true;}if (root->_color == RED && root->_parent->_color == RED){cout << "连续出现两个红色节点" << endl;return false;}if (root->_color == BLACK){num++;}return check(root->_left, num, BLACKNUM)&&check(root->_right, num, BLACKNUM);}bool IsBalance(){if (_root == nullptr){return false;}//根节点的颜色一定要是黑色if (_root->_color != BLACK){return false;}Node* ret = _root;int _blacknum = 0;while (ret){if (ret->_color == BLACK){_blacknum++;}ret = ret->_left;}return check(_root,0,_blacknum);}
private:void _Inorder(Node* root){if (root == nullptr){return ;}_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.first << endl;_Inorder(root->_right);}void RotaL(Node* pparent){Node* subR = pparent->_right;Node* subRL = subR->_left;pparent->_right = subRL;if (subRL){subRL->_parent = pparent;}Node* pNode = pparent->_parent;pparent->_parent = subR;subR->_left = pparent;if (pNode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (pNode->_left == pparent){pNode->_left = subR;}else if (pNode->_right == pparent){pNode->_right = subR;}subR->_parent = pNode;}}void RotaR(Node* pparent){Node* subL = pparent->_left;Node* subLR = subL->_right;pparent->_left = subLR;if (subLR != nullptr){subLR->_parent = pparent;}Node* pNode = pparent->_parent;subL->_right = pparent;pparent->_parent = subL;if (pNode == nullptr){_root = subL;_root->_parent = nullptr;}else{if (pNode->_left == pparent){pNode->_left = subL;}else{pNode->_right = subL;}subL->_parent = pNode;}}private:Node* _root = nullptr;
};

相关文章:

C++模拟实现红黑树

目录 介绍----什么是红黑树 甲鱼的臀部----规定 分析思考 绘图解析代码实现 节点部分 插入部分分步解析 ●父亲在祖父的左&#xff0c;叔叔在祖父的右&#xff1a; ●父亲在祖父的右&#xff0c;叔叔在祖父的左&#xff1a; 测试部分 整体代码 介绍----什么是红黑树 红…...

HTTPS协议之SSL/TLS详解(下)

目录 前言&#xff1a; SSL/TLS详解 HTTP协议传输安全性分析 对称加密 非对称加密 证书 小结&#xff1a; 前言&#xff1a; 在网络世界中&#xff0c;存在着运营商劫持和一些黑客的攻击。如果明文传输数据是很危险的操作&#xff0c;因为我们不清楚中间传输过程中就被哪…...

OLE对象是什么?为什么要在CAD图形中插入OLE对象?

OLE对象是什么&#xff1f;OLE对象的意思是指对象连接与嵌入。那为什么要在CAD图形中插入OLE对象&#xff1f;一般情况下&#xff0c;在CAD图形中插入OLE对象&#xff0c;是为了将不同应用程序的数据合并到一个文档中。本节内容小编就来给大家分享一下在CAD图形中插入OLE对象的…...

【微信小程序】-- 自定义组件 -- 数据、方法和属性(三十三)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…...

【Spring 深入学习】AOP的前世今生之代理模式

AOP的前世今生之代理模式1. 概述 什么是代理模式呢&#xff1f;&#xff1f;&#xff1f; 在不修改原有代码 或是 无法修改原有代码的情况下&#xff0c;增强对象功能&#xff0c;替代原来的对象去完成功能&#xff0c;从而达成了拓展的目的。 先给大家看下 JavaScript中实现方…...

操作系统复试

2017软学 给出操作系统的定义&#xff0c;分别从资源管理&#xff0c;任务调度&#xff0c;用户接口等三个方面论述操作系统的职能 操作系统是位于硬件层之上、所有其他系统软件层之下的一个系统软件&#xff0c;使得管理系统中的各种软件和硬件资源得以充分利用&#xff0c;方…...

藏经阁(五)温湿度传感器 SHT3x-DIS 手册 解析

文章目录芯片特性芯片内部框图芯片引脚定义芯片温湿度范围芯片寄存器以及时序讲解信号转换公式芯片特性 湿度和温度传感器完全校准&#xff0c;线性化温度补偿数字输出供电电压范围宽&#xff0c;从2.4 V到5.5 VI2C接口通讯速度可达1MHz和两个用户可选地址典型精度 2% RH和 0.…...

PCB焊盘设计基本原则

SMT的组装质量与PCB焊盘设计有直接的关系&#xff0c;焊盘的大小比例十分重要。如果PCB焊盘设计正确&#xff0c;贴装时少量的歪斜可以再次回流焊纠正(称为自定位或自校正效应)&#xff0c;相反&#xff0c;如果PCB焊盘设计不正确&#xff0c;即使贴装位置十分准确&#xff0c;…...

mysql锁分类大全

前言 为什么会出现锁 MySQL中的锁是为了保证并发操作的正确性和一致性而存在的。 当多个用户同时对同一份数据进行操作时&#xff0c;如果不加控制地进行读写操作&#xff0c;就可能导致数据不一致的问题。例如&#xff0c;当多个用户同时对同一行数据进行写操作时&#xff…...

推荐几款主流好用的远程终端连接管理软件

一、介绍 远程终端连接管理软件是管理服务器、虚拟机等远程计算机系统不可或缺的工具之一&#xff0c;它可以通过网络连接到另一台计算机&#xff0c;以执行命令、编辑文件或进行其他管理任务&#xff0c;下面我将为大家介绍几款主流好用的远程终端连接管理软件&#xff0c;并…...

描述性统计

参考文献 威廉 M 门登霍尔 《统计学》 文章目录定性数据的描述方法条形图饼图帕累托图定量数据点图茎叶图频数分布直方图MINITAB 工具在威廉《统计学》一书将统计学分为描述统计学和推断统计学&#xff0c;他们的定义分别如下&#xff1a;描述统计学&#xff1a;致力于数据集的…...

第十四届蓝桥杯三月真题刷题训练——第 7 天

目录 第 1 题&#xff1a;三角回文数 问题描述 答案提交 运行限制 代码&#xff1a; 第 2 题&#xff1a;数数 问题描述 答案提交 运行限制 代码&#xff1a; 第 3 题&#xff1a;倍数问题_同余定理_分情况讨论 题目描述 输入描述 输出描述 输入输出样例 运行限…...

剑指 Offer 57. 和为s的两个数字

一、题目 输入一个递增排序的数组和一个数字s&#xff0c;在数组中查找两个数&#xff0c;使得它们的和正好是s。如果有多对数字的和等于s&#xff0c;则输出任意一对即可。 示例 1&#xff1a; 输入&#xff1a;nums [2,7,11,15], target 9 输出&#xff1a;[2,7] 或者 [7…...

PDF转word在线转换方法!操作简单又高效

相信很多已经工作的人都知道&#xff0c;PDF文件格式的优点在于兼容性强、安全性高&#xff0c;而且查看和传输给他人都很方便。但是&#xff0c;这种格式的文件也有不太方便的地方&#xff0c;那就是不能对文件内容进行编辑和修改。对于许多人来说&#xff0c;如果想要编辑修改…...

Jquery项目中使用vue.js

大家在工作的情况中&#xff0c;可能会遇到之前的老项目采用jq书写&#xff0c;或者修改或者新增功能在jq中&#xff0c;原始jq的项目,代码可维护性很差,一个页面几千行jq,可维护性很差,工作量巨大&#xff0c;所以这个时候大家可以引入vue.js。 第一步&#xff1a;引入vue.js…...

蓝桥杯 删除字符

题目描述 给定一个单词&#xff0c;请问在单词中删除 t 个字母后&#xff0c;能得到的字典序最小的单词是什么&#xff1f; 输入描述 输入的第一行包含一个单词&#xff0c;由大写英文字母组成。 第二行包含一个正整数 t。 其中&#xff0c;单词长度不超过 100&#xff0c…...

析构函数 对象数组 对象指针

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章 &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下定决心去做” &#x1…...

Vue对Axios网络请求进行封装

一、为什么要对网络请求进行封装&#xff1f; 因为网络请求的使用率实在是太高了&#xff0c;我们有的时候为了程序的一个可维护性&#xff0c;会把同样的东西放在一起&#xff0c;后期找起来会很方便&#xff0c;这就是封装的主要意义。 二、如何进行封装&#xff1f; 1、将…...

Android framework HAL(HIDL)

简述 当你在Android系统中使用不同的硬件设备&#xff08;例如摄像头、传感器、音频设备等&#xff09;时&#xff0c;你需要与硬件抽象层&#xff08;HAL&#xff09;进行通信。 HAL是一个中间层&#xff0c;它充当了硬件和应用程序之间的桥梁。但是&#xff0c;由于硬件设备…...

QML 模型(ListModel)

LIstModel&#xff08;列表模型&#xff09; ListModel 是ListElement定义的简单容器&#xff0c;每个定义都包含数据角色。内容可以在 QML 中动态定义或显式定义。 属性&#xff1a; count模型中数据条目的数量dynamic动态角色&#xff0c;默认情况下&#xff0c;角色的类型…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...