搞定红黑树(C++实现)

点击蓝字

ebc842c0b56a916305c710b940fd60dd.png

关注我们

来源于网络,侵删

红黑树的概念

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

ec41e9de3da0891efd3fb68c44cbe2a3.png


红黑树的性质

  1. 每个结点不是红色就是黑色

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

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

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

上面的4点性质用自己的话可以总结为:(性质5不用记)

  • 结点不是红色就是黑色

  • 没有连续的红色结点

  • 每条路径上的黑色结点的数量是一样的

AVL树是通过高度来控制平衡的,是严格平衡的。那如果新插入结点很多那么旋转也是要付出代价的。红黑树通过颜色来控制平衡,但不是严格的平衡,它近似平衡。红黑树也可以达到AVL树的效率。它最长路径不超过最短路径的2倍。

那为什么红黑树的最长路径不超过最短路径的2倍呢?

通过上面的性质,假设我们把红黑树的黑色结点单独抽出来,从跟到叶子黑结点个数为N个

764e711f301aeba7282dacac2c22c389.png

那它最短路径就是长度为N
那它最长的路径可能是一黑一红

1540accb779b671f6c7984dfd37a8e73.png

那它的长度为2N,所以它的最长路径不超过最短路径的2倍,则其他路径的长度就在N-2N之间。那么红黑树增删查改的效率就在logN-2logN之间,和AVL树的logN差不多了。

红黑树的结点定义

红黑树的结点定义还是跟AVL树一样,定义成三叉链结构和KV模型,不同的则是红黑树用枚举加入了颜色。

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;Color _color;//该结点的颜色RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(RED){}
};

红黑树的插入

那么我们插入结点时选择插入黑结点还是红结点呢?

当然是选择插入红结点了。选择插入黑结点那麻烦就大了,那1条路径上就多了1个黑结点,破坏了性质4,代价很大。插入红结点,如果它的父亲结点是黑色则不用调整,拍拍屁股走人,它的父亲是红色那我们在进行后序的处理。

总结一下:

  • 插入黑色结点一定破坏性质4,调整起来会很麻烦

  • 插入红结点不一定破坏红黑树的性质,它的父亲结点是红色才进行调整,比插入黑结点调整起来方便。

插入的逻辑:

  • 找到插入结点的位置

  • 插入结点

  • 检测新结点插入后是否破坏了红黑的性质,如果破坏则需要进行处理

因为新插入结点的颜色是红色,若它的父亲结点是黑色不用调整,是红色的话需要对红黑树分情况来讨论。

红黑树调整主要看叔叔结点

下面我们根据叔叔结点的情况来具体看一下。

情况一

以下用p来代表parent结点,c代表cur为新增结点,g代表grandparent结点,u代表uncle结点。
我们还是跟AVL树一样画具象图:

叔叔结点存在且为红

c41edec54bc2a48b1f14fb9487846076.png

为什么把g变成红色呢?如果g不变成红色,那此时子树上就多了1个黑结点了。
只要我们画出具象图,那面试时手撕红黑树也完全不怂。

当然还有很多种情况,那就给出抽象图:

34f955dcba03ff36eb1f538800a9b22c.png

这种情况下cur在p的左边还是右边都不影响。

情况二

叔叔结点存在且为黑,新增结点是p的左边

3983ae0e47eb800fe1bf46648fe134c5.png

这是由情况一变来的,如果u存在那cur一定是黑的,不是新郑结点,这样才满足红黑树的性质。

新增结点是p的右边

10a0412afd6046471651ba9c87f9b1a5.png

情况三

叔叔结点不存在

新增结点在parent的左边

dcf3cba443efec46680f8e38402950c1.png

新增结点在parent的右边

472f330858d3d8c3fe58e9f91c1a2695.png

总结一下:

以上的情况都是父亲结点在祖先结点的左边,在祖先结点的右边也是相同的处理方法

  • 叔叔结点存在且为红,把父亲结点和叔叔结点变黑,祖先变红继续向上处理直到祖先是根节点

  • 叔叔存在为黑,祖孙三代在一条直线上进行单旋,不在则进行双旋

  • 叔叔不存在,祖孙三代在一条直线上进行单旋,不在则进行双旋

所以2,3的逻辑可以合在一起,分为新增结点在父亲结点的左边还是右边处理。

下面再来简单的说说父亲结点在祖先结点的右边

叔叔存在且为红

facf57745aef0adfd214d5943bcdd237.png

此时,cur在p的左边还是右边没有影响。

叔叔存在且为黑

