红黑树的 概念性质 和 详解实现(插入旋转等)

news/2024/4/19 7:52:17/文章来源:https://blog.csdn.net/Dreaming_TI/article/details/131010544

文章目录

  • 概念
  • 满足的条件性质
  • 实现
    • 红黑树的定义
    • 红黑树节点
    • 插入操作
      • 情况一
      • 情况二
      • 情况三
      • Insert()总代码
    • 其余操作
      • 左右单旋
      • RotateL 左单旋
      • RotateR 右单旋
      • prevCheck 红黑树性质检测
      • isBalance 红黑树平衡判断
      • InOrder 中序遍历
  • 完整代码

概念

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

满足的条件性质

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

这些性质保证了红黑树的高度不会超过 2log(n+1) ,其中 n 是树中节点的个数。因此,红黑树可以在O(log n)时间内完成插入、删除、查找等操作,是一种非常高效的数据结构。

这里提一下 NIL节点


nil 节点就是空节点,在红黑树的实现中,nil 节点代替二叉树中的 NULL:叶子节点的左节点和右节点指针都指向 nil 节点;只一个子树的节点,其另外一个子节点指针也指向 nil 节点;根节点的父节点也指向 nil 节点。


当我们从红黑树中删除一个节点时,需要考虑其子树的平衡问题,否则可能导致树不平衡,进而破坏红黑树性质。如果该节点只有一个子节点,我们需要将该节点的子节点替代该节点的位置。如果该节点没有子节点,则在其位置插入一个NIL节点,起到保持红黑树平衡的作用。

实现

红黑树的定义

// 枚举类型 标明颜色
// 节点颜色
enum 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){}
};// 红黑树定义
template <class K, class V>
struct RBTree
{//typedef节点typedef RBTreeNode<K, V> Node;
public:// 关键:插入操作bool Insert(const pair<K, V> kv){}// 中序遍历void InOrder(){_InOrder(_root);cout << endl;}// 判断平衡bool IsBalance(){}private:// 中序遍历void _InOrder(Node* root){}// 检查红黑树// 会先判断当前节点是否为nullptr// 如果是,则说明检查到叶节点,记录当前节点中黑色节点的数量,并且与之前的比较// 如果不相等,则输出错误信息并返回false。如果相等,则返回true。bool prevCheck(Node* root, int blackNum, int& benchmark){}// 左单旋void RotateL(Node* parent){}// 右单旋void RotateR(Node* parent){}private:Node* _root = nullptr;
};

红黑树节点

  • _left:表示当前节点的左子节点指针;
  • _right:表示当前节点的右子节点指针;
  • _parent:表示当前节点的父节点指针;
  • _kv:表示当前节点存储的键值对;
  • _col:表示当前节点的颜色,用于约束红黑树的性质。
  • 构造函数:用于初始化成员变量;
// 枚举类型 标明颜色
// 节点颜色
enum 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){}
};

插入操作

我们将插入操作分为三个部分进行:

1. 判断根节点是否为空,如果为空,则直接将新节点作为根节点创建并返回true

// 第一部分
// 1. 判断根节点是否为空,如果为空,则直接将新节点作为根节点创建并返回true。
if (_root == nullptr)
{_root = new Node(kv);_root->_col = BLACK; //根节点为黑色return true;
}

2. 在树中寻找待插入节点要插入的位置,当遍历到叶子节点时,将新节点加入到该叶子节点的左边或右边,成为该叶子节点的子节点。

  • 由于红黑树本身是一种平衡二叉树,在插入操作中先进行平衡二叉树一样的操作,第二部分就是进行平衡二叉树的节点插入操作。
