26 红黑树

目录

1.概念
2.性质
3.节点定义
4.结构
5.插入
6.验证
7.删除
8.红黑树和avl树比较
9.应用

概念

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

在这里插入图片描述

性质

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

第5条指的是空节点,方便数出二叉树的路径,上面的红黑树共有11条路径,每条路径都有2个黑色节点

最短路径:全黑
最长路径:一黑一红间隔
假设共有N个黑色节点,最短路径logN,最长路径2logN
假设每条路径都有N个黑色节点,每条路径的结点数量[N,2*N]之间

avl树是绝对平衡,红黑树是相对平横,最长路径不超过最短路径的2倍

节点定义

枚举一个颜色,红和黑,节点中加入颜色

enum color
{RED,BLACK
};template <class K, class V>
struct TreeNode
{struct TreeNode<K, V>* _parent;struct TreeNode<K, V>* _left;struct TreeNode<K, V>* _right;std::pair<K, V> _kv;color _col;TreeNode(std::pair<K, V> kv):_parent(nullptr), _left(nullptr), _right(nullptr), _kv(kv), _col(RED){}
};

为什么节点默认是红色?
因为每条路径的黑色节点数量都必须相等,如果插入结点默认为黑色,就会对所有路径造成影响。如果是红色,最多只会影响自己所在的路径

红黑树结构

为了后续实现关联式容器简单,红黑树的实现中可以增加一个头结点,因为根节点必须为黑色,为了与根节点区分,将头结点给成黑色,并且让头结点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的结点,_pRight域指向红黑树最大的结点,如下,本节的实现不采用头结点
在这里插入图片描述

插入

红黑树是在二叉搜索树的基础上加入其平衡限制条件,因此红黑树的插入可分为两步:

插入结点

#pragma once
#include <iostream>
#include <assert.h>
#include <queue>enum color
{RED,BLACK
};template <class K, class V>
struct TreeNode
{struct TreeNode<K, V>* _parent;struct TreeNode<K, V>* _left;struct TreeNode<K, V>* _right;std::pair<K, V> _kv;color _col;TreeNode(std::pair<K, V> kv):_parent(nullptr), _left(nullptr), _right(nullptr), _kv(kv), _col(RED){}
};template <class K, class V>
class RBTree
{typedef TreeNode<K, V> node;
public:bool insert(const std::pair<K, V>& kv){if (_root == nullptr){_root = new node(kv);_root->_col = BLACK;return true;}node* parent = nullptr;node* cur = _root;while (cur){parent = cur;if (kv.first < cur->_kv.first){cur = cur->_left;}else if (kv.first > cur->_kv.first){cur = cur->_right;}else{return false;}}//插入cur = new node(kv);cur->_parent = parent;if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}	}private:node* _root = nullptr;
};

检测新节点插入后,红黑树的性质是否遭到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲颜色是红色时,就违反了性质三不能有连在一起的红色节点,此时分情况讨论:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

1.当a/b/c/d/e都是空,cur就是新增
在这里插入图片描述

2.c/d/e是每条路径1个黑色节点的红黑树,那么就是x/y/z/r中任意一种
在这里插入图片描述
cur更改,变为黑色节点,左右各连a和b节点,cur就是在a和b插入,就会失衡
在这里插入图片描述

cde共有444种可能,插入位置有4个,就是64*4=256种可能

3.当每条路径变为2个黑色节点的子树
在这里插入图片描述
在这里插入图片描述

第一个是C88,C87…C80,再加上下面两个,第一个2个红色节点是C42,可以换一边,就是C42*2,第二个是C44,如果这些是100种可能,c,d,e就有1003种可能,其他更不用算了

从上面所有肯能找出一种规律,解决插入的相关问题
如果插入结点的双亲是黑色,不需要处理,是红色时才需要下面内容

  • 情况一:cur为红,p为红,g为黑,u存在且为红
  • 注意:此处看到的树,可能是一颗完整的树,也可能是一颗子树
    在这里插入图片描述

上面的右边是左边变色而来,p和u变黑,g变红,就平衡了
如果g是根节点,调整完成后,需要改为黑色

如果g是子树的根,调整完成后,g的双亲如果是黑色,就要继续往上调整
在这里插入图片描述
解决方式,将p和u改为黑,g改为红,然后把g当做cur,继续向上调整

