【C++进阶】红黑树
目录
- 什么是红黑树?
- 红黑树
- 红黑树的性质
- 定义红黑树
- 红黑树的操作
- insert
- inorder
- find
- height
- size
- 构造函数
- 析构函数
- 赋值拷贝
- 判断红黑树
- 全部代码
- 总结
什么是红黑树?
红黑树
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,用于保持树的平衡,以确保在最坏情况下基本操作(如插入、删除和查找)的时间复杂度仍为 O(log n)。红黑树的每个节点都包含一个额外的颜色位,即红色或黑色。红黑树通过严格的平衡条件来维持树的平衡。
红黑树的性质
- 节点是红色或黑色的:每个节点都有颜色属性,红色或黑色。
- 根是黑色的:红黑树的根节点必须是黑色的。
- 叶节点(NIL 节点)是黑色的:红黑树中的所有叶节点(这里的叶节点是指为空的 NIL 节点,而不是实际的元素节点)都是黑色的。
- 红色节点的子节点必须是黑色的(即红色节点不能有红色子节点,也叫“红黑相间”):红色节点不能连续连接在一起。
- 从任何节点到其每个叶子的所有简单路径都必须包含相同数量的黑色节点:这确保了树的平衡性。
定义红黑树
enum Color
{RED,BLACK,
};
template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Color _col;RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){}
};
template<class K,class V>
class RBTree
{
public:typedef RBTreeNode<K, V> Node;
private:Node* _root = nullptr;
};
红黑树的操作
insert
红黑树的逻辑是在普通的二叉搜索树上进行实现的,在普通的二叉搜索树中,我们不需要调节平衡,但是在红黑树当中我们需要根据红黑树的性质来调整数的颜色和高度,首先我们来看看第一种情况:

cur是插入节点,由于红色节点不能连续,所以这里已经破坏了平衡,所以我们应该进行调整颜色,我们不可能插入cur是黑色节点,因为如果插入黑色节点的话代价很大,每条路上都会多出来一个黑色节点,这样很不好维护,所以插入红色节点是代价最小的,插入红色节点之后,破坏了规则,所以p应该变成黑色,由于p这条路多了一个黑色节点,所以u应该也变成黑色节点,由于这棵树有可能是某个子树,这两条路多出来一个黑色节点为了保证黑色节点不多,所以g节点应该也变成红色,这样做完之后继续往上调整,将cur赋值到g节点,继续往上调整颜色。

这样就算调整完毕了。
第二种情况:当u节点不存在时

对于这种情况我们先准备调色,首先我们先将p改为黑色节点,然后将g改为红色节点之后,我们会发现,左边这条路多出来一个黑色节点,这样是不行的。

上面这个是不行的,左边已经多出来了一个黑色节点了,但是右边没有黑色节点,所以这种情况我们应该进行旋转。
对g节点进行右旋:

进行右旋之后将p改为黑色节点,将g改为红色节点,这样两边都保持平衡了。

还有一种情况:u存在但是为黑色

最下面的那个红色节点是插入节点:
这种情况我们先向上调整:

调整成这样之后,我们就发现,u是黑色节点,不管我们怎么调整,左边都会多出一个黑色节点,所以这里我们应该旋转:
绕着g节点进行旋转之后我们可以发现调整颜色就会平衡:

还有双旋的情况,双旋的情况,我们只说一种:

左旋将p和cur的位置交换,然后再对g进行右旋,就可以达到平衡的效果。
左旋接口:
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;else parentParent->_right = subR;subR->_parent = parentParent;}
}
右旋接口:
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;else parentParent->_right = subL;subL->_parent = parentParent;}
}
insert的代码:
bool Insert(const 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 (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else return false;}cur = new Node(kv);cur->_col = RED;//插入到右边if (parent->_kv.first < kv.first)parent->_right = cur;else parent->_left = cur;cur->_parent = parent;//调整颜色或旋转//parent不为空并且parent指向的颜色是红色才进行调整while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;//uncle存在且是红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}//uncle不存在或者存在为黑色else{//判断cur是parent的左子树还是右子树://右单旋if (cur == parent->_left){RotateR(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else{Node* uncle = grandparent->_left;//uncle存在且是红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}//uncle不存在或者存在为黑色else{//判断cur是parent的左子树还是右子树://右单旋if (cur == parent->_right){RotateL(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
inorder
void InOrder()
{_InOrder(_root);
}
void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ':' << root->_kv.second << endl;_InOrder(root->_right);
}
find
bool Find(const K& k)
{Node* cur = _root;while (cur){if (cur->_kv.first < k) cur = cur->_right;else if (cur->_kv.first > k) cur = cur->_left;else return true;}return false;
}
height
int height()
{return _height(_root);
}
int _height(Node* root)
{if (root == nullptr)return 0;int leftheight = _height(root->_left);int rightheight = _height(root->_right);return max(leftheight, rightheight) + 1;
}
size
int size()
{return _size(_root);
}
int _size(Node* root)
{if (root == nullptr)return 0;int left = _size(root->_left);int right = _size(root->_right);return left + right + 1;
}
构造函数
//默认构造
RBTree() = default;
//拷贝构造
RBTree(const RBTree<K, V>& t)
{_root = Copy(t._root);
}
Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* newnode = new Node(root->_kv);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;
}
析构函数
~RBTree()
{Destroy(_root);_root = nullptr;
}
void Destroy(Node* root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;
}
赋值拷贝
RBTree<K, V>& operator=(RBTree<K, V> t)
{swap(_root, t._root);return *this;
}
判断红黑树
bool IsBalance()
{//空树也是搜索树if (_root == nullptr) return true;//根节点不能为红色if (_root->_col == RED) return false;//参考值int refNum = 0;Node* cur = _root;while (cur){//遇到黑色++if (cur->_col == BLACK) refNum++;cur = cur->_left;}return Check(_root, 0, refNum);
}
bool Check(Node* root, int blackNum, const int refNum)
{//走到空之后就算出一条路径的数量了if (root == nullptr){if (blackNum != refNum){cout << "存在黑色节点不相等的路径" << endl;return false;}return true;}//存在连续的红色节点if (root->_col == RED && root->_parent->_col == RED)return false;if (root->_col == BLACK)blackNum++;return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}
全部代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
enum Color
{RED,BLACK,
};
template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Color _col;RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){}
};
template<class K,class V>
class RBTree
{
public:typedef RBTreeNode<K, V> Node;//默认构造RBTree() = default;//拷贝构造RBTree(const RBTree<K, V>& t){_root = Copy(t._root);}//赋值拷贝RBTree<K, V>& operator=(RBTree<K, V> t){swap(_root, t._root);return *this;}//析构函数~RBTree(){Destroy(_root);_root = nullptr;}//查找bool Find(const K& k){Node* cur = _root;while (cur){if (cur->_kv.first < k) cur = cur->_right;else if (cur->_kv.first > k) cur = cur->_left;else return true;}return false;}//中序遍历void InOrder(){_InOrder(_root);}//插入bool Insert(const 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 (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else return false;}cur = new Node(kv);cur->_col = RED;//插入到右边if (parent->_kv.first < kv.first)parent->_right = cur;else parent->_left = cur;cur->_parent = parent;//调整颜色或旋转//parent不为空并且parent指向的颜色是红色才进行调整while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (grandparent->_left == parent){Node* uncle = grandparent->_right;//uncle存在且是红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}//uncle不存在或者存在为黑色else{//判断cur是parent的左子树还是右子树://右单旋if (cur == parent->_left){RotateR(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else{Node* uncle = grandparent->_left;//uncle存在且是红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}//uncle不存在或者存在为黑色else{//判断cur是parent的左子树还是右子树://右单旋if (cur == parent->_right){RotateL(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;return true;}//大小int size(){return _size(_root);}//树的高度int height(){return _height(_root);}
private:int _height(Node* root){if (root == nullptr)return 0;int leftheight = _height(root->_left);int rightheight = _height(root->_right);return max(leftheight, rightheight) + 1;}int _size(Node* root){if (root == nullptr)return 0;int left = _size(root->_left);int right = _size(root->_right);return left + right + 1;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;else parentParent->_right = subR;subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;else parentParent->_right = subL;subL->_parent = parentParent;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ':' << root->_kv.second << endl;_InOrder(root->_right);}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newnode = new Node(root->_kv);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}Node* _root = nullptr;
};
总结
红黑树作为一种自平衡二叉搜索树,通过严格的颜色和旋转规则,保证了在最坏情况下仍能提供高效的查找、插入和删除操作。其特有的平衡机制使得红黑树在实际应用中被广泛采用,尤其在需要频繁增删节点的数据结构中表现出色。掌握红黑树的原理和操作,不仅有助于理解复杂数据结构的实现,也为开发高效算法奠定了坚实基础。希望通过本篇博客,大家能够对红黑树有更加深入的认识,并在实际编程中灵活应用。
相关文章:
【C++进阶】红黑树
目录 什么是红黑树?红黑树红黑树的性质 定义红黑树红黑树的操作insertinorderfindheightsize构造函数析构函数赋值拷贝判断红黑树 全部代码总结 什么是红黑树? 红黑树 红黑树(Red-Black Tree)是一种自平衡的二叉搜索树ÿ…...
linux使用ssh连接一直弹出密码框问题
1.查看ssh服务的状态 输入以下命令: sudo service sshd status 小编已经安装了。 如果出现 Loaded: error (Reason: No such file or directory) 提示的话,说名没有安装ssh服务,按照第二步:安装ssh服务。 如果出现 Active: in…...
Python 3 数据结构
Python 3 数据结构 引言 Python 是一种高级编程语言,因其简洁明了的语法和强大的功能而广受欢迎。在 Python 中,数据结构是组织和存储数据的方式,对于编写高效和可维护的代码至关重要。本文将深入探讨 Python 3 中的主要数据结构࿰…...
【开源社区】Elasticsearch(ES)中空值字段 null_value 及通过exists查找非空文档
文章目录 0、声明1、问题描述2、问题剖析2.1 NULL或者空值类型有哪些2.2 案例讲解:尝试检索值为 null 的字段2.3 解决思路 3、使用 null_value 的诸多坑(避免生产事故)3.1 null_value 替换的是索引,并不会直接替换源数据3.2 不支持…...
JavaDS —— 位图(BitSet)与 布隆过滤器
位图 引入问题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。 首先要注意 40 亿个数据如果使用 整型(int) 来存放的话,就是要 40 亿个整型,一个整型有…...
如何确保场外个股期权交易的安全?
如何确保场外个股期权交易的安全?投资者可以采取以下措施,以提高交易的安全性和减少风险: 增强知识储备:深入学习期权的基础知识,包括不同类型的期权、它们的权利和义务、定价方式以及风险特性,从而提升自…...
第2章:LabVIEW FPGA未来发展方向《LabVIEW ZYNQ FPGA宝典》
2.1:NI的LabVIEW FPGA未来战略部署 在展望NI公司的LabVIEW FPGA技术未来发展趋势之前,让我们先来回顾一下LabVIEW与FPGA的技术发展历程,如图2-1所示。可以看出,NI公司的LabVIEW FPGA软件一方面是跟随Xilinx最新的FPGA硬件可持续发…...
苹果电脑维护工具:CleanMyMac X让你的Mac焕发新生!
在我们的数字生活中,苹果电脑(Mac)已成为不可或缺的一部分,无论是为工作披星戴月,还是为娱乐畅游云端。但是,就像任何长时间运行的机器一样,Mac也可能会因为积累的文件和不必要的数据而开始变慢…...
MySQL2 DML数据操纵语言和SQL约束
DML和SQL约束 SQL-DML1.添加数据2.修改数据3.删除 TRUNCATE和DELETE的区别:SQL-约束Primary Key创建主键约束单列主键联合主键**验证主键约束**删除主键约束设置主键自增AUTO_INCREMENTdelete和truncate删除后,主键的自增 SQL-唯一约束UNIQUE创建唯一约束…...
Ubuntu 20.04 中安装 Nginx (通过传包编译的方式)、开启关闭防火墙、开放端口号
文章目录 前言一、安装包下载二、上传服务器并解压缩三、依赖配置安装四、生成编译脚本五、编译六、查看是否编译完成七、开始安装八、查看是否安装成功九、设置为开机自启动 前言 参考大佬文章并在基础上做了点修改,发篇文章记录下 防止下次遇到。 参考文章&#…...
解决no main manifest attribute错误
文章目录 0. 背景1. java程序如何运行2. jar是什么3. java -jar test-1.0-SNAPSHOT.jar:4. 添加执行入口 0. 背景 在开发Spring boot项目的时候,有时候会需要使用java -jar test-1.0-SNAPSHOT.jar指令来运行开发的java应用,但是很不幸&#…...
002 | 常见的金融量化指标计算
金融量化指标 在金融量化分析中,常用的指标可以帮助我们判断市场走势、评估风险和收益,以及构建交易策略。以下是一些常见的金融量化指标及其计算方法的详细教程,包括公式与Python代码实现。 1. 移动平均线(Moving Average, MA&…...
Web Vitals:提升用户体验的关键指标
Web Vitals 是 Google 提出的一套核心网页性能指标,旨在帮助开发者理解和优化网站的用户体验。这些指标分为核心 Web Vitals 和附加 Web Vitals,涵盖了加载性能、交互性和视觉稳定性三个方面。以下是详细的介绍和如何使用 Web Vitals 来优化你的网站。 …...
c#中的约束、TimeSpan、defult、operator
c#中的约束 在C#中,约束(Constraints)用于限制泛型类型参数的类型,以确保泛型类型或方法在编译时能够满足特定的要求。约束允许开发者指定泛型类型参数必须满足的条件,比如实现特定的接口或继承自特定的类。以下是一些…...
挖矿木马攻破了服务器
最近被国外的挖矿木马攻破了服务器 根据非法登录,用 #last指令查看登录ip 首先删掉登录主机 #kill -9 pts/0 第二步 #top 看看什么占用cpu高 第三步杀死狂刷CPU的服务 过一分钟后,服务又开始狂刷cpu。 第四步根据pid查到服务地址 #systemctl status…...
从容应对技术面试:策略、技巧与成功案例
欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: 工💗重💗hao💗:野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来:详细讲解AIGC的概念、核心技术、应用领域等内容。 ⭐…...
Spring Boot 整合 RestTemplate:详解与实战
Spring Boot 整合 RestTemplate:详解与实战指南 一、引言二、依赖添加Maven 示例:Gradle 示例: 三、创建 RestTemplate 实例四、使用 RestTemplate 发起请求五、处理响应六、高级用法1. 自定义 RestTemplate 实例2. 文件上传、下载以及常见的…...
【利用模板模式和责任链模式实现数据校验】
利用模板模式和责任链模式实现数据校验 一、业务背景二、模板模式和责任链模式代码实现1、数据校验抽象处理器ValidateHandler2、数据校验责任链工具类ValidateChainUtil3、网元调整数据校验抽象类AbstractNodeCheckHandler4、依次定义3个责任链handler,通过Order注…...
学习笔记第十九天
1.标准I/O的基本概念 标准输入(stdin):默认是指键盘输入。 标准输出(stdout):默认是指显示器输出。 标准错误(stderr):用于输出错误信息,也是指向显示器&…...
设计模式 - 单例模式
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言一、单例模…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