// 第二部分
// 2. 在树中寻找待插入节点要插入的位置,当遍历到叶子节点时,将新节点加入到该叶子节点的左边或右边,成为该叶子节点的子节点。Node* parent = nullptr;
Node* cur = _root;
// 如果cur->_kv.first小于kv.first,则将cur指向右子节点,并且parent指向cur;否则将cur指向左子节点,并且parent指向cur。
// 如果插入的键值已存在,则直接返回false
while (cur)
{// 插入节点大,向右插入if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}// 插入节点小,向左插入else if (kv.first < cur->_kv.first) {parent = cur;cur = cur->_left;}else {return false;}
}// 判断kv应该插入parent的左边or右边
cur = new Node(kv);
if (kv.first > parent->_kv.first) {parent->_right = cur;
}
else {parent->_left = cur;
}cur->_parent = parent;

3. 当新节点的父节点是红色,而叔节点是黑色(或者不存在),并且新节点是其父节点的左子节点,则需要进行右旋,然后进行变色操作,使之平衡。

对于红黑树的平衡我们分为三种情况:

首先我们令 插入的节点为 cur父节点为 parentparent的父节点为grandparent父节点同一层的另一个节点grandparent的另一个子节点)为uncle


情况一

情况一:

  • 当新节点的父节点和叔节点都是红色时,需要将父节点和叔节点变为黑色,将祖父节点变为红色,并将新节点指向祖父节点,然后继续向上处理。

cur为红色,parent为红色,grandparent为黑色,uncle存在且为红色

在这里插入图片描述

在这里插入图片描述

if (uncle && uncle->_col == RED)
{// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;
}
// 根节点变为黑色
_root->_col = BLACK;

情况二

情况二:

  • 当新节点的父节点是红色,而叔节点是黑色(或者不存在),并且新节点是其父节点的右子节点,则需要进行左旋,变成第三种情况。

cur为红色,parent为红色,grandparent为黑色,uncle不存在 / 存在且为黑色,且cur为parent的右子节点

u不存在

在这里插入图片描述
u存在且为黑色

在这里插入图片描述
在这里插入图片描述

// 情况二 : cur为红,p为红,g为黑,u不存在 / u为黑
// -> 右单旋+变色 -> 右单旋+p变黑,g变红
if (cur == parent->_left)
{// 右单旋 祖父节点RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;
}

情况三

情况三:

  • 当新节点的父节点是红色,而叔节点是黑色(或者不存在),并且新节点是其父节点的左子节点,则需要进行右旋,然后进行变色操作,使之平衡。

cur为红色,parent为红色,grandparent为黑色,uncle不存在 / 存在且为黑色,且cur为parent的左子节点

u不存在

在这里插入图片描述
u存在且为黑

在这里插入图片描述
在这里插入图片描述

// 情况三 : cur为红,p为红,g为黑,u不存在 / u存在且为黑
// 双旋 + 变色
if (cur == parent->_right)
{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;
}

Insert()总代码

  1. 根据上文分析的三种情况总结并补充其余细节写出插入操作的总实现
bool Insert(const pair<K, V> kv)
{// 二叉平衡树的插入操作// 1. 判断根节点是否为空,如果为空,则直接将新节点作为根节点创建并返回true。if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK; //根节点为黑色return true;}// 2. 在树中寻找待插入节点要插入的位置,当遍历到叶子节点时,将新节点加入到该叶子节点的左边或右边,成为该叶子节点的子节点。Node* parent = nullptr;Node* cur = _root;// 根据 kv.first 的大小逐步向左或右寻找合适的插入位置:// 如果cur->_kv.first小于kv.first,则将cur指向右子节点,并且parent指向cur;否则将cur指向左子节点,并且parent指向cur。// 如果插入的键值已存在,则直接返回falsewhile (cur){// 插入节点大,向右插入if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}// 插入节点小,向左插入else if (kv.first < cur->_kv.first) {parent = cur;cur = cur->_left;}else {return false;}}// 判断cur应该插入parent的左边or右边cur = new Node(kv);if (kv.first > parent->_kv.first) {parent->_right = cur;}else {parent->_left = cur;}cur->_parent = parent;// 3. 插入新节点后,需要通过旋转和变色实现红黑树的自平衡。// 如果新节点的父节点是黑色的,则直接插入不需要调整。如果父节点是红色的,则需要进行适当的旋转和变色使之平衡。while (parent && parent->_col == RED) {// 定义祖父节点Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK); //根据红黑树的性质,此时grandparent节点应该是黑色的// 平衡主要看 uncle节点if (parent == grandfather->_left) {Node* uncle = grandfather->_right;// 情况一 : uncle 存在且为红 -> 变色+继续向上处理if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else{// 情况二 : cur为红,p为红,g为黑,u不存在 / u为黑// -> 右单旋+变色 -> 右单旋+p变黑,g变红if (cur == parent->_left){// 右单旋 祖父节点RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// 情况三 : cur为红,p为红,g为黑,u不存在 / u存在且为黑// 双旋 + 变色else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}// (parent == grandfather->_right)else {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;}break;}}}// 根节点为黑色// 返回true;_root->_col = BLACK;return true;
}

