红黑树的概念以及基本模拟

目录

一、概念和规则:

1、思考为什么最长路径不超过最短路径的二倍?

2、红黑树的效率?

二、红黑树的代码实现

1、红黑树的节点结构

2、红黑树的插入

1、大致过程:

2、维护的三种情况:

1、情况一:变色

2、情况二:单旋+变色

 3、情况三:双旋+变色

3、红黑树的验证

4、整体代码


一、概念和规则:

  • 红黑树是一颗搜索二叉树,满足左小右大(或者左小右大)的规则;(前提规则)
  • 红黑树每一个节点不是黑色就是红色;(规则一)
  • 根节点是黑色的;(规则二)
  • 不能出现连续的红色节点,若一个节点是红色的,那么它的两个孩子都是黑色的;(规则三)
  • 对于任何一条简单路径(从根到空),其上的黑色节点的数量都是相同的;(规则四)
  • 红黑树的最长路径不超过最短路径的二倍(规则推论)

1、思考为什么最长路径不超过最短路径的二倍?

由于每条路径的黑色节点的数量的个数相同,极端情况下最短路径的长度就是全是黑色节点的数量,最长路径长度就是红色黑色相间的路径,那么恰好就是最短路径的二倍;那么其他的路径长度都在最长与最短之间,那么最长路径就不会超过最短路径的二倍。

2、红黑树的效率?

假设红黑树最短路径的高度为H,那么最长路径的长度最长为 2*H ,其他的都在这两者之间,那么节点的数量N(根据等比求和)就在 2^H-1到2^(2*H)-1之间,那么红黑树增删查的时间复杂度还是O(logN),和AVL属于同一量级;

AVL是通过左右子树高度差来控制平衡;而红黑树是通过规则和颜色来达到近似平衡。

二、红黑树的代码实现

1、红黑树的节点结构

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

这里用枚举定义颜色,方便观察;

2、红黑树的插入

1、大致过程:

  • 先按照二叉搜索树进行插入;
  • 若是空树,就将新插入的节点认作根节点,并且把颜色赋为黑色;若不是空树,那么插入之后,这个新插入的节点必须赋为红色,否则会破坏所有路径黑色节点数量相同的规则;但是连续的两个红色也会破坏,但是连续的两个红色更好维护;
  • 插入红色的,若插入的父节点是黑色的,那么就是正常的,无需维护,插入完成直接退出;
  • 插入红色的,若插入的父节点是红色的,那就需要维护,因为此时破坏了规则;而维护有三种情况,维护的关键是看叔节点,也就是父亲的兄弟节点;

2、维护的三种情况:

1、情况一:变色

此种情况出现适用于:当uncle节点存在并且是红色时;

首先grandfather节点(parent和uncle的父节点)时黑色的,parent和uncle节点是红色的,这是确定的条件;此时给parent插入红色的新节点,要维护规则;进行变色:将grandfather变红,parent和uncle变黑;

一次变完之后,因为grandfather变红了,但是其上可能还有节点,当其父节点为红时,还需要继续调整,将cur赋值为grandfather,parent跟着向上调,uncle随之变化;直到父节点的颜色为黑或者父节点为根节点时结束循环;最后会单独处理根节点,无论根节是哪种颜色,都赋为黑;

2、情况二:单旋+变色

适用情形,uncle为空或者为黑时;此时只是单纯的变色解决不了问题,因为每条简单路径上面的黑色节点数量会不同;

当uncle不存在时,那么cur一定是新插入进来的,若不是新插入的那么cur下面还有黑色节点,那么parent的左右子树的黑色节点数量就不同;若uncle存在且为黑色,那么cur一定不是新插入的红色节点,而是cur下面的节点通过变色变上来的红色节点,否则parent的左右子树黑色节点的数量就不同,违反规则;

那么就要将grandfather作为旋转基准进行左单旋或者右单旋,旋转之后parent成了cur和grandfather的根节点,此时再把parent变黑,grandfather变红 ;无论是uncle存在与否,最后都能达到维护的目的,并且每条简单路径上面的黑色节点的数量都相同。

旋转并且变色之后这颗红黑树整体上就已经满足规则了,无需像情况一一样向上调整。

 3、情况三:双旋+变色

