36.C++二叉树进阶5(平衡二叉搜索树 - 红黑树及其插入操作图解)

news/2025/3/14 16:10:22/

⭐上篇文章:35.C++二叉树进阶4(平衡二叉搜索树 - AVL树及其旋转操作图解)-CSDN博客

⭐本篇代码:c++学习/19.map和set的使用用与模拟 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)

⭐标⭐是比较重要的部分

 一. 什么是红黑树

        红黑树也是一种二叉搜索树,红黑树中的节点有红色与黑色,通过控制节点的颜色来保证最长路径节点数不会超过最短路径节点数的2倍。从而保证该二叉搜索树是非常接近平衡的。

4.1 红黑树的性质

1 根节点是黑色的

2 每一条路径上的黑色节点数量相同

3 红色节点的孩子只能为黑色节点(无连续的红色节点)

4 叶子节点是黑色的(这里的叶子指的是最后的空节点)

        通过上面的性质可以总结出来:

每条路径黑色节点数量相同

最短路径为:全部是黑色节点                                                ​​​​​​​        ​​​​​​​        ​​​​​​​        

最长路径为:一红一黑

        这样就能保证最长路径不会最短路径的二倍,从而保证红黑树是近似平衡的。

二. 有AVL树为何还要红黑树

        AVL树通过平衡因子和旋转操作来保证整棵树是高度平衡的,可以解决二叉搜索树退化为链表的问题。保证其增删查改效率为O(log N)。

        而红黑树通过控制节点颜色来保证整棵树是近似平衡的(最长路径不超过最短路径二倍),能够保证其增删查改的效率为O(log N) ~ 2*O(log N)

        既然如此,为何还要红黑树呢?

1 红黑树的平均效率是比AVL较低,但是现代计算机CPU效率非常高,logN和2 *log N的差异不是很大

2 AVL树严格的平衡是需要不断的旋转操作得到的,旋转操作是比较耗时的。而红黑树的旋转操作比AVL树少得多。

        所以在很多情况下,红黑树的效率更高,综合性能更好,且实现简单。

 三. 红黑树的代码框架  

3.1 红黑树的节点   

         红黑树节点只需增加一个表示颜色的变量即可

enum  Color
{RED,Block
};template<class K,class V>
struct rbTreeNode
{rbTreeNode<K, V>* _left;rbTreeNode<K, V>* _right;rbTreeNode<K, V>* _parent;pair<K, V> _kv;Color col;rbTreeNode(const pair<K, V>& _kv = pair<K, V>()):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

        这里默认将节点设置为红色,因为红色节点更温和,插入新节点容易调整红黑树如果默认节点是黑色,每一次插入数据都要调整各个路径的值,操作复杂。

3.2 红黑树框架

template<class K,class V>
class RBTree
{using Node = rbTreeNode<K, V>;
public:bool Insert(const pair<K, V>& kv){}bool Find(const pair<K, V>& kv){}
private:Node* _root;
};

如果想要增加其他功能,可以直接在类内部增加。本文主要说明红黑树的节点的插入。

四. 红黑树节点的插入操作⭐

        首先安装二叉搜索树的性质进行插入数据,插入数据后再根据红黑树的性质进行调整

3.1 插入节点父亲为黑色(情况一)

        如果插入节点的父亲为黑色,插入节点为红色。直接插入即可,无论是其左孩子还是右孩子。这样不会破坏红黑树的任意一个条件。

3.2 插入节点父亲为红色

        由于父亲为红色,插入节点也为红色。破坏了红黑树的条件(红色节点的孩子必须为黑色)。此时就需要进行调整。这里有很多种情况,读者可以根据图解快速理解。

        注意:

1 如果父亲为红色,那么爷爷一定是黑色

2 所以红黑树的调整主要是:叔叔节点的颜色,位置(爷爷的左右孩子),是否存在

a 叔叔存在且颜色为红色(情况二与三)

        如果叔叔存在且为红色,则会有下面两种情况。

        上面这两种情况,我们只需要将父亲p和叔叔u变为黑色,然后将爷爷变为黑色。这样就能保证从爷爷开始的整颗树满足红黑树

如下图:

 

调整完之后,还要继续向上调整。

        如果爷爷是根节点,则直接将爷爷变为黑色,调整结束。否则需要继续向上调整,因为爷爷是红色节点,如果爷爷的父亲是红色则需要重新调整(不能有连续的红节点)。

        此时爷爷g就是新的cur。 

就像下图:

 

b 叔叔不存在或者叔叔是黑色节点(情况三与四)

        图解如下:解释在图解中

         所以这种只需要对g进行右旋转然后再让p变黑色,g变红色即可

        如果cur此时是父亲的右侧,则需要先对p进行左旋。如下图:

 

        上面展示的是cur父亲是cur爷爷左孩子的情况,对于右孩子的情况也是一样。只是旋转的方向改一改就行。

c 插入操作的代码

