【数据结构】红黑树的插入与验证

文章目录

  • 一、基本概念
    • 1.时代背景
    • 2. 基本概念
    • 3.基本性质
  • 二、实现原理
    • 1. 插入
      • 1.1变色
      • 1.2旋转+变色
        • ①左旋
        • ②右旋
        • ③右左双旋
        • ④左右双旋
    • 2.验证
  • 源码
  • 总结

一、基本概念

1.时代背景

  • 1972年鲁道夫·拜尔(Rudolf Bayer)发明了一种数据结构,这是一种特殊的B树4阶情况。这些树保持了从根到叶的所有路径,节点数量相同,创造了完美平衡的树。但是,它们不是二叉搜索树。拜耳在他的论文中称它们为“对称二元B树”。这是红黑树的起源。

在这里插入图片描述

  • 在1978年的一篇论文“平衡树的二色框架”中,列奥尼达斯·吉巴斯(Leonidas J. Guibas )和罗伯特·塞奇威克(Robert Sedgewick)从对称的二元B树中推导出了红黑树。选择“红色”是因为它是作者在施乐PARC工作时可用的彩色激光打印机产生的最好看的颜色。吉巴斯的另一个回应说,这是因为他们可以使用红色和黑色的笔来画树。
    在这里插入图片描述
    在这里插入图片描述

第一张为——列奥尼达斯·吉巴斯,第二张为——罗伯特·塞奇威克。

  • 1993年,Arne Andersson引入了右倾树的想法,以简化插入和删除操作。
    在这里插入图片描述
  • 1999年,Chris Okasaki展示了如何使插入操作纯粹功能化。它的平衡功能只需要处理 4 个不平衡情况和一个默认平衡情况。
    在这里插入图片描述

详细请看:维基百科

2. 基本概念

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

  • 实现平衡的关键:最长路径小于等于最短路径的两倍

3.基本性质

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

强调:2,3,4点是有关联的,且是最关键的3点。

  • 假设根节点如果是红的,那么插入的结点就是只能是黑的(3),那么就违背了(4)。
  • 对于3分析,孩子结点为空,但空节点也被理解成黑色(5),因此(5)是用来辅助(3)的。
  • 对于4分析,可推理出两个结论——
    1 . 插入结点必须为红色的。
    2 . 满足最长路径小于最短路径的两倍(概念)。对此点可以看做间隔问题,即n个数之间(不算头一个数),有n个间隔,即n个黑结点(不算根节点),之间最多有n个红结点。

二、实现原理

1. 插入

1.1变色

根本逻辑:基于每条路径的黑色结点不变。

第一种变色方式:
在这里插入图片描述
这样变,是不是每条路径的黑色结点数没变呢?

那这样变的前提是什么呢?

  • 黑色结点的左右孩子为红且不为空。

那什么时候发生变色呢?

  • 基于性质3,红色结点的两个孩子必须为黑,但由4我们可以推出每次插入结点必须为红,那这时候我们按照4的原则进行处理,使处理结果符合3即可,怎么处理,就是进行变色。

在这里插入图片描述
此时,parent的右边进行插入新节点,且parent在grandfather的左边。

在这里插入图片描述

此时在parent的右边进行插入,且parent为grandfather的左节点。

  • 总结
  1. 变色的前提是每条路径的黑色结点不变
  2. uncle非空且为红,且parent为红,变grandfather为红,parent与uncle为黑。

继续分析,如果grandfather为红,其父节点是否可能为红呢?

  • 答案是可能的。

因此我们需要继续往上更新:

  1. 更新cur为grandfather
  2. parent为cur的parent

接着分析,如果grandfather为根节点呢?

  • 由于性质2,我们需要再次修改根节点的颜色为黑。

1.2旋转+变色

前面我们分析了一种简单的只需变色的情况,我们下面接着分析另外一种情况。

第二种变色需要在旋转的基础上进行分类讨论,具体情况有四种。

