在这里插入图片描述
各位大佬好,我是落羽!一个坚持不断学习进步的大学生。
如果您觉得我的文章有所帮助,欢迎多多互三分享交流,一起学习进步!

也欢迎关注我的blog主页: 落羽的落羽

一、红黑树的概念与规则

红黑树是一种更加特殊的平衡二叉搜索树,它在每个节点上增加一个存储位来表示 节点的颜色(红色或黑色) ,并通过几条规则确保树在插入和删除操作后仍然保持平衡。

红黑树有以下四条规则:

  1. 每个结点不是红色就是黑色
  2. 根结点是黑色的
  3. 如果一个结点是红色的,则它的两个孩子必须都是黑色的,也就是说任意一条路径上不会出现连续的红色结点(黑色结点的孩子颜色不做要求)
  4. 对于任意一个结点,从结点到这棵树所有空结点的简单路径上,均包含相同数量的黑色结点

依靠它的几条规则,从同一结点出发红黑树没有一条路径会比其他路径长出2倍,因而也是接近平衡的。这是因为,由于从根到空结点的每条路径都有相同数量的黑色结点,所以极限场景下,理论最短路径就是全是黑色结点的路径(设长度为bh),理论最长路径是一黑一红间隔组成,长度就是2bh了。也就是说,红黑树中任意一条从根到空结点的路径x,都有bh <= x <= 2bh,确保了红黑树最长路径不超过最短路径的2倍了。

假设N是红黑树中结点数量,h是最短路径长度,那么2h -1 <= N <= 22h -1 ,推出h约等于logN,红黑树增删查改最坏情况也就是走最长路径2*logN,时间复杂度还是O(logN)。

这些规则保证了红黑树的平衡性,使得树的高度保持在logN级别。相比于AVL树,红黑树的平衡结构可以说更加“松散”,AVL树严格要求了左右子树高度差不超过1,但红黑树只要路径长不超过2倍就行,但它们的效率还是相同的水平。

二、红黑树的实现

红黑树的结构

红黑树的基本结构,不需要AVL树的平衡因子,而需要一个颜色成员:
enum Color
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Color _col;RBTreeNode(const pair<K,V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){ }
};template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:private:Node* _root;
};

红黑树的插入

红黑树的插入新结点的过程是这样的:
  • 首先按照二叉搜索树的规则找到应插入的位置,插入后我们需要判断是否符合红黑树的规则。
  • 如果是空树插入,新增结点必须是黑色的,插入完成;如果是非空树插入,新增结点必须是红色的,因为如果非空树插入了黑色结点就会破坏规则4,这条规则是很难维护的。
  • 非空树插入红色结点后,如果父亲是黑色的,则不会破坏任何规则,插入完成。
  • 非空树插入红色结点后,如果父亲是红色的,就破坏了规则“红色结点的孩子必须是黑色”,此时需要下面进一步分析。

接下来,我们具体分析最后一条情况,又有几种场景。下面称新增结点为c,c的父亲为p,p的父亲为g,p的兄弟为u。c是红色的,p也是红色的,那么g一定是黑色的,这三者的颜色是确定的,所以关键是看u的状态(下图中只表示出cpgu结点,省略其他子树):

场景1:

u存在且为红色。这种场景下,c保持红色不变,使p和u都变成黑色,g变为红色。这样处理后,就能使包含p或u的路径的黑色结点数量保持一致。

场景2:

u存在且为黑色 或 u不存在。u不存在,则c一定是新增结点;u存在且为黑,则c一定不是新增结点,而是新增结点插入在了c的子树中通过场景1调整后使c变成了红色。

这两种情况下处理方式是一样的,需要旋转+变色处理,但是旋转变色的方式,和AVL树一样仍需分情况讨论:

  • p是g的左,c是p的左。以g为旋转点右单旋。然后把p变黑,g变红。如图
  • p是g的右,c是p的右。以g为旋转点左单旋。然后把p变黑,g变红。如图
  • p是g的左,c是p的右。**先以p为旋转点左单旋,再以g为旋转点右单旋(左右双旋)。然后把c变黑,g变红。**如图
  • p是g的右,c是p的左。**先以p为旋转点右单旋,再以g为旋转点左单旋(右左双旋)。然后把c变黑,g变红。**如图

红黑树的插入代码实现:

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){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);cur->_col = RED;cur->_parent = parent;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//p是g的左的情况if (parent == grandfather->_left){Node* uncle = grandfather->_right;//u存在且为红色//变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}//u不存在或存在为黑色else //旋转+变色{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//p是g的右的情况else{Node* uncle = grandfather->_left;//u存在且为红色//变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}//u不存在或存在为黑色else //旋转+变色{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}

