【C++】红黑树的底层原理以及实现

#1024程序员节 | 征文#

在这里插入图片描述

个人主页:夜晚中的人海

文章目录

  • ⭐前言
  • 🚆一、红黑树的概念
  • 🏠二、红黑树的规则
  • 🎄三、红黑树的效率
  • 🎡四、红黑树的实现
    • 1. 基本框架
    • 2. 插入操作
      • • 变色
      • • 单旋 + 变色
      • • 双旋 + 变色
    • 3. 查找操作
    • 4. 验证操作

⭐前言

我们在前面的文章中提到了当一棵树退化成单支时导致性能和效率降低时,我们可以用AVL树来解决这一问题。但由于AVL树是一棵高度平衡的树,且每次修改树的结构时都要保证左右子树的高度差不超过1。因此如果需要一棵结构动态变化的二叉搜索树时,红黑树的作用就出来了。下面就来详细讲解一下有关红黑树的相关知识以及操作。

🚆一、红黑树的概念

红黑树是一棵二叉搜索树,它在每个节点增加了一个存储位来表示节点的颜色,可以是红色或者黑色。红黑树确保没有一条路径会比其他路径长出2倍(通过对任何一条从根到叶子的路径上各个结点的颜色进行约束),因而是接近平衡的。

红黑树的结构示意图:

🏠二、红黑树的规则

每一棵红黑树,都需要满足以下规则:

1.每个节点不是红色就是黑色。
2.根节点是黑色的。
3.如果一个结点是红色的,则它的两个孩子结点必须是黑色的,即任意一条路径不会有连续的红色结点。
4.对于任意一个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色结点。

在这里插入图片描述
通过上述规则,我们思考一下红黑树是如何确保最长路径不超过最短路径的两倍的呢?

我们通过规则4可知,从根节点开始到空节点的每条路径上都有相同数量的黑色节点,因此在极端场景下,最短路径就是全是黑色节点的路径。我们假设该最短路径的长度为bh。

在这里插入图片描述

由规则2和3可知,任意一条路径上不会有连续的红色节点,因此在极端场景下,最长路径就是一黑一红间隔组成的。我们假设最长路径的长度为2 * bh。

在这里插入图片描述
综合上述规则,全黑的最短路径和一黑一红的最长路径并不是每一棵红黑树都存在的。因此我们假设一条从根到空节点的路径长度为x,则这个x的范围是在bh <= x <= 2 * bh之间的。

🎄三、红黑树的效率

1.红黑树在插入、查找和删除操作上的时间复杂度都为O(logN)。
2.与AVL树相比,其插入和删除操作上的平衡代价较低,整体性能略优于AVL树,且旋转情况少于AVL树,这使得红黑树在实际应用中更加高效和稳定

🎡四、红黑树的实现

1. 基本框架

// 枚举值表⽰颜⾊
enum Colour
{
	RED,
	BLACK
};

// 默认按KV结构进行实现
template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;
	RBTreeNode(const pair<K, V>& kv)
	:_kv(kv)
	, _left(nullptr)
	, _right(nullptr)
	, _parent(nullptr)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
private:
	Node* _root = nullptr;
};

2. 插入操作

红黑树的插入与AVL树的插入不同,这也是区分它们俩之间的一大特点。红黑树的插入减少了旋转的次数,采用变色+旋转的调整方式,提高了插入的效率。

红黑树插入一个值的过程:
1.按照二叉搜索树的规则进行插入,插入后看是否符合红黑树的4个规则。
2.如果是空树插入,则新增节点为黑色节点。如果是非空树进行插入,则新增节点必须是红色节点。因为如果新增的是黑色节点,则违反了规则4,而规则4是很难进行维护的。
3.非空树插入后,新增节点为红色。如果父亲节点是黑色的,则没有违反任何规则,插入结束。而如果父亲节点是红色的情况下,则违反了规则3。我们通过下图来分析一下:
在这里插入图片描述
我们可以确定,c是红色的,p也为红色的,则g必为黑色的,这三个颜色都固定了,因此关键的变化就看u的情况了。需要根据u分为以下几种情况进行分别处理。

