RBTree(红黑树)模拟实现(插入)

news2024/11/17 21:50:13

目录

红黑树的性质

红黑树的模拟插入

叔叔存在且为红色

叔叔不存在

旋转情况​​​​​​​

叔叔存在且为黑色

总结

插入实现

节点

插入逻辑

左单旋

右单旋


红黑树是一颗平衡搜索二叉树,但是红黑树并不像 AVL 树一样是高度平衡二叉树,任意一颗红黑树,它的子树不会超出它任意一个子树高度的二倍。

红黑树的性质

  • 每个节点不是红色就是黑色
  • 根节点是黑色的
  • 每个叶子节点(nil 节点/空节点)都是黑色的
  • 如果一个叶子节点是红色的,那么它的孩子节点都必须是黑色的
  • 任意一条路径上包含的黑色节点的数量都是相等的

其中,红黑树的平衡,就是由上面五条决定的。但是这里看到上面的五条并没有提到高度等字眼,红黑树也并不靠高度来维持平衡。

所以通过上面的条件,红黑树的高度虽然比 AVL 树的高度要高,但是红黑树的旋转次数是要比 AVL 树的少的,对于计算机而言,虽然高度可能比 AVL 树高,但是就搜索树的搜索时间复杂度为 O(log n) 来说,即使是红黑树的高度要相差二倍,那么时间复杂度最差也就是 O(log 2n) ,而对于计算机来说,这点时间并不算什么。

红黑树的模拟插入

红黑树的插入也是有几种情况,这几种情况分别需要不同的对待,所以下面我们看一下。

但是在插入之前,我们先想个问题,我们在插入的时候是插入红色节点呢?还是黑色节点?

  • 如果我们插入黑色节点,那么单条路径上的黑色节点的个数就变化了,所以我们还需要去修改其他路径的黑色节点,所以插入黑色节点的话,需要修改的节点数比较多
  • 插入红色节点,插入红色节点的话,我们的红黑树可能就不满足了,因为如果插入节点的父亲节点是红色的,那么就不满足红色节点的孩子节点必须是黑色的,所以这时候我们就需要对它的父亲节点进行修改颜色,或者是它的叔叔,并不会像插入黑色节点那样,需要对其他的路径都做修改
  • 所以,如果我们插入黑色节点的话,需要调整的工作量就比较大,但是如果我们插入红色节点的话,是有可能是不需要调整的,因为可能插入的节点的父亲节点就是黑色的,就算插入的父亲节点是红色的,那么也只需要修改它的父亲节点,或者是叔叔节点

叔叔存在且为红色

这里有一颗红黑树:

 假设我们现在要插入的节点是 21:

这时候,我们看到 cur 位置已经插入了节点 21,但是插入之后,该红黑树违反了,“红色节点的孩子节点必须是黑色的” 这一条规定,所以为例维持红黑树,我们需要对其进行变色,或者是旋转等操作来使其依旧是红黑树。

既然上面违反了 “红色几点的孩子节点必须是黑色的”,那么我们在看一下,cur 有没有叔叔节点,如果有的话,那么我们在看一下叔叔节点的颜色是不是红色的,如果都满足的话,那么下面我们就把父亲节点和叔叔节点都变为黑色的,然后把祖父节点变为红色,这样的话,这两条路径的黑色节点数量就不变了:

但是这里变色结束后,我们看到祖父节点是红色的,祖父节点的父亲节点也是红色的,所以我们可以继续刚才的步骤,知道祖父节点的颜色变为黑色,或者是祖父节点没有父亲节点,或者是其他情况的时候就结束,所以这时候,我们将祖父节点赋值给 cur 节点,然后继续啊上面的循环:

这时候,我们的祖父节点没有父亲节点,也就表示祖父节点就是根了,那么也就可以跳出循环,不过跳出循环后,我们的根节点不满足“根节点是黑色的”,这一条规定,所以出了循环之后,我们可以把根节点置为黑色:

在这样变色之后,该红黑树还是红黑树。

上面就是叔叔存在且为红色的情况。

叔叔不存在

如果是这种情况的话,那么就是叔叔不存在的情况,我们已经插入了 cur 节点,而这时候就是叔叔不存在的情况,那么这时候就不能靠改变父亲的颜色来维持红黑树了,因为这里只把父亲的颜色改变后和把祖父的颜色改变后,那么最右边的路径上黑色节点的数量就变少了,与其他路径的黑色节点的数量不同,所以不能光改变父亲和祖父的颜色,这时候就需要旋转加变色,那么怎么样旋转?这里我们可以对 grandparent 节点进行右单旋,然后将 parent 节点变为黑色,祖父节点变为红色:

 下一步就是将祖父节点变为红色,父亲节点变为黑色:

经过这样的旋转和变色之后,该树还是红黑树,不过刚才使用的是右单旋,那么怎么样判断是左单旋,还是右单旋,亦或者是右左双旋,或者是左右双旋?

旋转情况

在红黑树的旋转的判断条件并不是高度,而是看 cur parent 以及 grandparent 三个节点的位置。

1. 如果 parent 是 grandparent 的 left ,并且 cur 也是 parent 的 left ,那么就使用的是右单旋

2. 如果 parent 是 grandparent 的 right,并且 cur 也是 parent 的 right,那么就使用的是左单旋

3. 如果 parent 是 grandparent 的 right,并且 cur 也是 parent 的 left ,那么就使用的是右左双旋

4. 如果 parent 是 grandparent 的 left ,并且 cur 也是 parent 的 right,那么就使用的是左右双旋

所以下面我们看一下叔叔不存在,双旋的情况:

这时候,我们插入的节点是 24 这个节点,然后我们发现叔叔不存在,所以我们需要使用旋转加变色的方案,我们发现 parent 是 grandparent 的左,而 cur 是 parent 的右,所以我们需要使用左右双旋:

先对 parent 进行左单旋

 在对 grandparent 进行右单旋

旋转结束后,我们发现颜色不正确,所以我们在这种情况下,需要将grandparent 变为 红色,然后将 cur 变为 黑色。

叔叔存在且为黑色

上面就是叔叔存在且为黑色,我们到 cur 位置插入,但是这里我们先进行叔叔存在且为红色,所以我们向上跟新:

到这时候,就变成叔叔存在且为黑色了,所以这时候我们也不能单靠变色来解决问题,我们需要旋转加变色。

这时候的旋转也是按照上面的规则来的,我们看到parent 是 grandparent 的 右边,cur 是 parent 的右边所以这时候我们使用的是左单旋:

旋转结束后就是这个样子,但是颜色并不符合红黑树,所以我们还需要变色来处理,这种情况下,我们只需要把 grandparent 变为红色,父亲变为黑色:

其实叔叔不存在和叔叔存在且为黑色的情况是相同的,如果是单旋的话,那么就是旋转结束后,将付清的颜色变为黑色,然后将祖父的颜色变为红色,如果是双旋的话,那么就是将 cur 的颜色变为黑色,祖父的颜色变为红色。

下面看一下双旋的情况

这时候 cur 还是插入的节点,这时候我们跟新一次,然后就可以达到叔叔存在且为黑色,并且还是双旋的情况:

 此时就是叔叔存在且为黑色,并且我们发现 parent 是grandparent 的右边,cur 是 parent 的左边,所以这里我们使用的是右左双旋:

对 parent 进行右单旋

在对 grandparent 使用左单旋转

 这时候,就是将 grandparent 变为红色,然后将 cur 变为 黑色,所以我们在模拟插入后,发现叔叔不存在和存在且为黑的情况是一样的,所以我们后面就可以将叔叔存在且为红色分为一类,和叔叔不存在,或者存在且为黑色,分为一类,将这两类分开处理。

总结

旋转规则上面以及说过了,下面说一下变色:

1. 单旋情况下,将 grandparent 变为红色, parent 变为黑色

2. 双旋情况下,将 grandparent 变为红色, cur 变为黑色

插入实现

节点

  • 红黑树同样是kv结构,所以需要一个存储kv的变量
  • 既然是红黑树,那么除了三叉链,当然还需要一个颜色来控制
enum Colour
{
	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;