红黑树的验证

验证我们的红黑树是否合格,也就需要检查是否满足了那四条规则
  • 规则1不用检查,因为我们一开始就用了枚举类型赋值
  • 规则2检查根结点颜色即可
  • 规则3进行前序遍历,遇到红色结点就检查它的父结点是不是红色的
  • 规则4,首先用一个refNum记录最左路径的黑色结点数量,进行前序遍历,遍历过程中用一个变量blackNum记录到当前结点的黑色结点数量,遇到黑色结点就++blackNum,走到空就计算出了一条路径的黑色结点数量。此时若blackNum与refNum不同,则规则4不满足。
bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){if (refNum != blackNum){cout << "存在黑色结点数量不相同的路径!" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色结点!" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}bool IsBalance()
{if (_root == nullptr){return true;}if (_root->_col == RED){return false;}//参考值refNum记录最左路径的黑色结点数量int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){refNum++;}cur = cur->_left;}return Check(_root, 0, refNum);
}

本篇完,感谢阅读~

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

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

相关文章

【愚公系列】《MIoT.VC》001-认识、安装 MIoT.VC 软件

💎【行业认证权威头衔】 ✔ 华为云天团核心成员:特约编辑/云享专家/开发者专家/产品云测专家 ✔ 开发者社区全满贯:CSDN博客&商业化双料专家/阿里云签约作者/腾讯云内容共创官/掘金&亚马逊&51CTO顶级博主 ✔ 技术生态共建先锋:横跨鸿蒙、云计算、AI等前沿领域…

git:tag标签远程管理

git tag v1&#xff1a;在当前所在分支创建标签v1git tag -a v2 -m release version&#xff1a;创建一个带有附注的标签git tag -d v2&#xff1a;删除本地标签git tag&#xff1a;查看标签git push origin 标签1 标签2……&#xff1a;把多个标签推送到远程git push origin -…

力扣 hot100 Day49

105. 从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 //抄的 class Solution { private:unordered_map<int, i…

jvm-sandbox-repeater 录制和回放

https://github.com/alibaba/jvm-sandbox-repeater/blob/master/docs/user-guide-cn.md 快速录制自己应用 step0 安装sandbox和插件到应用服务器 curl -s https://github.com/alibaba/jvm-sandbox-repeater/releases/download/v1.0.0/install-repeater.sh | sh step1 修改repe…

【C++底层剖析】++a vs a++:到底谁是左值,谁是右值?

在 C 编程中&#xff0c;我们经常使用 a 和 a 来实现自增操作。乍一看它们只是“先加还是后加”的语法糖&#xff0c;但你真的理解它们的底层机制、返回值类型和左值右值属性吗&#xff1f;1. a 和 a 的基础区别表达式名称语义返回值类型左值 / 右值a前置自增先将 a 加 1&#…

【世纪龙科技】汽车故障诊断与排除仿真教学软件让课堂更高效安全

随着汽车产业向智能化、电动化快速转型&#xff0c;职业院校汽修专业的教学模式正面临全新挑战。传统实车实训存在成本高、风险大、场景单一等问题&#xff0c;而行业对人才的要求却越来越高——既需要扎实的理论基础&#xff0c;又必须具备熟练的故障诊断能力。如何在保证安全…

网络基础9:按流负载均衡实验(等价路由)

实验eNS拓扑图&#xff1a;1. 网络拓扑与 IP 配置AR5&#xff1a;GE0/0/0: 192.168.1.1/24&#xff08;连接 AR6&#xff09;GE0/0/1: 192.168.3.1/24&#xff08;连接 AR8&#xff09;Loopback0: 1.1.1.1/32&#xff08;源地址&#xff09;AR6&#xff1a;GE0/0/0: 192.168.1.…

4G模块 A7680发送中文短信到手机

命令说明 基础AT指令 ATi显示产品的标志信息 ATCIMI查询IMSI ATCICCID从SIM卡读取ICCID ATCGSN查询产品序列号 ATCPIN查询卡状态 ATCSQ查询信号强度 ATCGATT查询当前PS域状态 ATCREG查询GPRS注册状态 ATCEREG查询4G注册状态 ATCGPADDR查询PDP地址 ATCMGF选择短信格式 ATCMGS发…

深度学习-线性神经网络

文章目录线性回归基本概念随机梯度下降矢量化加速正态分布和平方损失极大似然估计线性回归实现从0开始**torch.no_grad()的两种用途****为什么需要 l.sum().backward()&#xff1f;**调用现成库softmax回归图像数据集从0开始实现softmax利用框架API实现课程学习自李牧老师B站的…

【王树森推荐系统】推荐系统涨指标的方法04:多样性

涨指标的方法有哪些&#xff1f; 改进召回模型&#xff0c;添加新的召回模型改进粗排和精排模型提升召回&#xff0c;粗排&#xff0c;精排的多样性特殊对待新用户吗&#xff0c;低活用户等特殊人群利用关注&#xff0c;转发&#xff0c;评论这三种交互行为 排序的多样性 精排多…

1. Spring AI概述

一、前言 Spring AI 是由 Spring 团队推出的开源项目&#xff0c;旨在为 Java 开发者提供简洁、一致的 Spring 风格开发体验&#xff0c;用于构建基于生成式人工智能&#xff08;GenAI&#xff09;和大型语言模型&#xff08;LLM&#xff09;的应用程序。它通过标准化抽象层简…

[每日随题10] DP - 重链剖分 - 状压DP

整体概述 难度&#xff1a;1600 →\rightarrow→ 2200 →\rightarrow→ 2600 P6005 [USACO20JAN] Time is Mooney G 标签&#xff1a;DP 前置知识&#xff1a;链式前向星 难度&#xff1a;绿 1600 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 样例输…

【Ubuntu22.04】repo安装方法

背景 repo是Google开发的用于基于git管理Android版本库的一个工具&#xff0c;管理多个Git仓库的工具&#xff0c;它可以帮助您在一个代码库中管理多个Git仓库的代码。其在鸿蒙操作系统中大量使用。下面我们就介绍repo在wsl中的安装部署。 安装方法 使用中国科技大学资源 脚本i…

Vue3的definePros和defineEmits

在 Vue 3 中&#xff0c;defineProps 和 defineEmits 是组合式 API 中用于定义组件的 props 和 事件 的方法&#xff0c;提供了一种更简洁和明确的方式来管理组件的输入和输出。它们属于 Composition API 的一部分&#xff0c;在 Vue 2 中通常使用 props 和 $emit 来实现。1. d…

【华为机试】122. 买卖股票的最佳时机 II

文章目录122. 买卖股票的最佳时机 II描述示例 1示例 2示例 3提示解题思路核心观察关键洞察算法实现方法1&#xff1a;贪心算法&#xff08;推荐&#xff09;方法2&#xff1a;动态规划方法3&#xff1a;动态规划&#xff08;空间优化&#xff09;方法4&#xff1a;波峰波谷法算…

Spring MVC @RequestParam注解全解析

RequestParam 注解详解 RequestParam 是 Spring MVC 中最常用的注解之一&#xff0c;用于从 HTTP 请求中提取查询参数&#xff08;Query String&#xff09;或表单数据。它主要处理 application/x-www-form-urlencoded 类型的请求&#xff08;如 GET 请求或 POST 表单提交&…

从零掌握XML与DTD实体:原理、XXE漏洞攻防

本文仅用于技术研究&#xff0c;禁止用于非法用途。 Author:枷锁 文章目录一、XML基础1. 什么是XML&#xff1f;2. XML语法规则3. 数据类型二、DTD1. 认识DTD2. 声明DTD3. DTD实体4. 如何防御XXE攻击&#xff1f;5. 总结一、XML基础 1. 什么是XML&#xff1f; XML &#xff1…

.NET 8 Release Candidate 1 (RC1)现已发布,包括许多针对ASP.NET Core的重要改进!

.NET 8 Release Candidate 1 (RC1)发布&#xff1a;ASP.NET Core重大改进来袭&#xff01; 近日&#xff0c;.NET 8 Release Candidate 1 (RC1)正式发布&#xff0c;这是在今年晚些时候计划发布的最终 .NET 8 版本之前的两个候选版本中的第一个。此版本包含了大部分计划中的功…

Jenkins pipeline 部署docker通用模板

Jenkinsfile: Docker的NETWORK_NAME不要使用bridge默认网络&#xff0c;要使用自定义的网络如test默认 bridge 网络&#xff1a;容器间不能用名字互相访问&#xff0c;只能用 IP。自定义网络&#xff1a;容器间可以用名字互相访问&#xff0c;Docker 自动做了 DNS 解析。pipeli…

【每日算法】专题十五_BFS 解决 FloodFill 算法

1. 算法思想 Flood Fill 问题的核心需求 给定一个二维网格&#xff08;如像素矩阵&#xff09;、一个起始坐标 (x, y) 和目标颜色 newColor&#xff0c;要求&#xff1a; 将起始点 (x, y) 的颜色替换为 newColor。递归地将所有与起始点相邻&#xff08;上下左右&#xff09; …