  • 情况二,cur为红,p为红,g为黑,u不存在/存在且为黑

在这里插入图片描述
u的情况有两种:
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4
2.如果u节点存在,则一定是黑色的,那么cur原来的颜色也是黑色,现在看到的红色是因为cur的子树在调整的过程中将cur节点的颜色由黑色变为红色
在这里插入图片描述

在这里插入图片描述
p为g的左孩子,cur为p的左孩子,则进行右单旋转,相反
p为g的右孩子,cur为p的右孩子,则进行左单旋转

  • 情况三,cur为红,p为红,g为黑,u不存在/存在且为黑

和情况二类似
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转,旋转后变成了情况二,再右单旋转
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,旋转后变成了情况二,再左单旋转

在这里插入图片描述

调整

//父节点是红色调整
while (parent && parent->_col == RED)
{node* gdparent = parent->_parent;node* uncle;if (gdparent->_left == parent){uncle = gdparent->_right;//第一种情况 叔叔节点是红色,变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;gdparent->_col = RED;}//第二种情况 分左右//     g//  par  un// curelse{if (cur == parent->_left){RotateRight(gdparent);parent->_col = BLACK;gdparent->_col = RED;}//     g//  par  un//    curelse{RotateLeft(parent);RotateRight(gdparent);cur->_col = BLACK;parent->_col = RED;gdparent->_col = RED;}break;}}else{uncle = gdparent->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;gdparent->_col = RED;}else{if (cur == parent->_right){RotateLeft(gdparent);parent->_col = BLACK;gdparent->_col = RED;}//     g//  par  un//    curelse{RotateRight(parent);RotateLeft(gdparent);cur->_col = BLACK;//parent->_col = RED;gdparent->_col = RED;}break;}}cur = gdparent;parent = cur->_parent;
}//根必须是黑色
_root->_col = BLACK;
return true;

根节点在最后必须变为黑色,在函数最后部分统一改变
首先判断par在g的左边的三种情况,叔叔节点存在且是红色直接变色,更新节点向上。叔叔节点不存在或为黑色,判断cur在par的哪边,采取不同的旋转,然后改变节点颜色
par在g右边是相反

插入过程:
在这里插入图片描述

红黑树的验证

红黑树的验证分为两步:
1.检测是否满足二叉搜索树的(中序遍历是否有序序列)
2.检测是否满足红黑树的性质
先判断根节点是否为黑色,再记录一条路径黑色节点数量用来参考,递归红黑树,遇见黑色节点,黑色节点数量++。如果是红色节点,判断它的父节点是否为红色。遇到空,判断这条路径黑色节点数量和参考值是否一样

bool IsBalance()
{if (_root->_col == RED){std::cout << "根节点是红色" << std::endl;return false;}int refVal = 0;  //记录最左路径黑色节点的数量,用来参考node* cur = _root;while (cur){if (cur->_col == BLACK){refVal++;}cur = cur->_left;}return _IsBalance(_root, 0, refVal);
}bool _IsBalance(node* cur, int blackNum, int refVal)
{if (cur == nullptr){//判断每条路径黑色节点数量正常if (blackNum != refVal){std::cout << "黑色节点数量不相等" << std::endl;return false;}return true;}if (cur->_col == BLACK){blackNum++;}if (cur->_col == RED && cur->_parent->_col == RED){std::cout << cur->_kv.first << " 连续红色节点" << std::endl;}return _IsBalance(cur->_left, blackNum, refVal) && _IsBalance(cur->_right, blackNum, refVal);
}

删除

参考《算法导论》或者《STL源码剖析》
https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

这里只提供思路
删除前部分内容和avl树都一样,如果删除的节点有两个孩子,就找前驱节点替换,这样所有情况都变成了删除节点只有一个孩子的情况,如果没有孩子也可以当做只有一个孩子处理

父亲节点称为par,删除节点del,孩子节点为child

  • 第一种情况,删除的结点是红色,它如果有孩子一定是黑色,只需要将par链接到child
  • 第二种情况,删除节点是黑色,child为红色,还是将par连接到child,将child改为黑色
  • 第三种情况,删除节点是黑色,child也为黑色,这时考察del的兄弟节点,v有两种情况

情况1,v是黑色节点,v的左子女是w,根据w的颜色讨论:
下图g为祖父节点,u为del的child节点,v是del的兄弟节点,del节点在g的右孩子,删除后g连接到u
当删除了del时,g的右边ul和ur少了一个黑色节点,g的左边的每条路径wl,wr,vr都有相等的黑色节点

根据节点w的状态,有以下情况:
设平衡路径有2个黑色节点
1.w是红色节点,设wl,wr,vr都有2个黑色节点,wl,wr,vr中各有一个黑色节点,ul和ur无黑色节点。以g为中心右旋,vr接到g的左边,v变为红色,w和g改为黑色。此时,wl和wr里面各有1个黑色,w也是黑色,还是2个。vr中有1个黑色,加上g也是2个,u是黑色,g是黑色,ul和ur路径也是两个黑色节点

2.w是黑色节点,这时看w的有兄弟r节点,根据r分情况
① r是红色节点,通过一次先左后右的双旋,将g染成黑色
wl和wr无黑色节点,rl和rr各有1个黑色。旋转完后,右边增加一个黑色节点
② r是黑色节点,r是黑色节点,这时就要看g的颜色,如果g是红色,只要交换g和子女v的颜色。如果g是黑色,可以做一次右单旋转,将节点v上升染成双重黑色,消除节点u的双重黑色,双重黑色向根的方向移动
在这里插入图片描述
情况2,v是红色节点,考察有子女r,r一定是黑色节点,在看r的左子女s,根绝s的颜色分两种情况:
(1)s是红色,先左后右双旋,s染黑色,r上升,使包含u的路径的黑高度加1
在这里插入图片描述

(2) s是黑色,再看s的右兄弟节点t,根绝t的颜色分情况:
① 如果t为红色,先以t左旋,t替补r的位置,然后再以t先左后右的双旋,消除u的双重黑色
在这里插入图片描述

② 若节点t是黑色,以v为轴右单旋转,并改变v和r的颜色
在这里插入图片描述

节点u是g的左子女的情况是镜像的

红黑树和avl树的比较

都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2N log2N),红黑树不追求绝对平衡,只要保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以再经常增删的结构中更优,实际中红黑树用的更多

红黑树的应用

1.C++STL库,map/set、mutil_map/mutil_set
2.java库
3.linux内核
4.其他一些库

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

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

相关文章

reverse-android-实战喜马拉雅-ollvm

资料 1. apk: com.ximalaya.ting.android.apk. 2020年8月 可以使用 2. 抓包分析 java层分析 so层分析 登录的算法so是在 liblogin_encrypt.so中。 32位的&#xff0c; 用 IDA打开&#xff0c;查看 静态的导出函数。 打开 一个 首先看到 IDA VIEW 是一个横向 比较多的分支&am…

2-9 基于matlab的传递矩阵计算轴的模态

基于matlab的传递矩阵计算轴的模态&#xff0c;包括模态频率和模态振型&#xff0c;可设置轴的结构参数。程序已调通&#xff0c;可直接运行。 2-9 传递矩阵计算轴的模态 模态频率 - 小红书 (xiaohongshu.com)

python-jupyter notebook安装教程

&#x1f308;所属专栏&#xff1a;【python】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的…

Jmeter 逻辑控制之IF条件控制器

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试环境 JMeter-5.4.1 循环控制器介绍 添加While Controller 右键线程组->添加->逻辑…

简易计算器需求报告

1. &#xff08;简易计算器&#xff09; 需求说明书 文件编号&#xff1a;2022[1] [木柚2] 06[3] [木柚4] 01[5] [木柚6] 完成日期&#xff1a;2024年 06月18日 编制&#xff1a; 易正阳 日期&#xff1a;2024年6月18日 审核&#xff1a;张正 日期&#xff1a;2024年6月18…

Vue微前端架构与Qiankun实践理论指南

title: Vue微前端架构与Qiankun实践理论指南 date: 2024/6/15 updated: 2024/6/15 author: cmdragon excerpt: 这篇文章介绍了微前端架构概念&#xff0c;聚焦于如何在Vue.js项目中应用Qiankun框架实现模块化和组件化&#xff0c;以达到高效开发和维护的目的。讨论了Qiankun…

软件测试面试题:性能测试关注哪些指标?

问题 在工作中&#xff0c;使用JMeter做压力测试时&#xff0c;需要关注其中的哪些指标&#xff1f; 性能测试关注哪些指标&#xff1f; 考察点 面试官想了解&#xff1a; 是否用过 JMeter 指标进行分析 技术点 涉及的技术点&#xff1a; JMeter 结果分析 回答 性能指…

gitblit git pycharm 新建版本库及push备忘

在终端l中输入ssh,如果有消息弹出说明安装成功。 // 在任意路径打开GIT BASH,执行以下命令,期间所有询问可以直接Enter跳过 ssh-keygen -t rsa -C "注册Gitlab的邮箱" “”之内可以任何文字,备注提示作用。 设置用户名和邮箱 已经设置的可以检查一下。 #设置用…

SpringBoot配置第三方专业缓存框架j2cache

j2cache的使用 这不是一个缓存 这是一个缓存框架 J2Cache, 也称为Java Cache或JSR-107&#xff0c;是一个用于缓存管理的标准API&#xff0c;它允许开发者在Java应用程序中实现分布式、基于内存的缓存。J2Cache主要通过javax.cache.Cache接口提供功能&#xff0c;用于存储和…

RoaringBitMap处理海量数据内存diff

一、背景 假设mysql库中有一张近千万的客户信息表(未分表)&#xff0c;其中有客户性别&#xff0c;等级(10个等级)&#xff0c;参与某某活动等字段 1、如果要通过等级性别其他条件(离散度也低)筛选出客户&#xff0c;如何处理查询&#xff1f; 2、参与活动是记录活动ID&#…

了解Nest.js

一直做前端开发&#xff0c;都会有成为全栈工程师的想法&#xff0c;而 Nest 就是一个很好的途径&#xff0c;它是 Node 最流行的企业级开发框架&#xff0c;提供了 IOC、AOP、微服务等架构特性。接下来就让我们一起来学习Nest.js Nest.js官网地址 一&#xff0c;了解Nest Cli …

充电学习—6、电量计FuelGauge

电量计功能&#xff1a; 检测电池 计量电量 电量计首要工作&#xff1a; 计算电池的剩余容量、充满时容量、电量百分比 电量百分比 剩余容量 / 充满时容量 * 100% SOC RM / FCC * 100% 典型的一个电池包框架&#xff1a; 包含电芯、电量计IC、保护IC、充放电MOSFET、保险丝…

栈帧浅析,堆栈漏洞概述——【太原理工大学软件安全期末补充】

在上一篇文章中我说实验一不重要&#xff0c;确实没必要完全按照实验内容逐字逐句理解&#xff0c;但是这里我们补充一个知识点 栈帧&#xff08;Stack Frame&#xff09;是计算机程序执行过程中&#xff0c;调用栈&#xff08;Call Stack&#xff09;中的一个单元&#xff0c;…

hugging face:大模型时代的github介绍

1. Hugging Face是什么&#xff1a; Hugging Face大模型时代的“github”&#xff0c;很多人有个这样的认知&#xff0c;但是我觉得不完全准确&#xff0c;他们相似的地方在于资源丰富&#xff0c;github有各种各样的软件代码和示例&#xff0c;但是它不是系统的&#xff0c;没…

Linux-DNS域名解析服务01

BIND 域名服务基础 1、DNS&#xff08;Domain Name System&#xff09;系统的作用及类型 整个 Internet 大家庭中连接了数以亿计的服务器、个人主机&#xff0c;其中大部分的网站、邮件等服务器都使用了域名形式的地址&#xff0c;如 www.google.com、mail.163.com 等。很显然…

探索C嘎嘎的奇妙世界:第十四关---STL(string的模拟实现)

1. string类的模拟实现 1.1 经典的string类问题 上一关已经对string类进行了简单的介绍&#xff0c;大家只要能够正常使用即可。在面试中&#xff0c;面试官总喜欢让学生自己来模拟实现string类&#xff0c;最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数…

MacOS - command not found: brew

问题描述 command not found: brew 原因分析 没有安装 Homebrew&#xff0c;安装后即可使用~ 解决方案 打开终端&#xff0c;输入&#xff1a;/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"&#xff0c;点击回车 在弹出…

小程序的登录+发布流程

今天我们来将一下小程序的登录和发布流程&#xff01;&#xff01;&#xff01; 小程序的登录流程 流程图 首先登录流程还是看官网说的&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 这是官网发布的一个流程图 认识cod…

2 图片的分割处理和亚像素精度处理(c++和python)

本文的图片处理分为图片分割、图像的亚像素坐标处理。亚像素处理的原理可以看论文一种基于多项式插值改进的亚像素细分算法&#xff0c;该论文的详解及c的代码实现可以看博文基于多项式插值的亚像素边缘定位算法_基于多项式插值的亚像素算法-CSDN博客。下面的内容很多来自以上博…

【论文阅读】-- 时间空间化:用于深度分类器训练的可扩展且可靠的时间旅行可视化

Temporality Spatialization: A Scalable and Faithful Time-Travelling Visualization for Deep Classifier Training 摘要1 引言2 动机3 问题定义4 方法论4.1 时空复合体4.2 复数约简 5 实验6 相关工作7 结论参考文献 摘要 时间旅行可视化回答了深度分类器的预测是如何在训练…