	Colour _col;

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

插入逻辑

	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			// 根节点为空,插入到根节点
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* cur = _root, *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->_parent = parent;
		if (parent->_kv.first < kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;

		// 维护红黑树
		//当父亲不为空,并且父亲的颜色是红色就继续调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				// parent 是 grandfather 的左边
				//       g
				//    p
				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)
					{
						//         g
						//     p
						//  c
						//右单旋
						RotateR(grandfather);
						//将父亲的节点变为黑色,祖父的节点变为红色
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
					else
					{
						//     g
						//  p
						//     c
						//左右双旋
						RotateL(parent);
						RotateR(grandfather);
						//将cur 的颜色变为黑色,祖父的节点变为红色
						cur->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
			}
			else
			{
				// parent 是 grandfather 的右边
				//     g
				//        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)
					{
						//    g
						//       p
						//          c
						//左单旋
						RotateL(grandfather);
						//将父亲的节点变为黑色,祖父的节点变为红色
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
					else
					{
						//       g
						//           p
						//       c
						//右左双旋
						RotateR(parent);
						RotateL(grandfather);
						//将cur 的颜色变为黑色,祖父的节点变为红色
						cur->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

而旋转,我们以及在 AVL 就以及说过了,树的旋转是搜索树的旋转规则,并不是AVL 树或者红黑树特有的,所以旋转是通用的,只是红黑树的旋转不需要维持平衡因子,只需要旋转即可。

左单旋

	// 左单旋
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curLeft = cur->_left;

		parent->_right = curLeft;
		if (curLeft)
		{
			curLeft->_parent = parent;
		}
		cur->_left = parent;
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (pparent == nullptr)
		{
			// parent 就是根节点
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
	}

右单旋

	//右单旋
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curRight = cur->_right;

		parent->_left = curRight;
		if (curRight)
		{
			curRight->_parent = parent;
		}
		cur->_right = parent;
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (pparent == nullptr)
		{
			// 说明 parent 是根节点
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
	}

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

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

相关文章

正在吞食世界的Python!

谁能想到&#xff0c;30年后&#xff0c;一条蟒蛇因为人工智能而席卷了全世界&#xff01;这一切&#xff0c;都源于1989年的那个圣诞节。 一个名叫Guido van Rossum程序员在荷兰的阿姆斯特丹呆着&#xff0c;无所事事的圣诞假期有点无聊。为了打发时间&#xff0c;他开发了一…

前端中的事件委托

前端小知识 事 件 委 托 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/132819265 【介绍】&#xff1…

MATLAB语言 实验一 MATLAB工作环境熟悉及简单命令的执行

一、 实验名称 MATLAB工作环境熟悉及简单命令的执行 二、 实验目的 熟悉MATLAB的工作环境&#xff0c;学会使用MATLAB进行一些简单的运算。 三、实验内容 MATLAB的启动和退出&#xff0c;熟悉MATLAB的桌面&#xff08;Desktop&#xff09;&#xff0c;包括菜单&#xff08…

抖音视频批量智能剪辑/智能一键成片功能如何技术开发源头?

抖音seo&#xff0c;视频剪辑&#xff0c;批量发布&#xff0c;账号矩阵管理&#xff0c;无人直播自动询盘锁定客户&#xff0c;想实现以上功能都要有正规的接口权限&#xff0c;这个权限接口已经在前面文章发过。 一、剪辑技术开发 智能剪辑&#xff1a;咱们研发公司自主研发…

初识Vue3

目录 创建实例 Vue3生命周期 响应式基础 为什么要使用 ref&#xff1f; 声明响应式状态 ref()和reactive() toRef()和toRefs() 创建实例 通过对Vue2的学习&#xff0c;我们可以这样在Vue2中创建一个实例&#xff1a; var vm new Vue({// 选项 }) 或者通过Vue全局api…

webrtc-m79-测试peerconnectionserver的webclient-p2p-demo

1 背景 webrtc的代码中有peerconnectionclient和peerconnectionserver的例子&#xff0c;但是没有对应的web端的例子&#xff0c;这里简单的写了一个测试例子&#xff0c;具体如下&#xff1a; 2 具体操作 2.1 操作流程 2.2 测试效果 使用webclient与peerconnectionclient的…

Windows安装MySQL8.0完整教程

很多朋友在安装MySQL的时候&#xff0c;总会遇到各种各样的问题。本文来教你怎样正确安装MySQL。 一、 下载MySQL 如果已经下载好了可以忽略&#xff0c;我下面提供两个版本的下载链接 阿里云盘 夸克云盘 链接&#xff1a;https://pan.quark.cn/s/1894623c2e6a 提取码&…

Go入门教程

什么是Go语言&#xff1f; Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近&#xff0c;但功能上有&#xff1a;内存安全&#xff0c;GC&#xff08;垃圾回…

BGP感想

BGP 边界网关协议 属于外部或域间路由协议&#xff0c;距离矢量路由协议。 AS(自治系统)&#xff0c;在一个自治系统内运行osfp,is-is,rip,vlan等,实现AS内网络互通。 BGP做什么&#xff0c;为处于不同自治系统&#xff08;AS&#xff09;中的路由器之间进行“路由信息通信…

视频推流测试——使用ffmpeg进行推流生成rtsp视频流

在我们完成开发工作之后,需要通过推流的形式来验证能否正确接收视频流,并送入视频检测程序。笔者在这里使用的是业内最为常用的ffmpeg。具体方法如下。 1、安装ffmpeg 访问ffmpeg的官网,地址为https://ffmpeg.org/download.html,按照如下途中来选择下载。 下载完成后,会…

LeetCode(力扣)53. 最大子数组和Python

LeetCode53. 最大子数组和 题目链接代码 题目链接 https://leetcode.cn/problems/maximum-subarray/ 代码 class Solution:def maxSubArray(self, nums: List[int]) -> int:result float(-inf)count 0for i in range(len(nums)):count nums[i]if count > result:res…

华为云云耀云服务器L实例评测|华为云云耀云服务器docker部署srs并调优,可使用webrtc与rtmp

华为云云耀云服务器L实例评测&#xff5c;华为云云耀云服务器docker部署srs并调优&#xff0c;可使用webrtc与rtmp 什么是华为云云耀云L实例 云耀云服务器L实例&#xff0c;面向初创企业和开发者打造的全新轻量应用云服务器。提供丰富严选的应用镜像&#xff0c;实现应用一键…

BUUCTF Reverse/[2019红帽杯]childRE

查看信息 分析代码 int __cdecl main(int argc, const char **argv, const char **envp) {__int64 v3; // rax_QWORD *v4; // raxconst CHAR *v5; // r11__int64 v6; // r10int v7; // er9const CHAR *v8; // r10__int64 v9; // rcx__int64 v10; // raxunsigned int v12; // ec…

基于人工智能与边缘计算Aidlux的工业表面缺陷检测

一&#xff1a;训练yolov8得到onnx模型&#xff08;相关教程有很多&#xff09; 二&#xff1a;模型转化&#xff1a; 网站&#xff1a; https://aimo.aidlux.com/ 输入试用账号和密码: 账号:AIMOTC001&#xff0c;密码:AIMOTC001 我们选择 TensorFlowLite 一步步完成转化 …

自然语言处理应用(二):自然语言推断

自然语言推断 自然语言推断&#xff08;Natural Language Inference&#xff09;是指通过对自然语言文本进行逻辑推理和推断&#xff0c;判断两个句子之间的关系&#xff0c;通常包括三种关系&#xff1a;蕴含&#xff08;entailment&#xff09;、矛盾&#xff08;contradict…

Java通过http请求的方式调用他人的接口

本功能的实现&#xff0c;去不参数于这篇博客&#xff0c;给这位大神点赞 基于Spring Boot使用Java调用http请求的6种方式 文章目录 业务背景第一步&#xff0c;配置url第二步&#xff0c;封装请求体&#xff0c;RequestBody第三步&#xff0c;使用HttpURLConnection调用服务…

机器学习(8)---数据预处理

文章目录 一、数据预处理1.1 数据无量纲化1.2 数据归一化1.3 数据标准化1.4 处理选择 二、缺失值2.1 填补的类和参数2.2 用Pandas和Numpy进行填补 三、处理分类型特征&#xff1a;编码与哑变量3.1 编码3.2 哑变量&#xff08;独热编码&#xff09; 一、数据预处理 1.1 数据无量…

【java】【SSM框架系列】【二】SpringMVC

目录 一、SpringMVC简介 1.1 SpringMVC概述 1.2 入门案例 1.3 入门案例工作流程分析 1.4 Conrtoller加载控制与业务BEAN加载控制 1.5 PostMan 二、请求与响应 2.1 请求映射路径 2.2 请求参数 2.2.1 Get 2.2.2 Post 2.2.3 SpringMVC解决post请求中文乱码处理 2.2.4 …

有效回文字符串(Valid palindrome)

题目描述 思路分析 代码实践 java: public class Solutation1 {//定义一个方法&#xff0c;判断是否是有效数字或者字母private static boolean isValid(char c) {//如果不是字母或者数字&#xff0c;那就返回一个flase//这里调用了Character类里面的方法return Character.i…

openwrt开启SSH远程访问与开启WEB远程访问——三种方法

openwrt 开启SSH远程访问 首先&#xff0c;你的电脑用网线连接路由器LAN口是可以访问WEB页面和SSH连接的。 例如&#xff0c;电脑1连接Openwrt路由器&#xff0c;可以进行SSH连接到openwrt 路由器。但是电脑2无法远程访问Openwrt路由器网页和SSH远程连接。 本次操作固件版本…