• 变色

当c为红,p为红,g为黑,u存在且为红时,则只需将p和u变黑,g变红,再把g当成新的c,不断往上进行更新即可。
在这里插入图片描述

为什么g还需要向上进行更新呢?
• 当这棵树是某棵子树时,g一定还有双亲节点,如果g的双亲节点为红色,则还需要进行向上调整。
• 当这棵树是整棵树时,g是根节点,则需要将g的颜色修改成黑色。

在这里我们只展示了一种情况,而在现实情况中我们需要处理很多种情况,因此我们只需看抽象图即可,其原理和处理方法都相似。
在这里插入图片描述

• 单旋 + 变色

当c为红,p为红,g为黑,u不存在或存在且为黑时,如果u不存在时,则c一定是新增节点;而如果u存在且为黑时,则c一定不是新增的,c之前是肯定是黑色的,在c的子树中进行插入,变色将c从黑色变为红色,不断更新上来的。
因此在这里我们发现,p必须变黑才能解决连续红色节点的问题,而这种情况下单纯的变色并不能解决,需要用到旋转+变色。
在这里插入图片描述

如果是上述图中的情况,那么我们以g为旋转点进行右单旋,再把p变黑,g变红即可。让p成为整棵树的新根,这样就可以解决连续红色节点的问题。且p并不需要往上更新,因为p的父亲节点是红色还是空都不违反规则。

在这里插入图片描述
如果是上述图中的情况,则与前面所讲的方法相反即可。以g为旋转点进行左单旋,再把p变黑,g变红即可。让p成为整棵树的新根。也同样不需要往上进行更新。

• 双旋 + 变色

当c为红,p为红,g为黑,u不存在或存在且为黑时,如果u不存在时,则c一定是新增节点;而如果u存在且为黑时,则c一定不是新增的,c之前是肯定是黑色的,在c的子树中进行插入,变色将c从黑色变为红色,不断更新上来的。
在这里插入图片描述
如果是上图这一情况,p是g的左,c是p的右时,那么则先对p为旋转的进行左单旋,再以g为节点进行右单旋,再把c变黑,g变红即可,让c成为整棵树的新根,这样就可以解决连续红色节点的问题了。

在这里插入图片描述
如果是上述这一情况,p是g的右,c是p的左时,那么先对p进行右单旋,再以g为旋转点进行左单旋,再把c变黑,g变红,让c成为整棵树的新根。

完整插入代码:

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->_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;

	//如果父亲节点是红色,则需要进行处理
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			//    g
			//  p   u
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				//继续往上进行处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else
		{
			//    g
			//  u   p
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				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;
}

3. 查找操作

按二叉搜索树的逻辑进行查找,其效率为 O(logN)

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

4. 验证操作

我们验证一棵树是否为红黑树时,还是先检查是否满足红黑树的那四条规则,如果符合则该树一定为红黑树。

bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历走到空时,意味着一条路径走完了
		//cout << blackNum << endl;
		if (refNum != blackNum)
		{
			cout << "存在黑色结点的数量不相等的路径" << endl;
			return false;
		}
		return true;
	}
	// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << root->_kv.first << "存在连续的红色结点" << endl;
		return false;
	}

	if (root->_col == BLACK)
	{
		blackNum++;
	}

	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

玄机平台-应急响应-webshell查杀

首先xshell连接 然后进入/var/www/html目录中&#xff0c;将文件变成压缩包 cd /var/www/html tar -czvf web.tar.gz ./* 开启一个http.server服务&#xff0c;将文件下载到本地 python3 -m http.server 放在D盾中检测 基本可以确认木马文件就是这四个 /var/www/html/shell.p…

高效实现聚水潭数据集成MySQL的技术案例

