红黑树简析

news2024/11/28 14:46:09

一.   概念

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


 二.   性质

1. 每个结点不是红色就是黑色

2. 根节点是黑色的

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

由于从一个节点起的每条路径中的黑色节点数目相同,因此最短为全黑,同时由于红节点不连续,因此最长为一半红一半黑,因此不会超出两倍


三.   节点定义

三叉链,key-value模型、额外的标明颜色的变量(颜色可以使用枚举常量)

同时,为了在插入时遵循第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)
		, _col(RED)
		, _kv(kv)
	{}
};

四.   基本框架

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

五.   插入

首先,一开始与AVL树是一致的,都是通过比较key来寻找合适的位置进行插入

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

	while (cur)
	{
		if (cur->_kv.first == kv.first)
			return false;
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			parent = cur;
			cur = cur->_left;
		}
	}

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

之后,虽然我们插入红色节点会使得每条路径的黑节点数目相同,但还要注意在红节点连续时进行相应的调整

在调整时,依旧是从插入节点(cur)向上迭代来观察,而我们需要进行调整的情况为cur和parent的颜色都为红,主要分为这两种情况

同样,和AVL树一样,也有对称的情况,但操作方法也都是类似的,不多说了

而这两种情况,我们对子树4的情况进行细分,首先当节点1的右节点为红色时

 处理方法都是一样的,只需要将节点2、4的颜色改为黑色,同时,我们在改变结构时,为了减小对上面的影响,应该保证前后的路径中黑节点的数目相同,改色之前,黑节点数目为n+1,因此,我们还需要将节点1改为红色

而由于我们将节点1的颜色改为了红色,我们需要继续向上判断

while (parent&&parent->_col == RED)
{
	Node* par_parent = parent->_parent;
	Node* uncle = nullptr;
	if (parent == par_parent->_left)
		uncle = par_parent->_right;
	else
		uncle = par_parent->_left;

	if (uncle && uncle->_col == RED)
	{
		uncle->_col = BLACK;
		parent->_col = BLACK;
		par_parent->_col = RED;
		cur = par_parent;
		parent = cur->_parent;
	}
}

 

还有两种情况,是节点4不存在或节点4为黑色,这两种情况的处理情况相同,由于不需要对节点4进行处理,我们就简化一下

首先是情况1

 首先若是我们想要改变颜色,首先对于节点2,右侧路径黑节点为n个,因此左侧也应该为n个,所以节点3的颜色不能改变,因此我们只能改变节点2,这样对于节点1,两侧路径的黑节点数目就不可能相同了,因此,我们可以使用在AVL树中使用到的旋转,很容易看出,我们需要使用右旋(对称情况使用左旋)

 之后,便可以来改变颜色,首先,有两种方案,节点3改为黑色或节点1改为红色、节点2改为黑色,原本路径中黑节点数为n+1,这两种方案改变后都符合,但由于若是我们将节点2颜色改为黑色,与改变前类似,也就不需要继续向上判断了,因此采用第二种方法

