红黑树(RBTree)

红黑树

  • 1. 红黑树的概念
  • 2. 红黑树的性质
  • 3. 红黑树节点的定义
  • 4. 红黑树的插入操作
  • 5. 红黑树与AVL树的比较

1. 红黑树的概念

红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为「对称二叉B树」,它现代的名字源于Leo J. Guibas和罗伯特·塞奇威克于1978年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在O(log n)时间内完成查找、插入和删除,这里的n是树中元素的数目。红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:节点是红色或黑色。 根是黑色。 所有叶子都是黑色(叶子是NIL节点)。 每个红色节点必须有两个黑色的子节点。 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
在红黑树中,所有的叶子结点都是黑色的空节点(NIL节点)。NIL节点是红黑树额外引入的结点,在计算红黑数高度时NIL结点也会被计算在内。NIL结点指的是叶结点空的左右子结点延伸出来的的结点,也包括了叶子节点本身。
在这里插入图片描述

2. 红黑树的性质

  1. 每个结点不是红色就是黑色 ;
  2. 根节点是黑色的 ;
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 ;
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 ;
  5. 每个叶子结点都是黑色的( 此处的叶子结点指的是空结点(NIL节点) )。

这些约束确保了红黑树的关键特性从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。 因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

3. 红黑树节点的定义

// 节点的颜色
enum Color{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)  //默认为红节点{}
};

插入红色节点树的性质可能不会改变,而插入黑色节点每次都会违反性质4,所以 将节点设置为红色在插入时对红黑树造成的影响是小的,而黑色的影响是最大的

4. 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点;
  2. 检测新节点插入后,红黑树的性质是否造到破坏,如过性质被破坏,就要进行旋转或变色的操作,此时需要对红黑树分情况来讨论:
    以下为父节点在祖父的左边
    对于红黑树的插入来说,如果插入的父节点为黑,并且插入的节点默认为红节点,所以如果父节点为黑色时,插入是不需要进行调节的,如图1->2:

在这里插入图片描述
有2->3图可以看出cur、p两个红节点相连,所以需要调整,这种情况计为情况一:cur为红,p为红,g为黑,u存在且为红。(约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)
unlce节点的作用: 在红黑树中,uncle节点是指当前节点的父节点的兄弟节点。它的作用是在插入新节点时,如果当前节点的父节点和uncle节点都是红色,那么需要将当前节点的父节点和uncle节点染成黑色,将当前节点的祖父节点染成红色,然后以祖父节点为当前节点进行下一轮操作。这样可以保证红黑树的性质4(每个红色节点必须有两个黑色的子节点)不被破坏。
对图3进行调整,需要保证红色节点不能相连,并且从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。如果只将cur改为黑色,那么违反规则4;将cur和uncle改为黑色,也会违反规则四,因为如果这棵树是一个子树,那么g结点的左右路径是会多一个黑色结点。那么如何调整?
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整(因为g的父节点可能为红色)。 这样保证了每条路径上的黑色结点的个数都相同。
对于变色来说,不论cur在parent的左边还是右边,变色后各个节点的位置都没有改变,所以不需要分类讨论。
代码如下:

while (parent && parent->_col == RED)
{Node* grandfather = parent->_parent;if (grandfather->_left == parent){//情况1:uncle存在且为红Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上调整cur = grandfather;parent = cur->_parent;}}
}

cur为parent的左边,如下图又该怎样调整?
在这里插入图片描述

说明: u的情况有两种
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同;这时就需要旋转+变色处理。
2.如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。(如上图下半部分所示)
计上述情况为情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
对于上述两种情况我们发现怎么变色都是不行的,所以可以旋转+变色处理。旋转的详解如本篇博客(AVL树)
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红

cur为parent的右边,如下图又该怎样调整?
在这里插入图片描述
上述情况记为情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转则转换成了情况2。
如下图进行旋转+变色:
在这里插入图片描述

代码如下:

// 情况2和情况三3:u不存在/u存在且为黑,旋转+变色//     g//   p   u// c 
//cur为父亲节点的左边
if (cur == parent->_left)
{RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;
}
//cur为父亲节点的右边,在右边,需要双旋(如AVL树中的旋转)
else
{//     g//   p   u//    cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;
}

对于父节点在祖父节点的右边,这种情况和上述大致相同,就不在多画了,完整代码如下。
RBTree.h文件中的代码:

#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:Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first > key){cur = cur->_left;}else if (cur->_kv.first < key){cur = cur->_right;}else{return cur;}}return nullptr;}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){parent = cur;if (cur->_kv > kv){cur = cur->_left;}else if (cur->_kv < kv){cur = cur->_right;}else{return false;}}//插入新节点cur = new Node(kv);if (parent->_kv > kv){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//进行调整这棵红黑树while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//父节点在祖父的左边if (grandfather->_left == parent){//情况1:uncle存在且为红Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上调整cur = grandfather;parent = cur->_parent;}else  //叔叔不存在或叔叔存在且为黑,旋转+变黑{//cur为父亲节点的左边if (cur == parent->_left){//     g//   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //cur为父亲节点的右边{//     g//   p   u//    c RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//父节点在祖父的右边else // (grandfather->_right == parent){//    g//  u   p//        cNode* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//    g//  u   p//        cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//    g//  u   p//     cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//确保每次调整完后根节点为黑色_root->_col = BLACK;return true;}//因为红黑树的结点是new出来的,所以需要释放,也就是需要写析构函数~RBTree(){_Destroy(_root);_root = nullptr;}int Height(){return _Height(_root);}void InOrder(){_InOrder(_root);}
private:int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}void _Destroy(Node* root){if (root == nullptr)return;_Destroy(root->_left);_Destroy(root->_right);delete(root);}
private:Node* _root = nullptr;
};