①左旋

在这里插入图片描述

补充:当uncle为黑结点时,parent的左子树不为空且根节点为黑色,cur的左右子树同理,这里不过多分析了,因为具体情况过多分析容易提高理解难度。

  • 开始时parent在grandfather右边,且cur在parent的右边

②右旋

在这里插入图片描述
对uncle的补充同左旋

  • 开始时parent在grandfather左边,且cur在parent的左边

③右左双旋

在这里插入图片描述
对uncle的补充同左旋

  • 开始时parent在grandfather的右边,且cur在parent的左边

④左右双旋

在这里插入图片描述
对uncle的补充同左旋

  • 开始时parent在grandfather的左边,且cur在parent的右边

  • 总结
    根据parent的位置我们可以大致分为两种情况:

  1. parent在grandfather的左边
    在这里插入图片描述

  2. parent在grand的右边
    在这里插入图片描述
    其实不难看出,AVL和红黑树都会进行旋转,只是AVL树旋转后处理的是平衡因子,红黑树旋转后处理的是变色,归根结底终究都是为了让树达到平衡。

  • 核心代码

//判断是否要进行旋转变色
//当父节点为红色说明要进行判断
while (parent && parent->_col == RED)
{//爷爷结点Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//如果uncle存在且为红色{//变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上迭代进行分析cur = grandfather;parent = cur->_parent;}else//如果uncle不存在或者为黑色{//旋转if (parent->_left == cur){RotateR(grandfather);parent->_col = BLACK;cur->_col = grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;parent->_col = grandfather->_col = RED;}break;}}else//grandfather->_right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//往上更新cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){//旋转RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = cur->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = parent->_col = RED;}break;}}
}
  • 插入代码
bool insert(const pair<Key, Val>& val)
{//第一步:插入操作//如果根节点为空if (_root == nullptr){_root = new Node(val);_root->_col = BLACK;return true;}else{Node* cur = _root, * parent = _root;while (cur){if (cur->_key > val.first){parent = cur;cur = cur->_left;}else if (cur->_key < val.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(val);if (parent->_key > val.first){parent->_left = cur;}else{parent->_right = cur;}//更新新增结点的_parentcur->_parent = parent;//判断是否要进行旋转变色//当父节点为红色说明要进行判断while (parent && parent->_col == RED){//爷爷结点Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//如果uncle存在且为红色{//变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上迭代进行分析cur = grandfather;parent = cur->_parent;}else//如果uncle不存在或者为黑色{//旋转if (parent->_left == cur){RotateR(grandfather);parent->_col = BLACK;cur->_col = grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;parent->_col = grandfather->_col = RED;}break;}}else//grandfather->_right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//往上更新cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){//旋转RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = cur->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = parent->_col = RED;}break;}}}//根节点可能为红色,不管红色还是黑色都弄成黑色_root->_col = BLACK;return true;}
}

2.验证

  • 原理
  1. 根节点不能为红
  2. 每条路径的黑色结点数相同
  3. 每条路径不能出现连续的红色结点。
  • 代码