else
{
	if (cur == parent->_left && parent == par_parent->_left)
	{
		RotateR(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	else if (cur == parent->_right && parent == par_parent->_right)
	{
		RotateL(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	break;
}

而左旋和右旋和AVL树是一样的 

void RotateL(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* right = parent->_right;
	Node* right_left = right->_left;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = right;
		else
			par_parent->_right = right;
	}
	else
	{
		_root = right;
	}
	right->_parent = par_parent;
	if (right_left)
		right_left->_parent = parent;
	parent->_right = right_left;
	right->_left = parent;
	parent->_parent = right;
}

void RotateR(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* left = parent->_left;
	Node* left_right = left->_right;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = left;
		else
			par_parent->_right = left;
	}
	else
	{
		_root = left;
	}
	left->_parent = par_parent;
	if (left_right)
		left_right->_parent = parent;
	parent->_left = left_right;
	left->_right = parent;
	parent->_parent = left;
}

 

之后便是情况2

同样只是改变颜色无法实现平衡 

我们就需要进行双旋

之后就需要改变颜色

还是将节点3变黑,节点1变红,不需要向上调整

else
{
	if (cur == parent->_left && parent == par_parent->_left)
	{
		RotateR(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	else if (cur == parent->_left && parent == par_parent->_right)
	{
		RotateR(parent);
		RotateL(par_parent);
		cur->_col = BLACK;
		par_parent->_col = RED;
	}
	else if (cur == parent->_right && parent == par_parent->_right)
	{
		RotateL(par_parent);
		parent->_col = BLACK;
		par_parent->_col = RED;
	}
	else
	{
		RotateL(parent);
		RotateR(par_parent);
		cur->_col = BLACK;
		par_parent->_col = RED;
	}
	break;
}

而在最后,头结点可能会被我们改变为红色,我们只需要将其手动变为黑就好了,不会对下面产生影响

总览

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

	while (cur)
	{
		if (cur->_kv.first == kv.first)
			return false;
		else if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			parent = cur;
			cur = cur->_left;
		}
	}

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

	while (parent&&parent->_col == RED)
	{
		Node* par_parent = parent->_parent;
		Node* uncle = nullptr;
		if (parent == par_parent->_left)
			uncle = par_parent->_right;
		else
			uncle = par_parent->_left;

		if (uncle && uncle->_col == RED)
		{
			uncle->_col = BLACK;
			parent->_col = BLACK;
			par_parent->_col = RED;
			cur = par_parent;
			parent = cur->_parent;
		}
		else
		{
			if (cur == parent->_left && parent == par_parent->_left)
			{
				RotateR(par_parent);
				parent->_col = BLACK;
				par_parent->_col = RED;
			}
			else if (cur == parent->_left && parent == par_parent->_right)
			{
				RotateR(parent);
				RotateL(par_parent);
				cur->_col = BLACK;
				par_parent->_col = RED;
			}
			else if (cur == parent->_right && parent == par_parent->_right)
			{
				RotateL(par_parent);
				parent->_col = BLACK;
				par_parent->_col = RED;
			}
			else
			{
				RotateL(parent);
				RotateR(par_parent);
				cur->_col = BLACK;
				par_parent->_col = RED;
			}
			break;
		}
	}
	_root->_col = BLACK;
	return true;
}

void RotateL(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* right = parent->_right;
	Node* right_left = right->_left;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = right;
		else
			par_parent->_right = right;
	}
	else
	{
		_root = right;
	}
	right->_parent = par_parent;
	if (right_left)
		right_left->_parent = parent;
	parent->_right = right_left;
	right->_left = parent;
	parent->_parent = right;
}

void RotateR(Node* parent)
{
	Node* par_parent = parent->_parent;
	Node* left = parent->_left;
	Node* left_right = left->_right;
	if (par_parent)
	{
		if (parent == par_parent->_left)
			par_parent->_left = left;
		else
			par_parent->_right = left;
	}
	else
	{
		_root = left;
	}
	left->_parent = par_parent;
	if (left_right)
		left_right->_parent = parent;
	parent->_left = left_right;
	left->_right = parent;
	parent->_parent = left;
}

六.   验证 

首先就是验证是否为搜索树,和AVL树的一致

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

void _InOrder(Node* root)
{
	if (root == NULL)
		return;

	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second <<':' << root->_col << "  ";
	_InOrder(root->_right);
}

之后,还需要验证三个问题:根节点是否为黑,路径中的黑节点数目是否相同、红节点是否不连续

首先第一点很好验证,若是验证第二点,我们可以先得到一条路径的黑节点,之后以它为基准,对每条路径进行判断,最后在遍历路径的时候顺便对红节点是否连续做一下判断

bool IsBalance()
{
	if (_root && _root->_col == RED)
	{
		cout << "根节点不是黑色" << endl;
		return false;
	}

	int banchmark = 0;
	Node* left = _root;
	while (left)
	{
		if (left->_col == BLACK)
			banchmark++;
		left = left->_left;
	}
	int blackNum = 0;
	return _IsBalance(_root, banchmark, blackNum);
}

bool _IsBalance(Node* root, int& banchmark, int blackNum)
{
	if (!root)
	{
		if (banchmark != blackNum)
		{
			cout << "存在路径黑色节点的数量不相等" << endl;
			return false;
		}
		return true;
	}
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "出现连续红色节点" << endl;
		return false;
	}

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

	return _IsBalance(root->_left, banchmark, blackNum)
		&& _IsBalance(root->_right, banchmark, blackNum);
}

end


 


 

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

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

相关文章

java 自定义json解析注解 复杂json解析

java 自定义json解析注解 复杂json解析 工具类 目录java 自定义json解析注解 复杂json解析 工具类1.背景2、需求-各式各样的json一、一星难度json【json对象中不分层】二、二星难度json【json对象中出现层级】三、三星难度json【json对象中存在数组】四、四星难度json【json对象…

【c++之于c的优化 - 下】

前言 一、inline 概念 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函数调用建立栈帧的开销&#xff0c;内联函数提升程序运行的效率。 如果在上述函数前增加inline关键字将其改成内联函数&#xff0c;在编译期间编译…

契约锁与200多家软件厂商实现集成应用,实现更多业务电子签需求

在中大型组织的数字化建设中&#xff0c;电子签章需求紧紧围绕业务展开&#xff0c;实现电子签章与管理软件集成应用已经成为普遍需求。 契约锁数字可信产品拥有200多家管理软件厂商集成对接经验&#xff0c;覆盖ERP、OA、专项业务系统、移动APP应用、低代码平台、BPM流程、小程…

Vue - 使用Lodash实现防抖和节流

GitHub Demo 地址 在线预览 Lodash 官网 参考&#xff1a; Vue 防抖节流 详细介绍 vue 优雅的实现防抖和节流 防抖、节流的介绍 js防抖和节流的实现原理及应用场景 js防抖节流应用场景及写法 JS中的防抖 什么的防抖和节流&#xff1f; 函数节流&#xff08;throttle&#x…

论文翻译:2022_Phase-Aware Deep Speech Enhancement: It’s All About The Frame Length