聚水潭奇门数据集成到MySQL的技术案例分享 在现代企业的数据管理中&#xff0c;如何高效、准确地实现不同系统之间的数据对接是一个关键问题。本文将聚焦于一个实际的系统对接集成案例&#xff1a;将聚水潭奇门平台的售后单数据集成到MySQL数据库中&#xff0c;具体方案名称为…

JVM—类的生命周期

目录 类的生命周期 加载阶段 连接阶段 验证阶段 准备阶段 解析阶段 初始化阶段 面试题1 面试题2 类的生命周期 类的生命周期描述了一个类加载、使用、卸载的整个过程&#xff0c;整体可以分为以下五个阶段。 1. 加载 2. 连接&#xff0c;其中又分为验证、准备、解析三…

Python学习的自我理解和想法(21)

学的是b站的课程&#xff08;千锋教育&#xff09;&#xff0c;跟老师写程序&#xff0c;不是自创的代码&#xff01; 今天是学Python的第21天&#xff0c;学的内容是文件的操作。开学了&#xff0c;时间不多&#xff0c;写得不多&#xff0c;见谅。 目录 1.文件 (1).参数…

Tcp_Sever(线程池版本的 TCP 服务器)

Tcp_Sever&#xff08;线程池版本的 TCP 服务器&#xff09; 前言1. 功能介绍及展示1.1 服务端连接1.2 客户端连接&#xff08;可多个用户同时在线连接服务端&#xff09;1.3 功能服务1.3.1 defaultService&#xff08;默认服务&#xff09;1.3.2 transform&#xff08;大小写转…

Rust与Javascript的使用对比

一、常量 RustJavascriptletconst 二、变量 RustJavascriptlet mutlet / var 三、常用打印 RustJavascriptprintln!(“换行”);console.log(‘hello’);print!(“不换行”);console.info(‘信息’);-console.error(‘错误’);-console.warn(‘警告’); 四、定义字符串 R…

开放式耳机哪个品牌音质好?高评分爆款开放式耳机推荐!

一直活跃在蓝牙耳机圈子里的我&#xff0c;对各种类型的耳机多少都有自己的看法&#xff0c;完全可以说是个耳机狂热者。近几年&#xff0c;开放式蓝牙耳机愈发火爆。开放式耳机不是任何品牌都能轻松做好的产品&#xff0c;特别是音质&#xff0c;它涵盖了核心单元技术等诸多方…

负载均衡服务器攻击怎么解决最有效?

负载均衡服务器攻击怎么解决最有效&#xff1f;常见的有效解决方法包括&#xff1a;使用SYNCookie机制、限制ICMP包速率、基于源IP的连接速率限制、检测并丢弃异常IP包、配置访问控制列表&#xff08;ACL&#xff09;、设置虚拟服务器/服务器连接数量限制、设置HTTP并发请求限制…

ABAQUS应用11——支座弹簧

文章目录 0、背景1、ABAQUS中几类弹簧的简介2、SPRING1的性质初探 0、背景 1、ABAQUS中几类弹簧的简介 先说参考来源&#xff0c;ABAQUS2016的帮助文档里第4卷&#xff0c;32.1.1节&#xff0c;有三种弹簧&#xff08;SPRING1 、SPRING2 以及SPRINGA&#xff09;。 三种弹簧里…

C for Graphic:视差渲染(一)

记录一下最近优化场景的做法&#xff1a;视差渲染 原理&#xff1a;通过视口坐标的变化&#xff0c;观察不同采样画面的功能&#xff0c;画面的载体为低模平面 我早期工作&#xff0c;在小作坊全栈的时候&#xff0c;做过一段时间web开发&#xff0c;做了一个古董藏…

【传知代码】机器学习在情绪预测中的应用(论文复现)

在科技迅猛发展的今天&#xff0c;我们不仅在追求更强大的计算能力和更高的精度&#xff0c;还希望我们的机器能够理解和回应我们复杂的情感世界。设想一下&#xff0c;当你面对挫折时&#xff0c;设备不仅能识别你的情绪&#xff0c;还能以一种富有同情心和洞察力的方式作出反…

