【数据结构】之红黑树

news2024/11/23 3:00:39

红黑树

  • 红黑树的概念
  • 红黑树的性质
  • 红黑树的插入操作(核心)
    • 情况一:uncle存在且为红
    • 情况二:uncle不存在/存在且为黑(在同一侧)
    • 情况三:uncle不存在/存在且为黑(在两侧)
    • 总结
  • 红黑树的简单实现

红黑树的概念

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

红黑树的性质

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

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
在这里插入图片描述

最短路径为全黑,最长路径就是红黑节点交替(因为红色节点不能连续),每条路径的黑色节点相同,则最长路径、刚好是最短路径的两倍。

最优情况:全黑或每条路径都是一黑一红的满二叉树,高度logN
最差情况:每颗子树左子树全黑,右子树一黑一红。高度2*logN。

红黑树的插入操作(核心)

为什么新插入的节点必须给红色?
新节点给红色,可能会违反上面说的红黑树性质3;如果新节点给黑色,必定会违反性质4。

根据插入节点后会破坏红黑树的结构,将其分为三种情况

情况一:uncle存在且为红

cur、parent、grandfather都是确定颜色的,uncleu存在且为红
在这里插入图片描述
将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

理解:cur为红那么就需要将parent变为黑;parent变黑需要控制每条路径上黑色节点的数量相同,那么就要把uncle变黑;如果grandfather不是根,需要反转为红,用以控制路径黑节点数量相同。继续向上调整即可

情况二:uncle不存在/存在且为黑(在同一侧)

第一种:uncle不存在,则cur为插入节点,单旋即可
在这里插入图片描述
第二种:uncle存在且为黑,是有第一种情况一变化而来的
在这里插入图片描述

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红

情况三:uncle不存在/存在且为黑(在两侧)

第一种:uncle不存在,则cur为插入节点,左右双旋
在这里插入图片描述
第二种:uncle存在且为黑
在这里插入图片描述

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,则转换成了情况2

总结

插入新节点时,父节点为红,看叔叔的颜色。

  1. 叔叔存在且为红,变色,向上调整(可能变为三种情况中的任意一种)

  2. 叔叔不存在或存在且为黑,同侧。单旋+变色

  3. 叔叔不存在或存在且为黑,异侧,两次单旋+变色

红黑树的简单实现

#pragma once
#include<assert.h>
#include<time.h>
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)
		,_col(Red)
	{}
};