首先和情况二前提相同:若uncle不存在,则cur一定为新插入的;若uncle存在且为黑,则cur一定不是新插入的,而是调整上来的;

双旋的原因:不同于单旋,当parent是grandfather的左节点但是cur是father的右节点时,单旋不能解决问题,若是单旋,只是交换了左右位置,本质上还是没有完成规则的维护;此时需要双旋之后再变色;

第一次循环以parent为基准旋转,第二次以grandfather为基准旋转;最后将cur变为黑,grandfather变为红;

3、红黑树的验证

 红黑树的验证要严格按照四条规则来,验证每一条规则是否被满足,只有当所有的规则都被满足才说明这棵树是红黑树;

  1. 直接判断根节点是否是黑色
  2. 每个节点不是黑就是红,这个天然而成无需判断
  3. 检查是否有红色连续的情况,通过递归每次检查当前节点的父亲,当前节点为红色并且当前节点的父节点也是红色时,就返回false;检查父节点更方便;若是每次检查当前节点的左右子树节点相较麻烦一点
  4. 检查每条简单路径上面是否含有相同黑色节点数量时,先计算一条路径上面的黑色节点数量作为一个比较的基准值,再在递归的过程中计算每条路径的黑色节点数量,当递归的根为空时说明一条路径走完了,此时比较,若不想等就返回false;整体的返回是左右子树通过&&连接来返回,也就是有一条不满足的这棵树就不是一颗红黑树的意思

4、整体代码

#include<iostream>
using namespace std;enum Color
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{Color _col;pair<K, V> _kv;RBTreeNode<K, V>* _parent;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode(const pair<K,V> kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr){}
};template<class K,class V>
class RBTree
{using Node = RBTreeNode<K, V>;
public:void Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;}Node* parent = nullptr;Node* cur = _root;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;}}cur = new Node(kv);if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;cur->_col = RED;//parent为空或者 parent颜色为黑色,插入了满足条件,不用进循环//进循环说明 parent为红并且其父节点为黑,新插入的只能是红while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//两个大方向:parent在 grandfather左边或者右边if (grandfather->_left == parent){Node* uncle = grandfather->_right;//首先是不用旋转的情况,此时无论 cur在 parent哪边都一样//将 grandfather变红,father和 uncle变黑//这种情况就是 uncle存在并且为红时if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;cur = grandfather;parent = cur->_parent;}//这里的 else含义是:uncle为空或者不为空但是颜色是黑色//要旋转+变色else{//uncle若是为空,cur就必是新插入的节点,否则每一条路径的黑色节点数量不同//uncle若不为空,cur就必不是新插入的节点,这样每条路径的黑色节点数量才可能相同//无论为空为否,都要进行旋转再变色if (parent->_left == cur){//右单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;break;//旋转完之后满足规则}else{//先左旋再右旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;break;}}}//parent为 grandfather的右孩子else{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 = RED;break;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;break;}}}}//将根节点置为黑色_root->_col = BLACK;}Node* Find(const K& k){Node* cur = _root;while (cur){if (k < cur->_kv.first){cur = cur->_left;}else if (k > cur->_kv.first){cur = cur->_right;}else{return cur;}}}bool IsRBTree(){if (!_root)return true;if (_root->_col == RED)return false;int mode_count = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)mode_count++;cur = cur->_left;}return Check(_root, 0, mode_count);}void Print(){_Print(_root);cout << endl;}private:Node* _root = nullptr;//右单旋void RotateR(Node* sub){Node* subl = sub->_left;Node* sublr = subl->_right;sub->_left = sublr;if (sublr)sublr->_parent = sub;Node* sub_P = sub->_parent;subl->_right = sub;sub->_parent = subl;if (!sub_P){_root = subl;subl->_parent = nullptr;}else{if (sub_P->_left == sub){sub_P->_left = subl;}else{sub_P->_right = subl;}subl->_parent = sub_P;}}//左单旋void RotateL(Node* sub){Node* subr = sub->_right;Node* subrl = subr->_left;sub->_right = subrl;if (subrl){subrl->_parent = sub;}Node* sub_P = sub->_parent;subr->_left = sub;sub->_parent = subr;if (!sub_P){_root = subr;subr->_parent = nullptr;}else{if (sub_P->_left == sub){sub_P->_left = subr;}else{sub_P->_right = subr;}subr->_parent = sub_P;}}void _Print(Node* root){if (!root){return;}_Print(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Print(root->_right);}bool Check(Node* root, int count, const int mode_count){if (root == nullptr){if (count != mode_count){cout << "存在不同路径的黑色节点数量不同";return false;}return true;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续的红色节点";return false;}if (root->_col == BLACK)count++;return Check(root->_left, count, mode_count) && Check(root->_right, count, mode_count);}
};

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

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

