二叉搜索树-----红黑树

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:数据结构——红黑树
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:红黑树也是一颗二叉搜索树,其作为map,set的底层容器,具有非常好的搜索性能,仅仅通过控制颜色和位置就能达到一种,近似平衡的效果,大大减少了旋转的次数。

目录

一.红黑树的概念

二. 红黑树的性质

三.红黑树节点及其整体的定义

四.红黑树的插入操作

五.红黑树 find

六.析构函数

七.红黑树的验证

八. 红黑树与AVL树的比较


一.红黑树的概念

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

二. 红黑树的性质

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

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

答案:因为性质3限制了,一条路径上,不可能出现连续的两个红色结点。又因为性质4,每条路径上黑色结点数目相同,那么最短路径就一定是全是黑色结点的路径,最长路径一定是红黑交错的路径,因为根节点一定是黑色,那么最长路径上红黑结点树一定是相等的,所以最长路径最多是最短路径的两倍

三.红黑树节点及其整体的定义

//枚举
enum Color
{RED,//红色BLACK//黑色
};
template<class K,class V>
struct _RBTreeNode
{_RBTreeNode(pair<K,V> kv):_kv(kv),_col(RED),//默认红色_left(nullptr),_right(nullptr),_parent(nullptr){}pair<K, V> _kv;              //存储数值Color _col;                  //颜色_RBTreeNode<K, V>* _left;    //左孩子_RBTreeNode<K, V>* _right;   //右孩子_RBTreeNode<K, V>* _parent;  //父亲
};
#pragma once
#include<iostream>
using namespace std;template<class K,class V>
class RBTRee
{typedef _RBTreeNode<K, V> Node;//结点
public:Node* find(const K key){//....}bool insert(pair<K, V> kv){//....}void Inorder(){//...}~RBTRee(){//...}private:Node* _root = nullptr;//根节点
};

思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?

答案:新创建的结点,妖色要么红色,要么黑色,除了颜色区别之外,就是在插入时对整个树的影响不同,如果插入的是黑色,会影响整颗树,所有路径上的黑色结点说就会不同,必然违反性质4。如果插入的是红色结点,仅仅是局部的影响,可能会影响性质3,一定不会影响性质4。

 四.红黑树的插入操作

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

1. 按照二叉搜索的树规则插入新节点。

bool insert(pair<K, V> kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}//找到了合适的位置cur = new Node(kv);if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//....  
}

因为性质2,所以我们每一次插入数据都想根节点变成黑色。

2.检测新节点插入后,红黑树的性质是否造到破坏,若满足直接退出,否则对红黑树进行旋转着色处理。

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

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

情况一: cur为红,p为红,g为黑,u存在且为红


解决方式:将 p , u 改为黑,g 改为红,然后把 g 当成 cur,继续向上调整。

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

  • p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
  • p、g变色--p变黑,g变红

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

  • p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;

  • p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
  • p、g变色--p变黑,g变红

这里的cur的也有可能是新增的结点,如果是cur本身就是新增节点那么u结点就是不存在的,否则违反规则 4,也有可能是因为cur下面的结点变黑导致 cur 变红色。