其余操作

左右单旋

这里不再进行旋转的详细解释,具体在下面的文章中:

AVLTree 的插入旋转操作

RotateL 左单旋

void RotateL(Node* parent)
{// 保存父节点右子树的左子树 subRLNode* subR = parent->_right;Node* subRL = subR->_left;// 将 parent 的右子树指向 subRLparent->_right = subRL;if (subRL)subRL->_parent = parent;// 调整 subR 和 parent 的关系subR->_left = parent;parent->_parent = subR;// 如果此时 parent 是根节点,修改根节点为 subRif (_root == parent){_root = subR;subR->_parent = nullptr;}else{// 将 parent 的父节点 ppNode 指向 subRNode* ppNode = parent->_parent;if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}
}

RotateR 右单旋

// 右单旋:以 parent 为轴心,将其左子树 subL 向右旋转(右子树不变)
void RotateR(Node* parent)
{// 保存父节点左子树的右子树 subLRNode* subL = parent->_left;Node* subLR = subL->_right;// 将 parent 的左子树指向 subLRparent->_left = subLR;if (subLR){subLR->_parent = parent;}// 调整 subL 和 parent 的关系subL->_right = parent;parent->_parent = subL;// 如果此时 parent 是根节点,修改根节点为 subLif (_root == parent){_root = subL;subL->_parent = nullptr;}else{// 将 parent 的父节点 ppNode 指向 subLNode* ppNode = parent->_parent;if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}
}

prevCheck 红黑树性质检测

  • 红黑树的性质检验函数 prevCheck,其功能是检查红黑树的各个节点是否符合红黑树的特征。
// 检查红黑树
// 会先判断当前节点是否为nullptr
// 如果是,则说明检查到叶节点,记录当前节点中黑色节点的数量,并且与之前的比较
// 如果不相等,则输出错误信息并返回false。如果相等,则返回true。
bool prevCheck(Node* root, int blackNum, int& benchmark)
{if (root == nullptr){// 如果benchmark的值为0,说明之前没有记录过当前红黑树路径中的黑色节点数量// 这时将benchmark的值设置为当前黑色节点的数量。if (benchmark == 0) {benchmark = blackNum;return true;}// 如果当前路径上的黑色节点数量与之前记录的benchmark不相等,输出错误信息并返回falseif (blackNum != benchmark) {cout << "某条路径的黑色节点数量不相等" << endl;return false;}else { // 当前路径上的黑色节点数量与之前记录的benchmark相等,返回truereturn true;}}// 遇到黑色节点,++blackNumif(root->_col==BLACK){++blackNum;}// 遇到连续的红节点,则证明有错if (root->_col == RED && root->_parent->_col == RED){cout << "error: 存在连续的红色节点" << endl;return false;}return prevCheck(root->_left, blackNum, benchmark)&& prevCheck(root->_right, blackNum, benchmark);
}

isBalance 红黑树平衡判断

  • 红黑树的平衡性检验函数 IsBalance。
bool IsBalance()
{if (_root == nullptr)return true;if (_root->_col == RED){cout << "false: 根节点为红色" << endl;return false;}// 黑色节点数量 基准值int benchmark = 0;// 也可行 ↓/*Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}*/return prevCheck(_root, 0, benchmark);
}

InOrder 中序遍历

public:
void InOrder()
{_InOrder(_root);cout << endl;
}private:
void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);
}