相关文章

IP反向追踪技术,了解一下?

DOSS&#xff08;拒绝服务&#xff09;攻击是现在比较常见的网络攻击手段。想象一下&#xff0c;有某个恶意分子想要搞垮某个网站&#xff0c;他就会使用DOSS攻击。这种攻击常常使用的方式是IP欺骗。他会伪装成正常的IP地址&#xff0c;让网络服务器以为有很多平常的请求&#…

【C++习题】15.滑动窗口_串联所有单词的子串

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 30. 串联所有单词的子串 题目描述&#xff1a; 解法 滑动窗口哈希表 这题和第14题不同的是&#xff1a; 哈希表不同&#xff1a;hash<string,int>left与right指…

论文笔记(五十七)Diffusion Model Predictive Control

Diffusion Model Predictive Control 文章概括摘要1. Introduction2. Related work3. 方法3.1 模型预测控制3.2. 模型学习3.3. 规划&#xff08;Planning&#xff09;3.4. 适应 4. 实验&#xff08;Experiments&#xff09;4.1. 对于固定奖励&#xff0c;D-MPC 可与其他离线 RL…

oracle 创建只可以查询权限用户+sqldeveloper如何看到对应表

声明 申明部分是从其他csdn用户哪里复制的&#xff0c;只是自己操作后发现无法达到我最后的预期&#xff0c;所以关闭忘记是看的那篇了&#xff0c;如果有侵权请见谅&#xff0c;联系我删除谢谢。 好了&#xff0c;故事的开始是我最近删投产表了。没错职业黑点&#xff0c;清…

比特币libsecp256k1中safegcd算法形式化验证完成

1. 引言 比特币和其他链&#xff08;如 Liquid&#xff09;的安全性取决于 ECDSA 和 Schnorr 签名等数字签名算法的使用。Bitcoin Core 和 Liquid 都使用名为 libsecp256k1 的 C 库来提供这些数字签名算法&#xff0c;该库以其所运行的椭圆曲线命名。这些算法利用一种称为modu…

15分钟做完一个小程序,腾讯这个工具有点东西

我记得很久之前&#xff0c;我们都在讲什么低代码/无代码平台&#xff0c;这个概念很久了&#xff0c;但是&#xff0c;一直没有很好的落地&#xff0c;整体的效果也不算好。 自从去年 ChatGPT 这类大模型大火以来&#xff0c;各大科技公司也都推出了很多 AI 代码助手&#xff…

Kafka知识体系

一、认识Kafka 1. kafka适用场景 消息系统&#xff1a;kafka不仅具备传统的系统解耦、流量削峰、缓冲、异步通信、可扩展性、可恢复性等功能&#xff0c;还有其他消息系统难以实现的消息顺序消费及消息回溯功能。 存储系统&#xff1a;kafka把消息持久化到磁盘上&#xff0c…

JVM调优篇之JVM基础入门AND字节码文件解读

目录 Java程序编译class文件内容常量池附录-访问标识表附录-常量池类型列表 Java程序编译 Java文件通过编译成class文件后&#xff0c;通过JVM虚拟机解释字节码文件转为操作系统执行的二进制码运行。 规范 Java虚拟机有自己的一套规范&#xff0c;遵循这套规范&#xff0c;任…

【Petri网导论学习笔记】Petri网导论入门学习(十一) —— 3.3 变迁发生序列与Petri网语言

目录 3.3 变迁发生序列与Petri网语言定义 3.4定义 3.5定义 3.6定理 3.5例 3.9定义 3.7例 3.10定理 3.6定理 3.7 有界Petri网泵引理推论 3.5定义 3.9定理 3.8定义 3.10定义 3.11定义 3.12定理 3.93.3 变迁发生序列与Petri网语言 对于 Petri 网进行分析的另一种方法是考察网系统…