测试代码:

#include <iostream>
using namespace std;#include "RBTree.h"void Test_RBTree1()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}t1.InOrder();
}
int main()
{Test_RBTree1();return 0;
}

运行结果如下:
在这里插入图片描述

5. 红黑树与AVL树的比较

1.红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log N),但它们的实现方式不同。
2.红黑树的查询性能略微逊色于AVL树,因为其比AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较
3.红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,红黑树在插入和删除上优于AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于AVL树为了维持平衡的开销要小得多。 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

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

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

相关文章

【前端二次开发框架关于关闭eslint】

前端二次开发框架关于关闭eslint 方法一方法二方法三方法四&#xff1a;以下是若想要关闭项目中的部分代码时&#xff1a; 方法一 在vue.config.js里面进行配置&#xff1a; module.exports {lintOnSave:false,//是否开启eslint保存检测 ,它的有效值为 true || false || err…

会一点stm32,只后是做嵌入式Linux还是转JAVA?

选择嵌入式Linux还是转向JAVA&#xff0c;取决于你的兴趣、职业规划和就业市场的需求。以下是一些考虑因素&#xff1a;兴趣和擅长&#xff1a;首先&#xff0c;你应该考虑自己对嵌入式Linux和JAVA的兴趣和擅长程度。如果你对嵌入式系统、硬件交互和底层编程更感兴趣&#xff0…

GPT-4 如何为我编写测试

ChatGPT — 每个人都在谈论它,每个人都有自己的观点,玩起来很有趣,但我们不是在这里玩— 我想展示一些实际用途,可以帮助您节省时间并提高效率。 我在本文中使用GPT-4 动机 我们以前都见过这样的情况——代码覆盖率不断下降的项目——部署起来越来越可怕,而且像朝鲜一样…

基于C#UI Automation自动化测试

步骤 UI Automation 只适用于&#xff0c;标准的win32和 WPF程序 需要添加对UIAutomationClient、 UIAutomationProvider、 UIAutomationTypes的引用 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.D…

C++11并发与多线程笔记(2)

C11并发与多线程笔记&#xff08;2&#xff09; 线程启动、结束&#xff0c;创建线程多法、join&#xff0c;detach 1. 范例演示线程运行的开始1.1 创建一个线程&#xff1a;1.2 join1.3 datch1.4 joinable 2. 其他创建线程的方法2.1 用类 重载了函数调用运算符2.2 lambda表达式…

Redis数据结构——Redis简单动态字符串SDS

定义 众所周知&#xff0c;Redis是由C语言写的。 对于字符串类型的数据存储&#xff0c;Redis并没有直接使用C语言中的字符串。 而是自己构建了一个结构体&#xff0c;叫做“简单动态字符串”&#xff0c;简称SDS&#xff0c;比C语言中的字符串更加灵活。 SDS的结构体是这样的…

Python-OpenCV中的图像处理-视频分析

Python-OpenCV中的图像处理-视频分析 视频分析Meanshift算法Camshift算法光流 视频分析 学习使用 Meanshift 和 Camshift 算法在视频中找到并跟踪目标对象: Meanshift算法 Meanshift 算法的基本原理是和很简单的。假设我们有一堆点&#xff08;比如直方 图反向投影得到的点&…

ASR 语音识别接口封装和分析

这个文档主要是介绍一下我自己封装了 6 家厂商的短语音识别和实时流语音识别接口的一个包&#xff0c;以及对这些接口的一个对比。分别是&#xff0c;阿里&#xff0c;快商通&#xff0c;百度&#xff0c;腾讯&#xff0c;科大&#xff0c;字节。 zxmfke/asrfactory (github.c…

ubuntu 安装 cuda