代码:

    while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//           g(B)                     g(R)//     p(R)       u(R)  -->     p(B)       u(B)//c(R)                     c(R)if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else //u不存在/u存在且为黑,旋转+变色{//           g(B)                   p(R)//     p(R)       u(B)   -->  u(B)        g(B)//c(R)										     u(B)if (cur == parent->_left){//右单旋RotateR(grandfather);parent->_col = BLACK;//cur->_col = RED;grandfather->_col = RED;}else{//            g(B)                   P(B)   //     p(R)         u(B)  --> c(R)          g(R)//         c(R)                                     u(B)// 左右双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else //grandfather->_right == parent,与上述情况相反{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else //u不存在/u存在且为黑,旋转+变色{if (cur == parent->_right){//左单旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 右左双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}

 旋转:

    void RotateL(Node* parent){Node* curR = parent->_right;Node* curRL = curR->_left;//调整结点,并且修改其父亲结点指针parent->_right = curRL;if (curRL)//可能为空{curRL->_parent = parent;}//在修改子树根节点之前,保存子树根节点的父亲Node* pparent = parent->_parent;//修改子树根节点curR->_left = parent;parent->_parent = curR;//子树根节点有可能是整棵树的根节点if (pparent == nullptr){_root = curR;_root->_parent = nullptr;}else//子树根节点不是整棵树的根节点{//还要看子树是它父亲的左孩子还是右孩子if (pparent->_left == parent){pparent->_left = curR;}else{pparent->_right = curR;}curR->_parent = pparent;}}void RotateR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;parent->_left = curLR;if (curLR){curLR->_parent = parent;}Node* pparent = parent->_parent;curL->_right = parent;parent->_parent = curL;if (parent == _root){_root = curL;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = curL;}else{pparent->_right = curL;}curL->_parent = pparent;}}

红黑树顺序插入:

 红黑树随机插入:

五.红黑树 find

根据二叉搜索树特性去查找:

    Node* find(const K key){Node* cur = _root;while (cur){if (key < cur->_kv.first){cur = cur->_left;}else if (key > cur->_kv.first){cur = cur->_right;}else{return cur;}}return nullptr;}

六.析构函数

后续遍历析构树:

    ~RBTRee(){_Destrory(_root);_root = nullptr;}void _Destrory(Node* root){if (root == nullptr){return;}_Destrory(root->_left);_Destrory(root->_right);delete root;}

七.红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质

 其中是否满足搜索树我们只要对其中序遍历是否有序即可。

 完整代码:

#pragma once
#include<iostream>
using namespace std;enum Color
{RED,BLACK
};
template<class K,class V>
struct _RBTreeNode
{_RBTreeNode(pair<K,V> kv):_kv(kv),_col(RED),_left(nullptr),_right(nullptr),_parent(nullptr){}pair<K, V> _kv;Color _col;_RBTreeNode<K, V>* _left;_RBTreeNode<K, V>* _right;_RBTreeNode<K, V>* _parent;
};template<class K,class V>
class RBTRee
{typedef _RBTreeNode<K, V> Node;
public:Node* find(const K key){Node* cur = _root;while (cur){if (key < cur->_kv.first){cur = cur->_left;}else if (key > cur->_kv.first){cur = cur->_right;}else{return cur;}}return nullptr;}bool insert(pair<K, V> kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);//找到了合适的位置if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while ( parent && parent->_col == RED){Node* grandfather = parent->_parent;//           g(B)                     g(R)//     p(R)       u(R)  -->     p(B)       u(B)//c(R)                     c(R)if ( grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else //u不存在/u存在且为黑,旋转+变色{//           g(B)                   p(R)//     p(R)       u(B)   -->  u(B)        g(B)//c(R)										     u(B)if (cur == parent->_left){//右单旋RotateR(grandfather);parent->_col = BLACK;//cur->_col = RED;grandfather->_col = RED;}else{//            g(B)                   P(B)   //     p(R)         u(B)  --> c(R)          g(R)//         c(R)                                     u(B)// 左右双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else //grandfather->_right == parent,与上述情况相反{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else //u不存在/u存在且为黑,旋转+变色{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;}void Inorder(vector<K> & v){_inorder(_root,v);cout << endl;}~RBTRee(){_Destrory(_root);_root = nullptr;}private:void _Destrory(Node* root){if (root == nullptr){return;}_Destrory(root->_left);_Destrory(root->_right);delete root;}void _inorder(Node* root, vector<K>& v){if (root == nullptr){return;}_inorder(root->_left,v);v.push_back(root->_kv.first);_inorder(root->_right,v);}void RotateL(Node* parent){Node* curR = parent->_right;Node* curRL = curR->_left;//调整结点,并且修改其父亲结点指针parent->_right = curRL;if (curRL)//可能为空{curRL->_parent = parent;}//在修改子树根节点之前,保存子树根节点的父亲Node* pparent = parent->_parent;//修改子树根节点curR->_left = parent;parent->_parent = curR;//子树根节点有可能是整棵树的根节点if (pparent == nullptr){_root = curR;_root->_parent = nullptr;}else//子树根节点不是整棵树的根节点{//还要看子树是它父亲的左孩子还是右孩子if (pparent->_left == parent){pparent->_left = curR;}else{pparent->_right = curR;}curR->_parent = pparent;}}void RotateR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;parent->_left = curLR;if (curLR){curLR->_parent = parent;}Node* pparent = parent->_parent;curL->_right = parent;parent->_parent = curL;if (parent == _root){_root = curL;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = curL;}else{pparent->_right = curL;}curL->_parent = pparent;}}Node* _root = nullptr;};
	//传参时benchmark是-1,代表还没有基准值,当走完第一条路径时,//将第一条路径的黑色节点数作为基准值,后续路径走到null时,就与基准值比较。//blacknum记录路径上的黑色节点数bool _isRBTree(Node* root, int blacknum, int benchmark){if (root == nullptr){if (benchmark == -1){benchmark = blacknum;}else{if (blacknum != benchmark){cout << "black Node !=" << endl;return false;}}return true;}if (root->_col == BLACK){blacknum++;}//判断时候出现两个连续的红色结点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "red connect " << endl;return false;}return _isRBTree(root->_left, blacknum, benchmark) && _isRBTree(root->_right, blacknum, benchmark);}

main.cpp

#include<vector>
#include<cassert>
#include"RB_Tree.hpp"int main()
{RBTRee<int, int> rb;int i = 100000;while(i--){int num = rand() + i;rb.insert(make_pair(num,num));}vector<int> v;rb.Inorder(v);for (int i = 0; i < v.size() - 1; i++){if (v[i] > v[i + 1]){assert(0);}}cout << rb.isRBTree() << endl;return 0;
}

 八. 红黑树与AVL树的比较

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

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

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

相关文章

[PyTorch][chapter 51][Auto-Encoder -1]

目录&#xff1a; 简介 损失函数 自动编码器的类型 一 AutoEncoder 简介&#xff1a; 自动编码器是一种神经网络&#xff0c;用于无监督学习任务.(没有标签或标记数据), 例如降维,特征提取和数据压缩. 主要任务&#xff1a; 1&#xff1a; 输入数据 …

功能强大的网站检测工具Web-Check

什么是 Web-Check &#xff1f; Web-Check是一款功能强大的一体化工具&#xff0c;用于查找有关网站/主机的信息。目前仪表版上可以显示&#xff1a;IP 信息、SSL 信息、DNS 记录、cookie、请求头、域信息、搜索爬虫规则、页面地图、服务器位置、开放端口、跟踪路由、DNS 安全扩…

设计模式之抽象工厂

文章目录 一、介绍二、基本组件三、演示案例1. 定义抽象工厂2. 定义抽象产品3. 定义具体工厂4. 定义具体产品5. 代码演示6. 代码改造 四、总结 一、介绍 抽象工厂模式(Abstract Factory Pattern)属于创建型设计模式。用于解决比工厂方法设计模式更加复杂的问题。 复杂到哪里了…

3.Redis 单线程模型

redis 单线程模型 redis 只使用一个线程来处理所有的命令请求&#xff0c;并不是说一个 redis 服务器进程内部真的就只有一个线程&#xff0c;其实也有多个线程&#xff0c;多个线程是再处理网络 IO。 那么在多线程中&#xff0c;针对类似于这样的场景两个线程尝试同时对一个…

【JVM 内存结构丨栈】

栈 -- 虚拟机栈 简介定义压栈出栈局部变量表操作数栈方法调用特点作用 本地方法栈&#xff08;C栈&#xff09;定义栈帧变化作用对比 主页传送门&#xff1a;&#x1f4c0; 传送 简介 栈是用于执行线程的内存区域&#xff0c;它包括局部变量和操作数栈。 Java 虚拟机栈会为每…

探索Java集合框架—数据结构、ArrayList集合

一、背景介绍 Java集合的使用相信大家都已经非常得心应手&#xff0c;但是我们怎么做到知其然&#xff0c;更知其所以然这种出神入化的境界呢&#xff1f;我们揭开集合框架底层神秘面纱来一探究竟 目录 一、背景介绍 二、思路&方案 数据结构是什么&#xff1f; 数据结…

Aspose.Tasks for .NET V23Crack

Aspose.Tasks for .NET V23Crack 改进了大型项目的内存占用。 添加了API&#xff0c;允许您在应用程序无法访问系统字体文件夹时指定用户的字体文件夹。 Aspose.Tasksfor.NET是处理MicrosoftProject文件的可靠的项目管理API。API支持在不依赖Microsoft Project的情况下读取、写…

【C++】list

list 1. 简单了解list2. list的常见接口3. 简单实现list4. vector和list比较 1. 简单了解list list的底层是带头双向循环列表。因此list支持任意位置的插入和删除&#xff0c;且效率较高。但其缺陷也很明显&#xff0c;由于各节点在物理空间是不连续的&#xff0c;所以不支持对…

ElasticSearch简介、安装、使用

一、什么是ElasticSearch&#xff1f; Elasticsearch 是 Elastic Stack 核心的分布式搜索和分析引擎。 Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。 Kibana 使您能够以交互方式探索、可视化和分享对数据的见解&#xff0c;并管理和监…

如何搭建智能家居系统并通过内网穿透实现远程控制家中设备

文章目录 前言1. 安装Home Assistant2. 配置Home Assistant3. 安装cpolar内网穿透3.1 windows系统3.2 Linux系统3.3 macOS系统 4. 映射Home Assistant端口5. 公网访问Home Assistant6. 固定公网地址6.1 保留一个固定二级子域名6.2 配置固定二级子域名 前言 Home Assistant&…

stm32基于HAL库驱动外部SPI flash制作虚拟U盘

stm32基于HAL库驱动外部SPI flash制作虚拟U盘 &#x1f4cc;参考文章&#xff1a;https://xiaozhuanlan.com/topic/6058234791&#x1f39e;实现效果演示&#xff1a; &#x1f516;上图中的读到的FLASH_ID所指的是针对不同容量&#xff0c;所对应的ID。 //W25X/Q不同容量对应…

【面试】线上 CPU 100% 问题排查

回答套路一般为&#xff1a;线上服务器没有排查过&#xff0c;线上服务器只有运维才有操作权限。在平时开发的时候&#xff0c;在测试服务器上排查过。 一、复现代码 public class Test {public static void main( String[] args ){int a 0;while (a < 100) {a * 10;}} }…

Redis知识点总结

概述 Redis诞生于2009年&#xff0c;全称是Remote Dictionarty Server(远程词典服务器) 只支持单线程 非关联&#xff1a;主要指的是表中没有主外键等概念 Redis是一款内存数据库&#xff0c;主要存储键值对类型的数据 基本用法 注意&#xff1a;该操作是在cli中进行的 首…

windows安装多个版本node

1.下载nvm-setup版本 安装过程会检测到你当前使用的node版本 提示 Node v12.14.0 is already installed. Do you want NVM to control this version? 翻译&#xff1a;已安装节点v12.14.0。你想让NVM控制这个版本吗? 选择 是(Y)。 nvm默认为我们增添了环境变量&#xff0c;…

docker 安装 Wordpress 用lnmp搭建出现的故障

第一个故障就是mysql出现的故障了 你起mysql镜像是这么起的导致pid号用不了 docker run --namemysql -d --privileged --device-write-bps /dev/sda:10M -v /usr/local/mysql --net mynetwork --ip 172.20.0.20 mysql:lnmp 解决方法 docker run --namemysql -d --privilege…

【MySQL系列】统计函数(count,sum,avg)详解

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Java中HashMap的基本介绍和详细讲解,HashMap的遍历以及HashMap的底层源码的分析

HashMap 基本介绍 HashMap 是 Java 中的一个集合类&#xff0c;实现了 Map 接口&#xff0c;用于存储键值对&#xff08;key-value&#xff09;数据。它基于哈希表的数据结构实现&#xff0c;可以实现高效的查找、插入和删除操作。 HashMap 细节讨论 无序性&#xff1a; Has…

[Linux]命令行参数和进程优先级

[Linux]命令行参数和进程优先级 文章目录 [Linux]命令行参数和进程优先级命令行参数命令行参数的概念命令函参数的接收编写代码验证 进程优先级进程优先级的概念PRI and NI使用top指令修改nice值 命令行参数 命令行参数的概念 命令行参数是指用于运行程序时在命令行输入的参数…

Qt6和Rust结合构建桌面应用

桌面应用程序是原生的、快速的、安全的&#xff0c;并提供Web应用程序无法比拟的体验。 Rust 是一种低级静态类型多范式编程语言&#xff0c;专注于安全性和性能&#xff0c;解决了 C/C 长期以来一直在努力解决的问题&#xff0c;例如内存错误和构建并发程序。 在桌面应用程序开…

ToolAI–全球最完整最全面的AI人工智能工具集合

ToolAI是一个全球最完整最全面的AI人工智能工具集合网站&#xff0c;收集了全球最完整的数千个AI网站、工具、app&#xff0c;包含文案写作、邮件助手、聊天机器人、社交媒体等等各种行业类型的AI工具&#xff0c;可以按照地区或者分类进行查找浏览&#xff0c;目前收集6800 人…