完整代码

gitee - RBTree代码

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

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

相关文章

OceanBase并行执行中 DTL消息接收处理的逻辑

OceanBase 并行执行的消息处理框架是很有意思的&#xff0c;里面用到了不少面向对象编程思想&#xff0c;值得分析。 DTL 从宏观上看可以分为三大部分&#xff1a; DTL 消息发送DTL 消息缓存DTL 消息处理 本文介绍 DTL 消息处理。 核心组件 DTL 消息缓冲区 DTL 消息缓冲区…

小白如何快速入门?

入门 Web 安全、安卓安全、二进制安全、工控安全还是智能硬件安全等等&#xff0c;每个不同的领域要掌握的技能也不同。当然入门 Web 安全相对难度较低&#xff0c;也是很多人的首选。主要还是看自己的兴趣方向吧。 本文就以下几个问题来说明网络安全大致学习过程&#x1f447…

如何用Python写个网页爬取程序

如何用Python写个网页爬取程序 准备开发工具安装PythonPython安装pipPip安装爬取插件准备好网页地址代码实现 准备开发工具 额&#xff0c;作者用的是vscode。具体怎么安装自行百度哈&#xff0c;这个都不会建议就不要学爬取了。 不忍心藏着也&#xff0c;给你个方法吧 vsc…

Javascript的闭包,匿名函数,自动调用

这里写目录标题 验证文本框HTMLJavascript分析var引起的赋值错误最优的解决方案forEach(function(item){})最简单的方式&#xff0c;const/let 申明一个局部变量直接使用函数通过声明函数变量的方式定义函数申明匿名函数和自动调用函数的区别 在案例的基础上分析。 验证文本框 …

90后测试员:“入职阿里,这一次,我决定不跳槽了...”

所谓“舒适”生活 记得上一份工作是去年听从了朋友的意见&#xff0c;“你一定要找一份舒适的工作&#xff0c;这样你一天就有好多时间玩&#xff0c;好多时间干自己想干的事情&#xff0c;摸鱼真香&#xff01;” 在这份“教导”下&#xff0c;开始了我的找工作之旅&#xf…

内网穿透技术

文章目录 前言1. 安装JAVA2. MCSManager安装3.局域网访问MCSM4.创建我的世界服务器5.局域网联机测试6.安装cpolar内网穿透7. 配置公网访问地址8.远程联机测试9. 配置固定远程联机端口地址9.1 保留一个固定tcp地址9.2 配置固定公网TCP地址9.3 使用固定公网地址远程联机 转载自内…

【正点原子STM32连载】 第二十五章 TFT-LCD(MCU屏)实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十…

没有硬件资源?免费使用Colab搭建你自己的Stable Diffiusion在线模型!保姆级教程...

部署 Stable Diffusion 需要一定的硬件资源&#xff0c;具体取决于要处理的图像大小和处理速度等因素。一般来说&#xff0c;至少需要一台具有较高计算能力的服务器&#xff0c;而对 GPU 的高要求就限制了我们学习和使用SD来生成我们想要的图像。 GPU是深度学习开发的重要硬件条…

chatgpt赋能python:Python列表分割与排序:完美解决数据处理问题

Python列表分割与排序&#xff1a;完美解决数据处理问题 在Python的开发实践中&#xff0c;数据处理是一项必不可少的操作。列表&#xff08;list&#xff09;是Python语言中常用的数据类型之一&#xff0c;列表中的元素可以是任意类型。列表的分割和排序是Python中常见的操作…

数字孪生:数字世界与现实世界的交汇

数字孪生是一种崭新的技术,指将现实世界中的物理实体、系统或过程通过数字化技术在虚拟数字世界中建立起虚拟模型。数字孪生可以帮助人们以更小的成本地理解和预测现实世界中的物理实体、系统或过程的行为和性能,从而提高生产效率、降低成本、减少风险等。 如今数字孪生技术…