ubuntu 安装 cuda 初环境与设备在官网找安装方式 本篇文章将介绍ubuntu 安装 CUDA Toolkit CUDA Toolkit 是由 NVIDIA&#xff08;英伟达&#xff09;公司开发的一个软件工具包&#xff0c;用于支持并优化 GPU&#xff08;图形处理器&#xff09;上的并行计算和高性能计算。它…

解析TCP/IP协议的分层模型

了解ISO模型&#xff1a;构建通信的蓝图 为了促进网络应用的普及&#xff0c;国际标准化组织&#xff08;ISO&#xff09;引入了开放式系统互联&#xff08;Open System Interconnect&#xff0c;OSI&#xff09;模型。这个模型包括了七个层次&#xff0c;从底层的物理连接到顶…

一、Dubbo 简介与架构

一、Dubbo 简介与架构 1.1 应用架构演进过程 单体应用&#xff1a;JEE、MVC分布式应用&#xff1a;SOA、微服务化 1.2 Dubbo 简介一种分布式 RPC 框架&#xff0c;对专业知识&#xff08;序列化/反序列化、网络、多线程、设计模式、性能优化等&#xff09;进行了更高层的抽象和…

ArcGIS Maps SDK for JavaScript系列之三:在Vue3中使用ArcGIS API加载三维地球

目录 SceneView类的常用属性SceneView类的常用方法vue3中使用SceneView类创建三维地球项目准备引入ArcGIS API创建Vue组件在OnMounted中调用初始化函数initArcGisMap创建Camera对象Camera的常用属性Camera的常用方法 要在Vue 3中使用ArcGIS API for JavaScript加载和展示三维地…

Java旋转数组中的最小数字(图文详解版)

目录 1.题目描述 2.题解 分析 具体实现 方法一&#xff08;遍历&#xff09;&#xff1a; 方法二&#xff08;排序&#xff09;&#xff1a; 方法三&#xff08;二分查找&#xff09;&#xff1a; 1.题目描述 有一个长度为 n 的非降序数组&#xff0c;比如[1,2,3,4,5]&a…

npm install 中 --save 和 --save-dev 是什么?

npm&#xff0c;全名 Node Package Manager&#xff0c;套件管理工具&#xff0c;package.json 会记下你在项目中安装的所有套件。 假设在项目中安装 lodash npm i --save lodash这样在 dependencies 中会出现&#xff1a; 如果修改了导入方式&#xff1a; npm i --save-dev …

Labview解决“重置VI:xxx.vi”报错问题

文章目录 前言一、程序框图二、前面板三、问题描述四、解决办法 前言 在程序关闭前面板的时候小概率型出现了 重置VI&#xff1a;xxx.vi 这个报错&#xff0c;并且发现此时只能通过任务管理器杀掉 LabVIEW 进程才能退出&#xff0c;这里介绍一下解决方法。 一、程序框图 程序…

pve7.2虚拟机 lvm磁盘扩容,增加硬盘操作

之前安装pve时候只有256的ssd,最近安装的虚拟机较多&#xff0c;给加块闲置硬盘&#xff0c;顺便学习一下&#xff0c;像pve这种虚拟机系统&#xff0c;硬盘应该可以像nas你这样随时增加&#xff0c;而不影响上层应用&#xff0c;我自己也是摸索着做。 一、安装好硬盘后打开pv…

vue3+ts-tsconfig.json报错Option ‘importsNotUsedAsValues’

vue3ts-tsconfig.json报错Option ‘importsNotUsedAsValues’ is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption ‘“ignoreDeprecations”: “5.0”’ to silence this error. Use ‘verbatimModuleSyntax’ instead 自我记录 翻译 选项…

echart 3d立体颜色渐变柱状图

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; 1.需求描述 根据业务需求将不同的法律法规&#xff0c;展示不同的3d立体渐变柱状图。 2.先看下效果图 3. 确定三面的颜色&#xff0c;这里我是自定义的颜色 // 右面生成颜色const rightColorArr ref(["#79D…

ComponentOne Studio ASP.NET MVC Crack

ComponentOne Studio ASP.NET MVC Crack FlexReport增强功能 添加了对在Microsoft Windows上部署Microsoft Azure的支持。 添加了对显示嵌入字体的支持。 .NET标准版的经典C1PDF(Beta版) GrapeCity的经典C1Pdf库现在提供了基于Microsoft.NET标准的版本。在任何.NET应用程序(包括…

江南大学计算机考研分析

24计算机考研|上岸指南 江南大学 江南大学计算机考研招生学院是人工智能与计算机学院。目前均已出拟录取名单。 江南大学人工智能与计算机学院成立于2020年3月&#xff0c;办学历史可追溯到1994年设立的计算机应用专业。学院秉持江南大学“彰显轻工特色&#xff0c;服务国计民…