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

目录

一、概念和规则:

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;让网络服务器以为有很多平常的请求&#…

二十五:如何“合法”地跨域访问?

跨域访问&#xff08;Cross-Origin Resource Sharing&#xff0c;简称CORS&#xff09;是现代Web开发中常见的一种技术需求。由于浏览器的同源策略&#xff08;Same-Origin Policy&#xff09;&#xff0c;不同域名、协议或端口之间的资源无法直接交互&#xff0c;这对前端开发…

计算机网络的功能

目录 信息交换 资源共享 分布式处理 可靠性增强 集中管理 信息交换 计算机网络最基本的功能之一是允许不同设备之间的数据通信。这包括电子邮件的发送和接收、即时消息的传递、文件传输等。通过网络&#xff0c;用户可以轻松地与全球各地的其他人进行沟通和协作。 信息交…

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

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

NUMA架构及在极速网络IO场景下的优化实践

NUMA技术原理 NUMA架构概述 随着多核CPU的普及&#xff0c;传统的对称多处理器&#xff08;SMP&#xff09;架构逐渐暴露出性能瓶颈。为了应对这一问题&#xff0c;非一致性内存访问&#xff08;NUMA, Non-Uniform Memory Access&#xff09;架构应运而生。NUMA架构是一种内存…

论文笔记(五十七)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…

Android 13 Aosp Settings Android Studio版本

Android 13 Aosp Settings Android Studio版本 Settings相关源码 Settings https://android.googlesource.com/platform/packages/apps/Settings/+/refs/heads/android13-release SettingsIntelligence https://android.googlesource.com/platform/packages/apps/SettingsIn…

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

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

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

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

软件测试丨Pytest生命周期与数据驱动

Pytest的生命周期概述 Pytest 是一个强大的测试框架&#xff0c;提供了丰富的特性来简化测试执行。它的生命周期包括多个阶段&#xff0c;涉及从准备测试、执行测试到报告结果的完整流程。因此&#xff0c;理解Pytest的生命周期将帮助我们更好地设计和管理测试用例。 开始阶段…

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

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

Python学习——猜拳小游戏

import random player int(input(“请输入&#xff1a;剪刀 0&#xff0c;石头 1&#xff0c;布2”)) computer random.randint(0,2)# print(“玩家输入的是%d&#xff0c;电脑输入的是%d” %(player,computer)) 用于测试 if (player 0) and (computer 0) or (player 1) a…

从零样本到少样本学习:一文读懂 Zero-shot、One-shot 和 Few-shot 的核心原理与应用!

爆款标题&#xff1a; 《从零样本到少样本学习&#xff1a;一文读懂 Zero-shot、One-shot 和 Few-shot 的核心原理与应用&#xff01;》 正文&#xff1a; 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Zero-shot、One-shot 和 Few-shot 学习已经成为衡量大语言…

Kafka知识体系

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

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

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

@sun.misc.Contended

某个类有以下两个属性 volatile int a; volatile int b; int为4字节&#xff0c;两个int为8字节&#xff0c;一个缓存行大小为64字节 故一旦缓存行a失效了&#xff0c;但是b没失效&#xff0c;会连带着b一起失效&#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 算子 二、合并与连接操作 …

解锁Scala编程:深入文本分析与数据处理的艺术

引言&#xff1a; 在数据科学的世界里&#xff0c;Scala以其强大的并发能力和简洁的语法&#xff0c;成为处理大规模数据集的理想选择。本文将带您踏上一段Scala编程的探索之旅&#xff0c;从基础的文本分析到复杂的数据处理&#xff0c;每一步都精心设计&#xff0c;让您在实…

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 提示工程…