新增结点在父亲结点的右边

5f057e12ba51b98482ceeb2edc3ebd4e.png

新增结点在父亲结点的左边

387a24d960b5185f943aed8318f53d3f.png

叔叔不存在

82c0ee1404c872432920e000dc2f16f6.png

总结一下:

  • 叔叔存在且为红,u,p变黑,g变红继续向上调,直到g为根结点,最后把g变黑

  • 叔叔存在且为黑,祖孙3带在一条直线上单旋,折线要双旋

  • 叔叔不存在,祖孙3带在一条直线上单旋,折线要双旋

代码如下:

pair<Node*, bool> insert(const pair<K, V>& kv){//1.树为空if (_root == nullptr){_root = new Node(kv);_root->_color = BLACK;//根结点为黑色return make_pair(_root, true);}//树不为空Node* cur = _root;Node* parent = nullptr;while (cur){//新结点key大于当前结点往右边if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}//新结点key小于当前结点往左边else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return make_pair(cur, false);}}cur = new Node(kv);Node* newnode = cur;newnode->_color = RED;if (parent->_kv.first < kv.first){parent->_right = newnode;newnode->_parent = parent;}else{parent->_left = newnode;newnode->_parent = parent;}//开始调整颜色//父亲存在且为红while (parent && parent->_color == RED){Node* grandParent = parent->_parent;    //parent是grandParent左孩子if (grandParent->_left == parent){Node* uncle = grandParent->_right;//叔叔存在且为红色,父亲和叔叔都调为黑色//祖先调为红色,如果不调那每条路径的黑结点变了if (uncle && uncle->_color == RED){parent->_color = BLACK;uncle->_color = BLACK;grandParent->_color = RED;//继续往上调cur = grandParent;parent = cur->_parent;}else//叔叔不存在或叔叔存在且为黑{if (parent->_left == cur){    //右单旋RotateR(grandParent);parent->_color = BLACK;grandParent->_color = RED;}else //parent->_right == cur{RotateL(parent);RotateR(grandParent);grandParent->_color = RED;cur->_color = BLACK;}break;}}else //parent是grandParent左孩子{Node* uncle = grandParent->_left;if (uncle && uncle->_color == RED){uncle->_color = BLACK;parent->_color = BLACK;grandParent->_color = RED;cur = grandParent;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandParent);parent->_color = BLACK;grandParent->_color = RED;}else{RotateR(parent);RotateL(grandParent);cur->_color = BLACK;grandParent->_color = RED;}break;}  }}_root->_color = BLACK;return make_pair(newnode, true);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;//先旋转parent->_right = subRL;subR->_left = parent;parent->_parent = subR;//在改父亲结点if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;_root->_parent = nullptr;}else{//subR旋转后可能是左右子树2种情况if (parentParent->_left == parent)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;//记录parent的父亲结点//subLR做parent->_leftparent->_left = subLR;subL->_right = parent;//同时更新动的2个节点的parent//注意subLR还可能是空结点if (subLR)subLR->_parent = parent;parent->_parent = subL;//parent可能是单独的树,或者子树,分情况if (_root == parent){_root = subL;_root->_parent = nullptr;}else{//还有可能parent是子树,可能是左子树if (parentParent->_left == parent)parentParent->_left = subL;else//也可能是右子树parentParent->_right = subL;//调整subL的父亲结点subL->_parent = parentParent;}}


红黑树的查找

查找跟AVL树的逻辑是一样的,这里就不做多的讲解了。

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;}


红黑树的验证

先检查有没有连续的红结点,还有红结点的父亲结点是不是黑色。这就保证了没有连续的红结点。还有路径也要算。我们找1条路径作为参考,例如最左路径,只要有1条路径和它的黑结点数量不同就不是红黑树。