【CSS3系列】第三章 · CSS3新增边框和文本属性

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

【Python 文本分析】零基础也能轻松掌握的学习路线与参考资料

Python 常用的文本分析工具有很多&#xff0c;如 Natural Language Toolkit (NLTK)、TextBlob、spaCy、Jieba等。本文将分别介绍这些工具及其对应的学习路线、参考资料和优秀实践。 Natural Language Toolkit (NLTK) Natural Language Toolkit (NLTK) 是 Python 中文本分析研…

如何申请免费ChatGPT 2500刀初创金

近日OpenAI 推出了OpenAI for Startups项目&#xff0c;那么什么是Startups项目呢&#xff1a; 它是由全球知名的人工智能研究公司 OpenAI 推出的一个开放式的创业计划&#xff0c;旨在为初创公司提供一种新的激励机制和技术推广方式。 也就是说我们可以用自己账号申请&#x…

记一次Java生成SQL脚本文件换行格式为window/unix的笔记

今天在做一个SQL脚本文件生成需求&#xff0c;其中&#xff0c;需要设置&#xff1a; 文件编码为&#xff1a;UTF-8文件换行格式为&#xff1a;UNIX UTF-8这个好说&#xff0c;因为java代码可以指定文件编码&#xff0c;如&#xff1a; 但是Unix换行格式就很神奇了&#xff0…

快手三面全过了,却因为背调时leader手机号造假,导致offer作废了!

这是一个悲伤的故事&#xff1a; 快手本地三面全过了&#xff0c;但因为背调时leader手机号造假&#xff0c;导致offer作废了。 楼主感叹&#xff1a;大家背调填写信息时&#xff0c;一定要慎重再慎重&#xff0c;不要重复他的悲剧&#xff01; 网友愤慨&#xff0c;照这么说&a…

OSPF最优路径选择

路由比较 1、内部区域>区域间路由>NSSA1>Nssa2 2、如果只有Ex1、Ex2或者Nssa1、nNssa2开销类型。则Ex1>Ex2或者Nssa1>Nssa2 3、如果Ex1、Nssa1,Ex2和Nssa2,Ex1和Nssa1优于Ex2和Nssa2 4、如果外部开销加上内部开销,Ex1和Nssa1一样,则Ex1和Nssa1相同负载分担 5、如果外…

京东工作8年,肝到T8就剩这份心得了,已助朋友拿到10个Offer

在京东工作了8年&#xff0c;工作压力大&#xff0c;节奏快&#xff0c;但是从技术上确实得到了成长&#xff0c;尤其是当你维护与大促相关的系统的时候&#xff0c;熬到T7也费了不少心思&#xff0c;小编也是个爱学习的人&#xff0c;把这几年的工作经验整理成了一份完整的笔记…

【TreeSet集合】比较器排序Comparator的使用

比较器排序Comparator的使用 存储学生对象并遍历&#xff0c;创建TreeSet集合使用带参构造方法 要求&#xff1a;按照年龄从小到大排序&#xff0c;年龄相同时&#xff0c;按照姓名的字母顺序排序 创建学生类&#xff1a; package com.gather.set.treeset; public class Stude…

C语言——分段函数求值

一、题目描述 二、题目分析 本题是简单的分段函数的求解&#xff0c;应学会合理的运用for\if\swich函数解答问题。 三、代码实现 //for语句解题#include <stdio.h> int main() {int x,y;scanf("%d",&x);if(x<1){yx;}else if(1<x && x<…

win10微软Edge浏览器通过WeTab新标签页免费无限制使用ChatGPT的方法,操作简单,使用方便

目录 一、使用效果 二、注册使用教程 1.打开Edge浏览器扩展 2.选择Edge浏览器外接程序 3.搜索WeTab 4.进入管理扩展 5.启用扩展 ​编辑 6.进入WeTab新标签页 7.打开Chat AI 8.注册 9.使用 ChatGPT是OpenAI推出的人工智能语言模型&#xff0c;能够通过理解和学习人类…