template<class K, class V>
struct RBTree
{
	typedef RBTreenode<K, V> Node;
public:
	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//存在与插入结点相同的结点key
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_col = Red;
		if (parent->_kv.first<kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		//红黑树的调整
		while (parent&&parent->_col==Red)
		{
			//祖父节点
			Node* grandfather = parent->_parent;
			if (parent==grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//一:uncle存在且为红
				if (uncle && uncle->_col==Red)
				{
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;

					//如果祖父节点同时是子节点,考虑双红,需要
					//继续向上调整
					cur = grandfather;
					//考虑parent是否为空
					parent = cur->_parent;
				}
				else
				{
					//情况二:cur为红,p红,g黑
					// uncle存在且为黑/不存在
					// 祖孙三代全在同一侧
					//左单选或右单旋
					if (cur==parent->_left)
					{
						RotateR(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						//情况三:
						// c,p为红色,g为黑
						// u不存在/存在且为黑
						//需要进行双旋操作
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
			else//parent==grandfather->_right
			{
				Node* uncle = grandfather->_left;
				if (uncle&&uncle->_col==Red)
				{
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//   g                
					//      p
					//         c
					if (cur==parent->_right)
					{
						RotateL(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else
						{//   g                
						//		   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = Black;
						grandfather->_col = Red;

					}
					break;
				}
			}
		}
		_root->_col = Black;
		return true;
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;


		if (ppNode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		if (ppNode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}
	}
	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);
	}
	//判断是否有连续红结点和路径上黑色节点数量是否一致
	bool Check(Node* root,int BlackNum,const int ref)
	{
		//这里用传值(不用引用)BlackNum,
		//递归返回上一层的时候BlackNum不会被改变
		//每个节点都有一个BlackNum
		if (root==nullptr)
		{
			if (BlackNum != ref)
			{
				cout << "违反规则,本条路径结点与基准不一致" << endl;
				return false;
			}

			return true;
		}
		if (root->_col==Red && root->_parent->_col==Red)
		{
			cout << "违反规则:出现连续红结点" << endl;
			return false;
		}
		if (root->_col==Black)
		{
			++BlackNum;
		}
		return Check(root->_left, BlackNum, ref) &&
			Check(root->_right, BlackNum, ref);
	}
	bool IsRBTree()
	{
		if (_root==nullptr)
		{
			return true;
		}
		if (_root->_col != Black)
		{
			return false;
		}
		//所有路径黑色节点数相同,以最左侧路径黑色节点数为基准
		//递归查看是否匹配
		int ref = 0;
		Node* left = _root;
		while (left)
		{
			if (left->_col==Black)
			{
				++ref;
			}
			left = left->_left;
		}
		return Check(_root,0,ref);

	}
private:
	Node* _root = nullptr;
};

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

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

相关文章

三、学习分类 - 基于图像大小进行分类

天下一半剑仙是我友 谁家娘子不娇羞 我以醇酒洗我剑 谁人说我不风流 1 设置问题 根据图片的尺寸&#xff0c;把图片分为纵向图像和横向图像。这种把图像分成两种类别的问题&#xff0c;就是二分类问题。 纵向图片示例&#xff1a; 横向图片示例&#xff1a; 这样就有了两个…

【Go语言开发】简单了解一下搜索引擎并用go写一个demo

写在前面 这篇文章我们一起来了解一下搜索引擎的原理&#xff0c;以及用go写一个小demo来体验一下搜索引擎。 简介 搜索引擎一般简化为三个步骤 爬虫&#xff1a;爬取数据源&#xff0c;用做搜索数据支持。索引&#xff1a;根据爬虫爬取到的数据进行索引的建立。排序&#xf…

Wsl 错误 0x80004002 解决

wsl2安装教程&#xff1a;https://www.jianshu.com/p/6e7488440db2 我安装的过程中出现了如下错误&#xff1a; 解决办法&#xff1a; 已管理员身份运行Powershell运行以下命令以获取包的全名 Get-AppxPackage |? { $_.Name -like "*WindowsSubsystemforLinux*"…

(33)(33.3) 连接实例

文章目录 前言 33.3.1 嵌入在集体PPM/总信号通道中的RSSI 33.3.2 模拟电压型RSSI被输送到一个专用引脚 33.3.3 PWM类型的RSSI输送到一个专用引脚 前言 以下是典型的 RC 接收机 RSSI 连接方案示例&#xff1a; 33.3.1 嵌入在集体PPM/总信号通道中的RSSI 通常的做法是在一根…

NLog写日志到数据库

需求&#xff1a;NLog写日志到数据库 一、必须要安装&#xff1a; System.Data.SqlClient 二、 NLog配置&#xff1a; <?xml version"1.0" encoding"utf-8" ?> <nlog xmlns"http://www.nlog-project.org/schemas/NLog.xsd" …

存css实现动态时钟背景

代码实现 <!DOCTYPE html> <html lang"en"> <head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><title>Title</title><meta name"referrer" content"no-referrer…

Spring的控制翻转(IOC)与依赖注入(DI)

SpringIOC 即 Inversion of Control&#xff0c;缩写为 IOC&#xff0c;就是由 Spring IoC 容器管理对象&#xff0c;而非传统实现中由程序代码直接操控. 使用IOC容器管理bean&#xff08;IOC&#xff09; 在IOC容器中将有依赖关系的bean进行关系绑定 最终达到的目的&#…

【C】回调函数和qsort详解

回调函数概念 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一 个函数&#xff0c;当这个指针被用来调用其所指向的函数时&#xff0c;我们就说这是回调函数。回调函数不是由该 函数的实现方直接调用&#xff0c;…

七大排序算法——快速排序,通俗易懂的思路讲解与图解(完整Java代码)

文章目录 一、排序的概念排序的概念排序的稳定性七大排序算法 二、快速排序核心思想Hoare法挖坑法前后指针法(选学) 三、性能分析四、算法优化优化基准的选取优化少量数据时的排序方案优化后的完整代码 五、七大排序算法 一、排序的概念 排序的概念 排序&#xff1a;所谓排序…

基于ChatGPT和私有知识库搭建Quivr项目

准备工作 安装docker和docker-compose申请supabase账号 拉取Quivr代码 git clone https://github.com/StanGirard/Quivr.git 复制.XXXXX_env文件 cp .backend_env.example backend/.env cp .frontend_env.example frontend/.env 更新backend/.env和frontend/.env文件 ba…

靶场的安装

sqli-lab 1.将安装包解压放到WWW目录下 2.修改 db-creds.inc文件里面的数据库的用户名密码为自己的用户名密码 路径&#xff1a;D:\phpStudy_64\phpstudy_pro\WWW\sqli-labs-master\sql-connections\db-creds.inc 3. 更改php版本位5.9版本&#xff0c;不然会报错 4.安装数…

【采用有限元法技术计算固有频率和欧拉屈曲荷载】使用有限元法的柱子的固有频率和屈曲荷载(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Android JNI 异常处理 (十一)

🔥 Android Studio 版本 🔥 🔥 创建包含JNI的类 JNIException.java 🔥 package com.cmake.ndk1.jni;public class JNIException {static {System.loadLibrary("exception-lib");}public native void nativeInvokeJavaException();public native void nativ…

B站这些“搬运工”还能有这么高的流量吗?

飞瓜数据&#xff08;B站版&#xff09;观察发现&#xff0c;B站经常有一些搬运视频能够获得超高流量。 比如拉取近15天的B站热门视频&#xff0c;位列前排的就有两个是搬运二创视频&#xff0c;播放量高达900万上下&#xff0c;可以说是爆款视频了。 这些视频有一个相同的点就…

Qt Https通信: TLS initialization failed 解决方法

Qt Https通信&#xff1a; TLS initialization failed 解决方法&#xff0c;Window端使用Qt 做开发请求Https资源时&#xff0c;会经常遇到 TLS initialization failed。 原因分析&#xff1a; 在Qt中并未包含 SSL所包含的库&#xff0c;因此需要开发者&#xff0c;自己将库拷贝…

最新华为鸿蒙4.0安装谷歌服务框架,安装Play商店,谷歌Google,GMS

最近华为推出了最新鸿蒙4.0开发者Beta版本&#xff0c;让用户测试体验。那么测试体验的机器主要是最近发布的几款机器为P60,P60 Pro, mate50,mate50 pro等几款产品可以先期进行体验测试鸿蒙4.0&#xff0c;那么很多的用户在疑问我升级到鸿蒙4.0。是不是还是可以使用Google谷歌服…

LINUX环境小实验

实验报告 实验名称 小环境搭设 实验目的 1.搭建DHCP服务器&#xff08;IP&#xff1a;192.168.100.253静态IP网卡vmnet1&#xff09; 2.搭建DNS&#xff08;通过DHCP服务器分到指定的IP&#xff1a;192.168.100.252&#xff09; 3.搭建网站服务&#xff08;通过DHCP服务器分…

波分复用(WDM)基本原理

文章目录 波分复用WDMDWDM解决问题&#xff0c;特点&#xff0d;超长距离无电中继传输&#xff0c;降低成本 波分系统的基本组成DWDM网元基本类型波分常见站点类型OM/OD技术&#xff0d;波分复用器主要参数 DWDM系统关键技术光转发技术 OM/OD技术&#xff0d;波分复用器件 波分…

Bun 0.6.14发布,1.0版预计发布于9月7日

Bun 是一个 JavaScript 运行时。 Bun 是一个从头开始构建的新 JavaScript 运行时&#xff0c;旨在服务现代 JavaScript 生态系统。它有三个主要设计目标&#xff1a; 速度。包子启动快&#xff0c;运行也快。它扩展了 JavaScriptCore&#xff0c;即为 Safari 构建的注重性能的 …

Office如何通过VSTO进行PPT插件开发?

文章目录 0.引言1.工具准备2.PPT外接程序创建和生成3.外接程序生成并使用 0.引言 VSTO&#xff08;Visual Studio Tools for Office &#xff09;是VBA的替代&#xff0c;是一套用于创建自定义Office应用程序的Visual Studio工具包。VSTO可以用Visual Basic 或者Visual C#扩展O…