Flink--API 之Transformation-转换算子的使用解析

目录 一、常用转换算子详解 &#xff08;一&#xff09;map 算子 &#xff08;二&#xff09;flatMap 算子 &#xff08;三&#xff09;filter 算子 &#xff08;四&#xff09;keyBy 算子 元组类型 POJO &#xff08;五&#xff09;reduce 算子 二、合并与连接操作 …

Top 10 Tools to Level Up Your Prompt Engineering Skills

此文章文字是转载翻译&#xff0c;图片是自已用AI 重新生成的。文字内容来自 https://www.aifire.co/p/top-10-ai-prompt-engineering-tools 供记录学习使用。 Introduction to AI Prompt Engineering AI Prompt Engineering 简介 1&#xff0c;Prompt Engineering 提示工程…

Rust语言俄罗斯方块(漂亮的界面案例+详细的代码解说+完美运行)

tetris-demo A Tetris example written in Rust using Piston in under 500 lines of code 项目地址: https://gitcode.com/gh_mirrors/te/tetris-demo 项目介绍 "Tetris Example in Rust, v2" 是一个用Rust语言编写的俄罗斯方块游戏示例。这个项目不仅是一个简单…

Spring Boot 与 Spring Cloud Alibaba 版本兼容对照

版本选择要点 Spring Boot 3.x 与 Spring Cloud Alibaba 2022.0.x Spring Boot 3.x 基于 Jakarta EE&#xff0c;javax.* 更换为 jakarta.*。 需要使用 Spring Cloud 2022.0.x 和 Spring Cloud Alibaba 2022.0.x。 Alibaba 2022.0.x 对 Spring Boot 3.x 的支持在其发行说明中…

(免费送源码)计算机毕业设计原创定制:Java+ssm+JSP+Ajax SSM棕榈校园论坛的开发

摘要 随着计算机科学技术的高速发展,计算机成了人们日常生活的必需品&#xff0c;从而也带动了一系列与此相关产业&#xff0c;是人们的生活发生了翻天覆地的变化&#xff0c;而网络化的出现也在改变着人们传统的生活方式&#xff0c;包括工作&#xff0c;学习&#xff0c;社交…

Ubuntu Opencv 源码包安装

说明&#xff1a; ubuntu20.04 建议 使用 opencv-4.6.0版本 ubuntu18.04 建议 使用 opencv-4.5.2-版本 安装包准备 1、下载源码包 OpenCV官网 下载相关版本源码 Sources # 克隆方式 OpenCV 源码git clone https://github.com/opencv/opencv.gitcd opencvgit checkout 4.5.2 …

Linux 下自动化之路:达梦数据库定期备份并推送至 GitLab 全攻略

目录 环境准备 生成SSH 密钥对 数据库备份并推送到gitlab脚本 设置定时任务 环境准备 服务器要有安装达梦数据库&#xff08;达梦安装这里就不示例了&#xff09;&#xff0c;git 安装Git 1、首先&#xff0c;确保包列表是最新的&#xff0c;运行以下命令&#xff1a; …

<项目代码>YOLOv8 停车场空位识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

Spring Boot 集成 Knife4j 的 Swagger 文档

在开发微服务应用时&#xff0c;API 文档的生成和维护是非常重要的一环。Swagger 是一个非常流行的 API 文档工具&#xff0c;可以帮助我们自动生成 RESTful API 的文档&#xff0c;并提供了一个友好的界面供开发者测试 API。本文将介绍如何在 Spring Boot 项目中集成 Knife4j …

微信小程序中会议列表页面的前后端实现

题外话&#xff1a;想通过集成腾讯IM来解决即时聊天的问题&#xff0c;如果含语音视频&#xff0c;腾讯组件一年5万起步&#xff0c;贵了&#xff01;后面我们改为自己实现这个功能&#xff0c;这里只是个总结而已。 图文会诊需求 首先是个图文列表界面 同个界面可以查看具体…

git(Linux)

1.git 三板斧 基本准备工作&#xff1a; 把远端仓库拉拉取到本地了 .git --> 本地仓库 git在提交的时候&#xff0c;只会提交变化的部分 就可以在当前目录下新增代码了 test.c 并没有被仓库管理起来 怎么添加&#xff1f; 1.1 git add test.c 也不算完全添加到仓库里面&…