摘要 虽然相位感知语音处理近年来受到越来越多的关注&#xff0c;但大多数帧长约为32 ms的窄带STFT方法显示出相位对整体性能的影响相当有限。与此同时&#xff0c;现代基于深度神经网络(DNN)的方法&#xff0c;如Conv-TasNet&#xff0c;隐式修改幅度和相位&#xff0c;在非常…

CUDA虚拟内存管理

CUDA中的虚拟内存管理 文章目录CUDA中的虚拟内存管理1. Introduction2. Query for support3. Allocating Physical Memory3.1. Shareable Memory Allocations3.2. Memory Type3.2.1. Compressible Memory4. Reserving a Virtual Address Range5. Virtual Aliasing Support6. Ma…

自动化测试实战篇(6)jmeter实现脚本录制,抓取接口信息

Jmeter中脚本录制&#xff0c;是一个非常方便找到接口内容的一种工具&#xff0c;不用想fiddler抓包定位接口信息速度不够快 设置代理服务器 这里以谷歌浏览器为例子 打开您的计算机的代理设置 把代理服务器打开这里就以127.0.0.1和8080端口为例子&#xff0c;这个需要记住…

ChatGPT背后的经济账

ChatGPT能否取代Google、百度这样的传统搜索引擎&#xff1f;为什么中国不能很快做出ChatGPT&#xff1f;当前&#xff0c;对这些问题的探讨大多囿于大型语言模型&#xff08;LLM&#xff09;的技术可行性&#xff0c;忽略或者非常粗糙地估计了实现这些目标背后的经济成本&…

((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第4天(搜索与图论-下 专题)】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有63天

&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6; 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&a…

差分隐私学习笔记

2021网络空间安全西湖学术论坛线上报告中介绍了差分隐私过去发展&#xff0c;目前现状以及未来研究方向。博主对这个报告进行了介绍与总结。总结中提到学习差分隐私最重要的环节是&#xff1a; 了解差分隐私的基本机制&#xff1a;拉普拉斯机制、指数机制和高斯机制差分隐私的组…

【数据结构与算法】前缀树的实现

&#x1f320;作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《数据结构与算法要啸着学》 &#x1f387;座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;…

54 循环神经网络 RNN【动手学深度学习v2】

54 循环神经网络 RNN【动手学深度学习v2】 深度学习学习笔记 学习视频&#xff1a;https://www.bilibili.com/video/BV1D64y1z7CA/?spm_id_from333.880.my_history.page.click&vd_source75dce036dc8244310435eaf03de4e330 对序列化数据集的训练网络&#xff0c;通常称为RN…

第三章 Opencv图像像素操作

目录1.像素1-1.确定像素位置1-2.获取指定像素的像素值1-3.修改像素的BGR值2.用numpy模块操作像素2-1.创建图像1.创建黑白图像2.创建彩色图像3.创建随机图像2-2.拼接图像1.水平拼接hstack()方法2.垂直拼接vstack()方法1.像素 1.像素是构成数字图像的最小单位。每一幅图像都是由M…

【第29天】SQL进阶-查询优化- performance_schema系列实战四:查看最近的SQL执行信息(SQL 小虚竹)

回城传送–》《32天SQL筑基》 文章目录零、前言一、 查看最近的top sql1.1 数据准备&#xff08;如果已有数据可跳过此操作&#xff09;1.2 查询events_statements_summary_by_digest表二、查看最近执行失败的SQL2.1 开启第一个会话&#xff0c;执行错误sql2.2 开启第二个会话&…

pytest当中pytest.ini使用

目录 一、作用 二、存放位置 三、功能&#xff08;只列了简单的&#xff09; 1、 addopts 2、更改测试用例收集规则 四、运行就减少了命令了 前言&#xff1a;pytest配置文件可以改变pytest的运行方式&#xff0c;它是一个固定的文件pytest.ini文件。 一、作用 pytest.in…

Ceph分部署存储知识总结

Ceph 一.deploy-ceph部署 投入使用ceph前&#xff0c;要知道一个很现实得问题&#xff0c;ceph对低版本内核得客户端使用非常不友好&#xff0c;低内核是指小于等于3.10.0-862&#xff0c;默认的centos7.5及以下的系统都是小于此类内核&#xff0c;无法正常使用ceph的文件存储…

内网渗透(十一)之内网信息收集-内网IP扫描和发现

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

用YOLOv8推荐的Roboflow工具来训练自己的数据集

YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本&#xff0c;相较于之前的版本&#xff0c;YOLOv8可以更快速有效地识别和定位图像中的物体&#xff0c;以及更准确地分类它们。 作为一种深度学习技术&#xff0c;YOLOv8需要大量的训练数据来实现最佳性能。…

如何旋转YUV图片数据且使用Qt显示

前言 提一下这篇文章的需求&#xff1a;将USB相机获取到的YUV数据进行旋转&#xff0c;然后转为QImage进行显示。原本程序中是有旋转的代码&#xff0c;但不知道为什么&#xff0c;旋转出来的图片会花屏。关于花屏的问题&#xff0c;后面会稍微阐述一下。所以&#xff0c;经过…

[多线程进阶] 常见锁策略

专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录: 1. 常见的锁策略 1.1 乐观锁 vs 悲观锁 1.2 读写…