开放式耳机哪个牌子好?开放式蓝牙耳机排行榜分享

​耳机已经成为我们日常生活中的必需品&#xff0c;但长时间佩戴传统入耳式耳机可能会导致耳朵不适&#xff0c;甚至影响健康。为了应对这一挑战&#xff0c;开放式耳机应运而生。这类耳机不侵入耳道&#xff0c;有效减轻了耳朵的压力&#xff0c;同时减少了感染风险&#xff0…

fmql之Linux中I2C总线框架

正点原子第44章 I2C zynq I2C pcf8563芯片 我们用的是ds3231. Linux I2C总线框架 I2C总线驱动 这部分内容是半导体厂商编写的。 I2C总线设备 zynq I2C适配器驱动 I2C设备驱动编写 使用设备树 代码编写 设备树修改 设备驱动编写 因为用的是ds3231&#xff0c;所以先找…

使用 PyTorch 构建 LSTM 股票价格预测模型

目录 引言准备工作1. 训练模型&#xff08;train.py&#xff09;2. 模型定义&#xff08;model.py&#xff09;3. 测试模型和可视化&#xff08;test.py&#xff09;使用说明模型调整结论 引言 在金融领域&#xff0c;股票价格预测是一个重要且具有挑战性的任务。随着深度学习…

1024软件推荐-rubick

开源的插件化桌面端效率工具箱。插件是基于 npm 进行安装和卸载&#xff0c;非常轻便。插件数据支持 webdav 多端同步&#xff0c;非常安全。支持内网部署&#xff0c;可二次定制化开发&#xff0c;非常灵活。 前言 rubick 之前的插件管理&#xff0c;依托于云服务器存储&…

滴水逆向三期笔记与作业——02C语言——13 指针(3)(4)

滴水逆向三期笔记与作业——02C语言——13 指针3、4 一、模拟实现CE的数据搜索功能 OneNote迁移 一、模拟实现CE的数据搜索功能 //其中有0xAA&#xff0c;超过有符号char范围&#xff0c;在vscode中会报错&#xff0c;所以使用unsigned char unsigned char data[100] {0x00,0…

一起搭WPF架构之完结总结篇

一起搭WPF架构之完结总结篇 前言设计总结设计介绍页面一页面二页面三 结束 前言 整体基于WPF架构&#xff0c;根据自己的需求简单设计与实现了衣橱的数据统计、增加与读取数据、并展示数据的小软件。我知道自己在设计方面还有很多不足&#xff0c;暂时先做到这里了&#xff0c…

gbase8s权限管理

一 权限分类 分片级权限&#xff08;分片表&#xff09; 表引用 类型级权限 例程级权限 语言级权限 序列级权限 等... 其中常用的为 数据库级权限&#xff0c;表级权限&#xff0c;序列级权限以及例程级权限 二 权限控制 当创建一个用户时&#xff0c;该用户没有任何权…

为了数清还有几天到周末,我用python绘制了日历

日历的秘密 昨天&#xff0c;在看小侄子写作业的时候&#xff0c;发现了一个秘密&#xff1a;他在“演算纸”&#xff08;计算数学题用的草纸&#xff09;上画了非常多的日历。对此我感到了非常的困惑&#xff0c;“这是做什么的&#xff1f;” 后来&#xff0c;经过了我不懈…

机器学习面试笔试知识点-线性回归、逻辑回归(Logistics Regression)和支持向量机(SVM)

机器学习面试笔试知识点-线性回归、逻辑回归Logistics Regression和支持向量机SVM 一、线性回归1.线性回归的假设函数2.线性回归的损失函数(Loss Function)两者区别3.简述岭回归与Lasso回归以及使用场景4.什么场景下用L1、L2正则化5.什么是ElasticNet回归6.ElasticNet回归的使…