	bool Insert(const pair<K, V>& kv){//1.按照搜索树的规则进行插入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);if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//2.调整颜色//新增结点是红色的(因为红色节更温和,影响红黑树的结构小)cur->_col = RED;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//父亲在祖父左边if (parent == grandfather->_left){//红黑树的调节关键看叔叔Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = grandfather->_parent;}else{//情况二三,叔叔是黑色或者不存在//情况三,cur在parent右边(折线),此时对parent左旋,再对grandfather右旋if (parent->_right == cur){RotateLeft(parent);swap(parent, cur);	//只是交换了指针,kv节点并没有交换(更换了父亲和孩子的位置)}//情况二,cur在parent左边(直线),此时对grandfather右旋即可//再让parent变黑,grandfather变红RotateRight(grandfather);parent->_col = BLACK;grandfather->_col = RED;break;}}else  //说明父亲在祖父的右边{Node* uncle = grandfather->_left;//情况一if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = grandfather->_parent;}else{//情况二三//情况三(折线),此时父亲在右边,cur在父亲左边,对parent右旋,再对grandfath左旋if (cur == parent->_left){RotateRight(parent);swap(cur, parent);}//情况二(直线)						RotateLeft(grandfather);grandfather->_col = RED;parent->_col = BLACK;break;	//此时颜色已经调整好了,不需要继续调整}}}_root->_col = BLACK;	//无论如何变色,最后让根为黑色。就不会出错return true;}

旋转操作的代码:

void RotateLeft(Node* parent){if (!parent)return;Node* ppNode = parent->_parent;//要旋转节点的父亲Node* SubR = parent->_right;//要旋转节点的右孩子Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子//一:调整节点parent->_right = SubRLeft;if (SubRLeft)SubRLeft->_parent = parent;SubR->_left = parent;parent->_parent = SubR;//1.parent是根,现在SubR是根//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接if (_root == parent){_root = SubR;SubR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubR;}else{ppNode->_right = SubR;}SubR->_parent = ppNode;}}//左左,右单旋void RotateRight(Node* parent){if (!parent)return;Node* ppNode = parent->_parent;Node* SubL = parent->_left;Node* SubLRight = SubL->_right;//1.旋转,调整节点parent->_left = SubLRight;if (SubLRight)SubLRight->_parent = parent;SubL->_right = parent;parent->_parent = SubL;if (_root == parent){_root = SubL;SubL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubL;}else{ppNode->_right = SubL;}SubL->_parent = ppNode;}}

 其他操作的代码在代码仓库中,读者可自行阅读。

五. 红黑树的应用

        很多软件,代码库都有着红黑树的身影。

1 C++STL中的map/set使用的就是红黑树

2 在某些代码库中,哈希表的链表过长时候会使用红黑树替代

3 某些数据库系统使用红黑树作为索引

4 Linux内核调度器,虚拟内存管理使用红黑树

......


http://www.ppmy.cn/news/1579090.html

相关文章

iOS开发,SQLite.swift, Missing argument label ‘value:‘ in call问题

Xcode16中&#xff0c;集成使用SQLite.swift&#xff0c;创建表的时候&#xff1a; let id Expression<Int64>("id")&#xff0c;报错Missing argument label value: in call 直接使用SQLite.Expression<Int64>("id") 或者定义一个全局typ…

用TypeScript和library needle来创建视频爬虫程序

使用 TypeScript 和 needle 库创建视频爬虫程序的过程可以按照以下步骤进行。needle 是一个轻量级的 HTTP 请求库&#xff0c;适用于进行网络请求。 步骤&#xff1a; 安装依赖&#xff1a; 你需要安装 needle 来发送 HTTP 请求&#xff0c;以及一些额外的库来帮助处理 HTML 数…

Java 实现 Android ViewPager2 顶部导航:动态配置与高效加载指南

Java 实现&#xff1a;明确使用的编程语言。Android ViewPager2&#xff1a;技术栈和核心组件。顶部导航&#xff1a;功能点。动态配置与高效加载指南&#xff1a;突出动态配置的灵活性和性能优化的重点。 在 Android 中使用 Java 实现 ViewPager2 和 TabLayout 的顶部导航也是…

【抽奖项目】|第三篇

前言&#xff1a; 高并发的活动预热肯定不可以在数据库操作&#xff0c;需要redis&#xff0c;特别是这种秒杀活动更是需要注意&#xff0c;所以可以在高并发的前夕先进行活动预热。 上一篇写完了怎么样活动预热&#xff0c;可以看看这篇写怎么样高并发抽奖接口 【抽奖项目】|第…

神经网络为什么要用 ReLU 增加非线性?

在神经网络中使用 ReLU&#xff08;Rectified Linear Unit&#xff09; 作为激活函数的主要目的是引入非线性&#xff0c;这是神经网络能够学习复杂模式和解决非线性问题的关键。 1. 为什么需要非线性&#xff1f; 1.1 线性模型的局限性 如果神经网络只使用线性激活函数&…

当AI回答问题时,它的“大脑”里在炒什么菜?

文章目录 1. 拆解订单&#xff1a;AI如何听懂你的“暗号”&#xff1f;2. 调用工具&#xff1a;AI的“万能工具箱”里有什么&#xff1f;3. 知识不够&#xff1f;去“图书馆”现学现卖&#xff01;4. 人类的秘密武器&#xff1a;给AI戴上“镣铐”5. 为什么AI会“胡言乱语”&…

VSCode 搭建C++编程环境 2025新版图文安装教程(100%搭建成功,VSCode安装+C++环境搭建+运行测试+背景图设置)

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、VScode下载及安装二、安装 MinGW-w64 工具链三、Windows环境变量配置四、检查 M…

【面试题系列】 Redis 核心面试题(二)答案

本文主要介绍Redis 的面试题&#xff0c;涵盖持久化、集群、缓存策略、事务等方面 一、持久化机制 1. RDB 与 AOF 的核心区别及适用场景&#xff1f; 答案&#xff1a; 特性RDBAOF存储内容内存快照&#xff08;二进制文件&#xff09;写命令日志&#xff08;文本格式&#x…