bool _CheckBlance(Node* root,int blackNum, int count){if (root == nullptr){if (count != blackNum){cout << "黑色节点的数量不相等" << endl;return false;}return true;}if (root->_color == RED && root->_parent->_color == RED){cout << "存在连续的红色节点" << endl;return false;}if (root->_color == BLACK){count++;}return _CheckBlance(root->_left, blackNum, count)&& _CheckBlance(root->_right, blackNum, count);}bool CheckBlance()
{if (_root == nullptr){return true;}if (_root->_color == RED){cout << "根节点是红色的" << endl;return false;}// 找最左路径做黑色节点数量参考值int blackNum = 0;Node* left = _root;while (left){if (left->_color == BLACK){blackNum++;}left = left->_left;}int count = 0;return _CheckBlance(_root, blackNum, count);}

我们来测试一下:

e8cadbcd9c7bdcb36eae5d085cb21e55.png

没有问题,我也是调试了好长时间。一定要用好调试。

红黑树的和AVL树的简单比较

红黑树的删除也是了解,红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

54a7c6b658da90c90a280152c92118be.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

1a0156c31fcf3a1f8502140d127bd0ab.gif

戳“阅读原文”我们一起进步

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

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

相关文章

java launcher_JAR清单类路径不仅适用于Java Application Launcher

java launcher自从我开始学习Java以来​​&#xff0c;我几乎已经知道&#xff0c; 清单文件中的Class-Path标头字段为可执行JAR &#xff08;具有由另一个称为Main-Class清单指定应用程序起点的 JAR&#xff09;指定了相对运行时类路径。 一个同事最近碰到一个让我感到惊讶&am…

C语言实现银行ATM存取款系统 | 附源码

点击蓝字关注我们来源于网络&#xff0c;侵删银行ATM存取款系统银行ATM存取款系统业务描述如下&#xff1a;银行ATM存取款系统能为用户提供存款、取款、查询、转账和修改密码的功能。为了模拟真实的ATM业务环境&#xff0c;本系统必须实现存款、取款、查询、转账、修改密码以及…

php 链接文件名_7、php-fpm进程管理

1、进程管理php-fpm采用的是master-worker的进程方式。其中&#xff0c;master负责fork worker进程;其次&#xff0c;注册信号&#xff0c;通过信号进行管理worker负责监听端口&#xff0c;等待链接&#xff0c;处理具体的逻辑如下图所示2、信号管理master进程可以理解如下信号…

C语言代码实现平衡二叉树|图解+详细代码

点击蓝字关注我们来源于网络&#xff0c;侵删1. 什么是平衡二叉树平衡二叉树&#xff0c;我们也称【二叉平衡搜索树/AVL】,树中任何节点的两个子树的高度最大差别为1&#xff0c;巴拉巴拉。。。(https://baike.baidu.com/item/AVL树/10986648?fraladdin)但是有个注意的点: 平衡…

cba比赛比分预测_【CBA直播】深圳vs广东前瞻:深圳战广东再掀反攻?

北京时间4月13日晚19点35分&#xff0c;CBA季后赛半决赛第三回合&#xff0c;深圳队主场迎战广东队。尽管目前双方总比分深圳以0-2落后对手&#xff0c;但他们在第二战的顽强表现给人留下了深刻印象。回归主场作战的他们&#xff0c;将在沈梓捷和贺希宁的带领下&#xff0c;力争…

C语言内存泄漏问题及其检视方法

点击蓝字关注我们来源于网络&#xff0c;侵删通过介绍内存泄漏问题原理及检视方法&#xff0c;希望后续能够从编码检视环节就杜绝内存泄漏导致的网上问题发生。本文通过介绍内存泄漏问题原理及检视方法&#xff0c;希望后续能够从编码检视环节就杜绝此类问题发生。预防内存泄漏…

未定义与 struct 类型的输入参数相对应的函数 fetch_引入鲁棒性作为连续参数,这种新损失函数实现了自适应、随时变换...

编辑&#xff1a;陈萍损失函数是机器学习里最基础也是最为关键的一个要素&#xff0c;其用来评价模型的预测值和真实值不一样的程度。最为常见的损失函数包括平方损失、指数损失、log 对数损失等损失函数。这里回顾了一种新的损失函数&#xff0c;通过引入鲁棒性作为连续参数&a…

清理jdk注册表_JDK 9早期版本安装后的Windows注册表清理

清理jdk注册表在我的上一篇博文中 &#xff0c;我演示了在安装早期版本的JDK 9&#xff08;内部版本68&#xff09;之后围绕Oracle Java符号链接 &#xff08;基于Windows的计算机上的C:\ProgramData\Oracle\Java\javapath\目录&#xff09;的问题的解决方案。这似乎阻止了早期…

汇编语言调用C语言/C++实例:乘法表

点击蓝字关注我们来源于网络&#xff0c;侵删现在编写一个简单的应用程序&#xff0c;提示用户输入整数&#xff0c;通过移位的方式将其与 2 的幕 (2〜2ⁿ) 相乘&#xff0c;并用填充前导空格的形式再次显示每个乘积。输入-输出使用 C。汇编模块将调用 3 个 C 编写的函数。程序…

rect函数_R函数不会写,quot;抄quot;总会吧!

前面我们简单的介绍了R函数。有些人可能会说&#xff0c;我现在的R水平有限&#xff0c;还不足以写出很高级的函数&#xff0c;该怎么办&#xff1f;俗话说前人栽树后人乘凉&#xff0c;他山之石可以攻玉&#xff0c;鲁迅同志也提出过“拿来”主义。已经有前人&#xff0c;高手…

10个超赞的C语言开源项目,强烈推荐!

点击蓝字关注我们来源于网络&#xff0c;侵删今天分享10个超赞的C语言开源项目&#xff0c;希望这些内容能对大家有所帮助&#xff01;目录&#xff1a;1. Webbench2. Tinyhttpd3. cJSON4. CMockery5. Libev6. Memcached7. Lua8. SQLite9. UNIX v610. NETBSD1. WebbenchWebbenc…

jboss性能指标_JBoss BRMS复杂事件处理(CEP)性能基准

jboss性能指标技术来了又去&#xff0c;但是一件事保持不变。 在设计企业解决方案时&#xff0c;我们喜欢使我们的生活更轻松的复杂组件&#xff0c;作为建筑师和开发人员&#xff0c;我们一直在寻找使我们的生活更轻松的方法。 一种方法是跟上与感兴趣的技术有关的流行新站点…

C语言经验分享:二维指针与二维数组的两种错误用法

点击蓝字关注我们来源于网络&#xff0c;侵删引子首先看一段代码:void test(int *p) {}int main() {int arr[] {30, 450,14,5};test(arr);return 0; }毫无疑问&#xff0c;上面这段代码是运行OK的。因为C语言标准中有以下规则:在函数参数的声明中&#xff0c;数组名被编译器当作…

camel 使用_使用Camel从WildFly 8向WebLogic 12发送JMS消息

camel 使用系统集成是一个很好的挑战。 特别是当您在寻找通信标准和可靠的解决方案时。 在当今的微服务世界中&#xff0c;每个人都在谈论REST服务和基于http的协议。 实际上&#xff0c;对于大多数通常具有更复杂的需求集的大多数企业项目来说&#xff0c;这是远远不够的。 合…

C++异常处理控制流下的OLLVM混淆

点击蓝字关注我们来源于网络&#xff0c;侵删Inflated!!!C异常化处理OLLVM-控制流平坦化Two PuzzlesException一般碰到C异常逆向&#xff0c;确定了异常分发、处理部分&#xff0c;直接把call throw改为jmp catch块&#xff0c;再F5即可。PS: 多个catch块根据rdx来当为异常处理…

【微服务】springboot整合kafka-stream使用详解

目录 一、前言 二、kafka stream概述 2.1 什么是kafka stream 2.2 为什么需要kafka stream 2.2.1 对接成本低 2.2.2 节省资源 2.2.3 使用简单 2.3 kafka stream特点 2.4 kafka stream中的一些概念 2.5 Kafka Stream应用场景 三、环境准备 3.1 搭建zk 3.1.1 自定义d…

C语言知识总结一:C语言的基本知识汇总

点击蓝字关注我们来源于网络&#xff0c;侵删C语言是一种计算机程序设计语言。它既有高级语言的特点&#xff0c;又具有汇编语言的特点。它可以作 为系统设计语言&#xff0c;编写工作系统应用程序&#xff0c;也可以作为应用程序设计语言&#xff0c;编写不依赖计算机 硬件的应…

jboss8日志级别设置_罐中研讨会:设置JBoss BPM Suite全日研讨会

jboss8日志级别设置是否在寻找一种简单的方法来宣传&#xff0c;展示或演示JBoss业务流程管理套件&#xff08;BPM Suite&#xff09;产品的入门难度&#xff1f; 别无所求&#xff0c;因为我们已经召集了这个研讨会&#xff0c;因此您可以围绕JBoss BPM Suite构建一个晚上&a…

yapi 接口文档_1分钟docker部署顶尖 API 文档管理系统

YApi 是高效、易用、功能强大的 api 管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API&#xff0c;YApi 还为用户提供了优秀的交互体验&#xff0c;开发人员只需利用平台提供的接口数据写入工具以及简单的点击…

微软CTO建议业界弃用C/C++采用Rust,遭C++之父回怼

点击蓝字关注我们来源于网络&#xff0c;侵删近日&#xff0c;Microsoft Azure CTO、Sysinternals 的主要开发者 Mark Russinovich 在其社交账号上发布动态称&#xff0c;开发人员是时候停止使用 C/C 来启动新项目&#xff0c;并建议可在需要使用 non-GC 语言的场景中使用 Rust…