【C++】红黑树的实现

news2024/11/27 12:53:18

文章目录

  • 📕 概念
    • 特性
  • 📕 红黑树具体实现
    • 节点定义
    • 结构框架
    • 插入

📕 概念

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

特性

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

如下,是一个红黑树的例子。其叶子节点都是黑色,指的是最后的 NULL 节点。
该红黑树一共有 11 条路径,因为有 11 个 NULL 节点(叶子节点)。

请添加图片描述

满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍
其实很好理解,每个节点非红即黑,红色节点不允许连续。那么最短的路径的节点一定是全黑的(假设有 n 个黑色节点),最长路径的节点是一黑一红,而由于每条路径上黑色节点数目相同,所以最长路径也有n个黑色节点,由于一黑一红,所以红色节点也是n个,最长路径有 2n 个节点。这就是其原理。

📕 红黑树具体实现

节点定义

如下,红黑树里面需要有颜色,可以用枚举。

同时,当 new 一个节点的时候,默认是红色,这是因为,如果插入一个节点,默认为黑色,那么就会导致这个节点所在路径的黑色节点数目+1,原本一棵红黑树的结构收到破坏(性质4不满足)。
所以,默认是红色可以减少插入新节点对红黑树造成的影响。

enum Colour {
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode {
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

结构框架

如下,和 AVL 树大致类似,区别只是 AVL 树里面的平衡因子换成了红黑树的颜色。

#pragma once
#include<iostream>
#include<cassert>

using namespace std;

namespace simulate {
	
enum Colour {
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode {
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};


template<class K, class V>
class RBTree {

	typedef struct RBTreeNode<K, V> Node;

public:
	// 成员方法

private:
	Node* _root;
};

}

插入

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

  1. 按照二叉搜索的树规则插入新节点。
  2. 检测新节点插入后,红黑树的性质是否造到破坏。

对于第一步,就不必赘述了,过于简单。

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

情况一

cur为红,p为红,g为黑,u存在且为红。
cur为新插入的节点,p为 cur 的父亲,g 为 cur 的祖父,u 为 cur 的叔叔(p的兄弟)。

解决方法:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
因为在当前这棵树(可能是子树)其余部分忽略,只剩下 g、p、u三个节点。并且看为两层,g为第一层,p、u 为第二层,当前这棵树,任意一条路径,都会经过第一层,也必然会经过第二层。

修改颜色前,第一层的一个节点,颜色是黑色;第二层的两个节点,颜色是红色,所以任意一条路径,经过这两层,会有一个黑色节点。
修改颜色后,第一层的一个节点,颜色是红色;第二层的两个节点,颜色是黑色,所以,任意一条路径,经过这两层,也必然会有一个黑色节点。
我们只改变了这两层的颜色,但是改变前后,任意一条路径经过这两层黑色节点的数目并没有发生改变!

而如果 g 就是根节点,则无法向上继续修改颜色了,根据规则 根节点要是黑色,将其改成黑色。根节点颜色变成黑色,每条路径的黑色节点个数在修改根节点颜色之前的基础上 +1,那么,修改前后,每条路径黑色节点个数都是相等的。
请添加图片描述

如果修改之后,g 并不是根节点,那么遵循如下规则:

  • 如果 g 的父亲节点颜色是黑色,可以不修改。
  • 如果 g 的父亲节点颜色是红色,把 g 当作 cur ,继续向上调整。

请添加图片描述

情况二

cur为红,p为红,g为黑,u不存在 / u存在且为黑

解决方式:
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转。
p、g变色——p变黑,g变红。

如下,查看各个路径的黑色节点数目。很显然,旋转前后,a、b、c、d、e 任何一个区域内,任何一个路径(以 NULL 为叶子节点),其黑色节点的数目没有改变。

请添加图片描述

情况三

cur为红,p为红,g为黑,u不存在/u存在且为黑

解决方法:
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转。
则转换成了情况2,然后依据情况二来处理

请添加图片描述

虽然这里分了三类,但是每一类都会面临一个问题: p 是 g 的左孩子,还是 g 的右孩子?

所以,实际写代码的时候,也可以按照 p 是 g 的左/右孩子来分类,然后三种情况一次在每一类中实现。

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr) {
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) { // 找到要插入的位置
			if (cur->_kv.first > kv.first) {
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first) {
				parent = cur;
				cur = cur->_right;
			}
			else
				return false;
		}

		// 插入
		cur = new Node(kv);
		if (parent->_kv.first < kv.first) {
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first) {
			parent->_left = cur;
		}
		cur->_parent = parent;


		// 调整颜色
		while (parent && parent->_col == RED) {
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left) {
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED) { // uncle 为红色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else { // uncle不存在 or 存在且为黑
					if (cur == parent->_left) {
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						//cur->_col = RED;
					}
					else if (cur == parent->_right) {
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
						parent->_col = RED;
					}

					break;
				}
			}
			else { // if (parent == grandfather->_right)
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED) { // uncle 为红色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else { // uncle不存在 or 存在且为黑
					if (cur == parent->_right) {
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						//cur->_col = RED;
					}
					else { // cur == parent->_left
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}

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

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

相关文章

Python基础(四)

目录 一、程序的组织结构 1、前言 二、顺序结构 1、介绍 三、对象的布尔值 1、介绍 2、规定 四、分支结构 1、单分支if结构 1、语法语义 2、语法结构 3、案例 2、双分支if...else结构 1、语法语义 2、语法结构 3、案例 3、多分支if...elif...else结构 1、语…

Java语言---栈与队列

目录 一.栈 1.1栈的概念 1.2.栈的实现 1.2.1数组实现 栈 栈的创建 栈的基本方法实现 1.2.2链表实现 栈 栈的创建 栈的基本方法实现 二.队列 2.1队列的概念 2.2队列的实现 2.3代码实现 2.3.1队列代码的构建 2.3.2 队列 基础方法实现 总结 &#x1f63d;个人主页…

深入理解2D卷积和3D卷积

文章目录 卷积核的维度2D卷积单通道多通道代码example2d卷积操作后变化 3D卷积单通道多通道代码 在项目中用到了conv3但是对其背后的原理还有一些模糊的地方&#xff0c;conv2d与多通道的conv2d的区别在哪里&#xff1f;conv3d的思想理论是什么&#xff1f;对此进行探究和记录……

「AI之劫」:当机器超越人类底线,正在侵犯我们的创造力和道德

随着AI技术的不断发展&#xff0c;AI生成内容&#xff08;AIGC&#xff09;已经成为数字娱乐、商业营销和学术研究等领域的热门话题。随着人工智能技术的不断发展越来越多的领域开始应用AI技术&#xff0c;其中之一就是内容生成领域。 AIGC全称为AI-Generated Content, 指基于生…

2023年5月广州/深圳制造业产品经理很适合考的证书-NPDP

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

【利用AI让知识体系化】入门Egg框架(含实战)

思维导图 文章目录 思维导图第一章&#xff1a;概述1.1 Egg.js 简介1.2 Egg.js 的架构和优势1.3 Egg.js 的基本组件和插件 第二章&#xff1a;环境搭建2.1 Node.js 环境安装和配置2.2 Egg.js 应用创建和项目结构介绍2.3 Egg.js 应用部署和启动 第三章&#xff1a;基本开发3.1 路…

经纬恒润新产品系列 | 这款AR-HUD将颠覆你的认知

随着科技的发展与突破&#xff0c;智能化产品在汽车领域扮演了越来越重要的角色。本文即将介绍**经纬恒润新产品——AR-HUD&#xff08;增强现实抬头显示系统&#xff09;&#xff0c;**它可以将科幻电影中的驾驶场景变为现实——将信息投影在挡风玻璃上&#xff0c;基于此功能…

开发环境搭建和创建STM32工程

目录 一、开发环境搭建 1. STM32CubeMX 2.Keil安装 二、创建STM32工程 一、开发环境搭建 1. STM32CubeMX ST公司出品 工具链接 https://www.st.com/zh/development-tools/stm32cubemx.html STM32CubeMX是一种图形工具&#xff0c;通过分步过程可以非常轻松地配置STM32微控制器和…

coolshell 镜像备份站点

缅怀技术大佬做的一个镜像站点 - RIP 消息刚开始是在推特传开&#xff0c;后面得到了家人同事的证实。噩耗&#xff01; worldpeople2019 太意外了&#xff01;中年程序员&#xff0c;感觉年龄跟我差不多&#xff0c;怎么就这么突然去世了&#xff1f;&#xff01;诸位码农朋友…

Python爬虫进阶(1),Django+Selenium+Mysql+SimpleUI,从零开始搭建自己的爬虫后台管理系统

如果爬虫做完的话都会发现每个文件要么保存到csv或者是其他格式的文件中&#xff0c;这样做多少会有些麻烦&#xff0c;所以需要将这些内容保存起来方便自己管理和查看内容。 相对于flask而言Django有着相对成熟的一个后台管理系统配合上其他一些插件就可以做到即插即用的效果…

hive安装及配置

hive安装和部署 Hive地址 1&#xff0e;Hive官网地址 http://hive.apache.org/ 2&#xff0e;文档查看地址 https://cwiki.apache.org/confluence/display/Hive/GettingStarted 3&#xff0e;下载地址 http://archive.apache.org/dist/hive/ 4&#xff0e;github地址 http…

全新版本,手把手教你配置c\c++

上一篇图片多&#xff0c;语句乱&#xff0c;内容乱 这一篇采用全新的教程 这次在不删软件的前提下进行 老规矩先把之前看的教程残余删除 如果你有很多插件和设置这边建议先备份 打开c盘&#xff0c;在搜索栏里输入你的用户名 在箭头位置搜索你的用户名&#xff0c;就是你…

通过Python的wordcloud库将单词生成词云(心形形状)

文章目录 前言一、wordcloud库是什么&#xff1f;二、安装wordcloud库三、查看wordcloud库版本四、使用方法1.引入库2.定义图片路径3.定义需要分词的文本4.采用jieba搜索引擎模式分词5.加载心形图片6.创建词云对象7.生成词云8.保存词云图9.词云图效果 总结 前言 大家好&#xf…

第十五章 使用iSCSI服务部署网络存储

文章目录 第十五章 使用iSCSI服务部署网络存储一、iSCSI技术介绍1、硬盘接口类型 二、创建RAID磁盘阵列1、添加四块硬盘2、创建RAID磁盘阵列 三、配置iSCSI服务端1、iSCSI服务的和客户端的操作系统以及IP地址2、安装targetcli3、配置服务端共享资源4、创建iSCSI target名称及配…

B.Conveyor Belts

Codeforces Round 863 (Div. 3) 题目链接 题目大意&#xff1a; 有个矩阵传送带&#xff0c;从其中一个传送带跳到邻近的传送带需要消费一点能量。问从 x 1 , y 1 x_1,y_1 x1​,y1​到 x 2 , y 2 x_2,y_2 x2​,y2​最少要多少能量 个人题解&#xff1a; 我只需要算出该点在哪个…

强化学习笔记-05 蒙特卡罗方法Monte Carlo Method

本文是博主对《Reinforcement Learning- An introduction》的阅读笔记&#xff0c;不涉及内容的翻译&#xff0c;主要为个人的理解和思考。 上一节介绍了通过动态规划法来解决强化Markov decision process MDP环境下的学习问题&#xff0c; 动态规划法假设环境是完全可知&#…

APP测试常见功能测试点汇总,赶紧来记笔记

目录 1、安装和卸载 2、运行 3、注册和登录 4、日历控件 5、权限设置 6、软件更新  7、网络环境 8、兼容性测试&#xff1a;   9、异常测试   1、安装和卸载 安装和卸载是任何一款APP中都属于最基本功能。一旦出错&#xff0c;就属于优先级为紧要的BUG。因此APP…

三十六、链路追踪、配置中心

1、链路追踪 在一次调用链路中&#xff0c;可能设计到多个微服务&#xff0c;如果在线上&#xff0c;某个微服务出现故障&#xff0c;如何快速定位故障所在额微服务呢。 可以使用链路追踪技术 1.1链路追踪介绍 在大型系统的微服务化构建中&#xff0c;一个系统被拆分成了许多微…

Ambari-2.7.7源码编译

0 说明 本文基于Ambari-2.7.7版本进行源码编译。所需的编译资料统一提供如下&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1F2D7zBGfKihxTBArnOilTw 提取码&#xff1a;8m17 1 前提条件 1.1 下载ambari源码包 wget https://github.com/apache/ambari/releases/t…

【代码调试】《Multi-scale Positive Sample Refinement for Few-shot Object Detection》

论文地址&#xff1a;https://arxiv.org/abs/2007.09384#:~:textMulti-Scale%20Positive%20Sample%20Refinement%20for%20Few-Shot%20Object%20Detection.,previous%20attempts%20that%20exploit%20few-shot%20classification%20techniques%20 代码地址&#xff1a;https://git…