bool _IsRBTree(Node* root)
{if (root == nullptr)return true;//根节点是黑色的if (root->_col == RED)return false;//各个路径的黑色结点数是相同的,因此设立一个基准进行比较合适,//再对树进行遍历求每个路径的黑色结点的数量,最后比较即可。int benchmark = 0;Node* cur = root;while (cur){if (cur->_col == BLACK)benchmark++;cur = cur->_left;}return Check(root);
}
bool Check(Node* root, int BCount,int benchmark)
{if (root == nullptr){//验证基准值是否等于黑色结点数//只要有一个不是,即不是红黑树。if (BCount != benchmark)return false;return true;}//求每条黑色结点的个数if (root->_col == BLACK)BCount++;//验证性质3,即不能有连续的红色结点。if (root->_col == RED && root->_parent && root->_parent->_col == RED){return false;}return Check(root->_left,BCount,benchmark) && Check(root->_right, BCount, benchmark);
}

源码

#pragma once
#include<iostream>
using namespace std;
namespace MY_STL
{enum Color{RED = 0,BLACK = 1};template<class Key,class Val>struct RBTreeNode{typedef RBTreeNode<Key, Val> Node;RBTreeNode(const pair<Key,Val>& key):_key(key.first),_val(key.second),_right(nullptr),_left(nullptr),_parent(nullptr),_col(RED){}Node* _right;Node* _left;Node* _parent;Key _key;Val _val;Color _col;};template<class Key,class Val>class RBTree{typedef RBTreeNode<Key, Val> Node;public:bool insert(const pair<Key, Val>& val){//第一步:插入操作//如果根节点为空if (_root == nullptr){_root = new Node(val);_root->_col = BLACK;return true;}else{Node* cur = _root, * parent = _root;while (cur){if (cur->_key > val.first){parent = cur;cur = cur->_left;}else if (cur->_key < val.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(val);if (parent->_key > val.first){parent->_left = cur;}else{parent->_right = cur;}//更新新增结点的_parentcur->_parent = parent;//判断是否要进行旋转变色//当父节点为红色说明要进行判断while (parent && parent->_col == RED){//爷爷结点Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//如果uncle存在且为红色{//变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上迭代进行分析cur = grandfather;parent = cur->_parent;}else//如果uncle不存在或者为黑色{//旋转if (parent->_left == cur){RotateR(grandfather);RotateCount++;parent->_col = BLACK;cur->_col = grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);RotateCount+=2;cur->_col = BLACK;parent->_col = grandfather->_col= RED;}break;}}else//grandfather->_right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//往上更新cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){//旋转RotateL(grandfather);RotateCount++;//变色parent->_col = BLACK;grandfather->_col = cur->_col = RED;}else{RotateR(parent);RotateL(grandfather);RotateCount += 2;cur->_col = BLACK;grandfather->_col = parent->_col = RED;}break;}}}//根节点可能为红色,不管红色还是黑色都弄成黑色_root->_col = BLACK;return true;}}bool IsRBTree(){return _IsRBTree(_root);}size_t Height(){return Height(_root);}private:size_t Height(Node* root){if (root == nullptr){return 0;}int LHeight = Height(root->_left);int RHeight = Height(root->_right);return max(LHeight, RHeight) + 1;}bool _IsRBTree(Node* root){if (root == nullptr)return true;//根节点是黑色的if (root->_col == RED)return false;//各个路径的黑色结点数是相同的,因此设立一个基准进行比较int benchmark = 0;Node* cur = root;while (cur){if (cur->_col == BLACK)benchmark++;cur = cur->_left;}return Check(root,0,benchmark);}bool Check(Node* root, int BCount,int benchmark){if (root == nullptr){//验证基准值是否等于黑色结点数//只要有一个不是,即不是红黑树。if (BCount != benchmark)return false;return true;}//求每条黑色结点的个数if (root->_col == BLACK)BCount++;//验证性质3,即不能有连续的红色结点。if (root->_col == RED && root->_parent && root->_parent->_col == RED){return false;}return Check(root->_left,BCount,benchmark) && Check(root->_right, BCount, benchmark);}void RotateL(Node* parent){//画图分析://操作的结点有cur,cur_left,ppnodeNode* cur = parent->_right;Node* cur_left = cur->_left;//将parent的右节点改为cur_leftparent->_right = cur_left;//改变cur_left父节点的转向//cur_left可能为空if (cur_left != nullptr){cur_left->_parent = parent;}//将parent链接在cur的左边//为了更新cur的parent需要保存parent的父节点Node* ppnode = parent->_parent;cur->_left = parent;parent->_parent = cur;//ppnode可能为空if (ppnode == nullptr){//需要修改根节点_root = cur;cur->_parent = nullptr;}else{//改变ppnode的指向if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node* parent){//操作的结点Node* cur = parent->_left;Node* cur_right = cur->_right;//第一步:将cur_right链接到parent的leftparent->_left = cur_right;//更改cur_right的父节点//注意:cur_right可能为空if (cur_right != nullptr){cur_right->_parent = parent;}//第二步:将parent链接到cur的右结点。//先保存一下parent的父节点Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;//ppnode为空说明需要修改根节点if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}Node* _root = nullptr;public:size_t RotateCount = 0;};
};

总结

 红黑树的理解较AVL树抽象,需要画图分析,不过有了AVL树旋转的基础,这里的难度要下降不少。还是与之前一样,只要图画的好,代码跑不了,所以这里的关键就在于画图。
 总之,今天的分享到这里就结束了,如果感觉有所帮助,不妨点个赞鼓励一下吧!

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

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

相关文章

蓝桥杯打卡Day6

文章目录 N的阶乘基本算术整数查询 一、N的阶乘OI链接 本题思路&#xff1a;本题是关于高精度的模板题。 #pragma GCC optimize(3) #include <bits/stdc.h>constexpr int N1010;std::vector<int> a; std::vector<int> f[N];std::vector<int> mul(in…

MATLAB入门-数据的导入和导出

MATLAB入门-数据的导入和导出 注&#xff1a;本篇文章是课程学习笔记&#xff0c;课程链接为&#xff1a;头歌 常见的几个导入数据的方法 load函数 load函数专门用于引入MATLAB的.mat格式数据&#xff0c;十分的简单方便。 例如&#xff1a;一个-ASCII编码形式存储的数据文件…

使用本地mysql+linux实现mysql主从同步

1.配置linux 保证linux已经安装好了mysql1.1修改该linux配置文件 vim /etc/my.cnf1.2重启linux的mysql systemctl restart mysqld1.3使用账户密码登录linux中的mysql,查看是否配置成功 mysql> show master status;若显示有FIile和Posttion就表示注linux的主节点配置成功…

【算法】一文带你从浅至深入门dp动态规划

文章目录 一、前言二、动态规划理论基础1、基本概念2、动态规划五部曲【✔】3、出错了如何排查&#xff1f; 三、实战演练&#x1f5e1;0x00 斐波那契数0x01 第N个泰波那契数0x02 爬楼梯0x03 三步问题0x04 使用最小花费爬楼梯⭐解法一解法二 0x05 解码方法* 四、总结与提炼 一、…

输入时并未按照格式,没注意汉字符号

&#x1f388;问题现象&#xff1a; 运行出来的代码没得到想要的结果&#xff1a; &#x1f388;原因分析&#xff1a; 程序运行起来了&#xff0c;计算的结果是错误的&#xff0c;这个最好的解决办法就是调试&#xff0c;一步步的看代码在每个阶段的值是不是我们期望的&…

docker-compose deploy 高可用 elasticsearch TLS

文章目录 1.sysctl2. swap3. hosts4. 配置 instances.yaml5. 创建证书6. 部署7. 修改 kibanna 密码8. 清理 1.sysctl [rootgithub es_tls]# cat /etc/sysctl.conf # sysctl settings are defined through files in # /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/…

Redis优化 RDB AOF持久化

---------------------- Redis 高可用 ---------------------------------------- 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境…

【SpringMVC】实现增删改查(附源码)

目录 引言 一、前期准备 1.1.搭建Maven环境 1.2.导入pom.xml依赖 1.3.导入配置文件 ①jdbc.properties ②generatorConfig.xml ③log4j2.xml ④spring-mybatis.xml ⑤spring-context.xml ⑥spring-mvc.xml ⑦修改web.xml文件 二、逆向生成增删改查 2.1.导入相关u…

长胜证券:融券打新虽失宠 券源分配仍需透明

近期&#xff0c;关于战略投资者出借限售股作为融券券源的准则备受商场热议。不少投资者担心&#xff0c;跟着新股的大都券源被量化私募掌握&#xff0c;量化私募融券打新的战略有或许成为新股上市首日上涨后回身跌跌不休的首要原因。 券源分配是否有失公允&#xff1f;融券打…

基于SSM的家居商城系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【云原生】Kubeadmin安装k8s集群

目录 前言&#xff1a; 一 环境部署 1.1 服务器部署功能 1.2 环境准备&#xff08;所有节点&#xff09; 二 安装docker&#xff08;所有节点&#xff09; 三 所有节点安装kubeadm&#xff0c;kubelet和kubectl 3.1 定义kubernetes源 3.2 开机自启kubelet 四 部署K8S集…

【LeetCode】剑指 Offer <二刷>(6)

目录 题目&#xff1a;剑指 Offer 12. 矩阵中的路径 - 力扣&#xff08;LeetCode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;剑指 Offer 13. 机器人的运动范围 - 力扣&#…

自然语言处理(七):来自Transformers的双向编码器表示(BERT)

来自Transformers的双向编码器表示&#xff08;BERT&#xff09; BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是一种预训练的自然语言处理模型&#xff0c;由Google于2018年提出。它是基于Transformer模型架构的深度双向&#xff0…

Google云数据库的“Enterprise“和“Enterprise Plus“版怎么选

最近&#xff0c;Google Cloud SQL&#xff08;Google云上的RDS&#xff09;做了一次大的产品调整与发布&#xff1a;将原来的Cloud SQL分为了两个版本&#xff0c;分别为"Enterprise"和"Enterprise Plus"版本。本文概述了两个版本的异同&#xff0c;以帮助…

Python算法练习 9.11

leetcode 392 判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcd…

PlantUML——类图(持续更新)

前言 在分析代码流程中&#xff0c;我们常常会使用到各种UML图&#xff0c;例如用例图、时序图和类图等&#xff0c;以往使用ProcessOn或亿图图示等工具&#xff0c;但是这些工具难以规范化&#xff0c;有没有一种用代码来生成图形的工具呢&#xff1f; 刚好在出差的晨会中机缘…

springboot MongoDB 主从 多数据源

上一篇&#xff0c;我写了关于用一个map管理mongodb多个数据源&#xff08;每个数据源&#xff0c;只有单例&#xff09;的内容。 springboot mongodb 配置多数据源 临到部署到阿里云的测试环境&#xff0c;发现还需要考虑一下主从的问题&#xff0c;阿里云买的数据库&#x…

vue+springboot+mysql的垃圾分类管理系统

1、引言 设计结课作业,课程设计无处下手&#xff0c;网页要求的总数量太多&#xff1f;没有合适的模板&#xff1f;数据库&#xff0c;java&#xff0c;python&#xff0c;vue&#xff0c;html作业复杂工程量过大&#xff1f;毕设毫无头绪等等一系列问题。你想要解决的问题&am…

离散性行业介绍及与MES系统的好处

离散型行业是指那些生产、制造或提供一种有形产品或明确定义的服务的行业&#xff0c;这些产品或服务通常可以分为离散的单位&#xff0c;而且它们的生产通常遵循一定的工序或流程。与连续型行业不同&#xff0c;离散型行业的生产过程通常是间断的&#xff0c;而不是连续的。 …

什么是lockbit勒索病毒,中了勒索病毒怎么办?勒索病毒解密,数据恢复

lockbit是一种勒索病毒&#xff0c;是一种极具破坏性的电脑病毒&#xff0c;它利用加密技术来锁定用户文件&#xff0c;并以此为条件向用户勒索钱财。lockbit病毒的传播方式有通过电子邮件附件、恶意网站、点对点网络等多种途径进行传播。这种病毒一旦侵入电脑系统&#xff0c;…