C++进阶篇4---番外-红黑树

一、红黑树的概念

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

红黑树的性质

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

下图是一个红黑树

根据红黑树的性质:我们能得到这样一个结论---最长路径不会超过最短路径的两倍,为什么呢?

理由如下:因为每条路径上黑色节点的个数要相同,所以最短的路径上的点均为黑色结点,同时因为不能出现红色结点相邻的情况,所以最长路径上的结点颜色只能是黑红相间,故得出上诉结论

二、红黑树结点定义

enum Colour //这里用的枚举类型,也可以用其他的类型,只要能代表红黑两种颜色就行,如true/false
{
	BLACK,
	RED
};

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

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

思考:为什么要将结点颜色默认设置为红色???

理由如下:由于每条路径上的黑色节点的个数要保持相同,如果我们插入的结点的颜色为黑色,那么必然该节点所在路径的黑色节点的个数要增加,就会导致这颗树的其他所有路径都需要多一个黑色节点,影响范围太大,而如果插入的结点颜色为红色,我们只要关心它所在子树的情况就行,具体看下面的插入操作。故每个新插入结点颜色都默认为红色

三、红黑树插入结点

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1.按照二叉搜索树的规则插入新节点,如下
template <class K,class V>
class 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* cur = _root;
		Node* parent = nullptr;
		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);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
        
        //调整红黑树
        //...
    }
private:
	Node* _root = nullptr;
};

2.检查新节点插入后,红黑树的性质有没有被破坏

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

情况一:cur为红,p为红,g为黑,u存在且为红

在这种情况下,我们只要改变结点的颜色就能保持红黑树的性质。

(注意:这种情况下,不用关心cur所在的位置)

情况二:cur为红,p为红,g为黑,u不存在或为黑色

 

这里就无法通过改变p、g、u的颜色来保持红黑树的性质(读者可以去手玩尝试一下),需要进行旋转+变色才行,那么看到这种形状的图形,我们就应该想到AVL树中的单旋和双旋,上面的情况很明显对应右单旋,旋转之后如下图

如何改变颜色使得红黑树的性质保持不变?

1.uncle为空,显而易见,将g变为红,p变为黑(可能有人会觉得将cur变成黑不是也行嘛?但如果p为红,且它是子树,那么还需要向上调整,会很麻烦,但如果p为黑色,那么它本身既符合红黑树性质,也并不会对其他的子树产生影响,直接就一步到位了)

2.uncle为黑色,(这种情况下,cur不可能是新插入的结点,只能是情况一向上调整得到的),这里就要分析a,b,c,d,e这几颗子树中黑色节点的个数,显然a,b,c的黑色结点个数相同,d和e的黑色结点比abc少一个,所以将p变为黑色,g变为红色,就能保持红黑树的性质(至于为什么不选择将g变成红色,理由同上)

具体的旋转和AVL树一样,只是红黑树旋转后需要变色,AVL树旋转后需要调整平衡因子,不了解的,可以看我写过的AVL树

这里,我们还要考虑cur所在的位置,共四种情况,第一种情况就是上面所讲的,剩下三种的旋转+变色,留给读者思考【旋转不会的可以去看我之前写的AVL树】

 

总结:红黑树的结点的插入首先看父节点是否为黑色(插入结点为根的情况要特别判断),如果为黑,不用处理,如果为红,关键看uncle结点,它决定了我们是否需要旋转,如果是红色,则只需要变色,如果是黑色/不存在,我们需要根据cur所在的位置选择合适的旋转方式,并对旋转之后的结点进行变色

 代码如下---附带检查检查红黑树是否正确的函数

enum Colour {
	BLACK,
	RED
};

template<class K,class V>
struct RBTreeNode {
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _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 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* cur = _root;
		Node* parent = nullptr;
		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);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		//父节点为黑,不用处理
		//为红需要调整
		while (parent && parent->_col == RED)
		{
			//    g
			//  p   u
			//c
			Node* grandparent = parent->_parent;
			if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				if (uncle && uncle->_col == RED)//情况一
				{
					grandparent->_col = RED;
					parent->_col = uncle->_col = BLACK;

					cur = grandparent;
					parent = cur->_parent;
				}
				else//情况二
				{
					if (parent->_left == cur)//单旋
					{
						RotateR(grandparent);
						grandparent->_col = RED;
						parent->_col = BLACK;
					}
					else//双旋
					{
						RotateL(parent);
						RotateR(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
			else
			{
				//     g
				//  u     p
				//       c  c
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					//变色
					grandparent->_col = RED;
					parent->_col = uncle->_col = BLACK;
					//向上走
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pParent = parent->_parent;
		subR->_left = parent;
		parent->_right = subRL;
		parent->_parent = subR;
		if (subRL)//注意h==0的情况
			subRL->_parent = parent;
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = pParent;
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
		}
	}


	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pParent = parent->_parent;
		subL->_right = parent;
		parent->_left = subLR;
		parent->_parent = subL;
		if (subLR)//注意h==0的情况
			subLR->_parent = parent;
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = pParent;
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
		}
	}

	void InOrder()
	{
		_InOrder(_root);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	bool Isbalance()
	{
		return _Isbalance(_root);
	}
	//检查是否出现两个连续的红色节点+是否路径上的黑色节点的个数是否相同
	bool check(Node* root,int blacknum)
	{
		if (root == nullptr)
		{
			cout << blacknum << " ";
			return true;
		}
		if (root->_col == RED && root->_parent->_col == RED)
			return false;
		if (root->_col == BLACK)
			blacknum++;
		return check(root->_left, blacknum) && check(root->_right, blacknum);
	}


	bool _Isbalance(Node* root)
	{
		if (root == nullptr)
			return true;
		if (root->_col == RED)
		{
			cout <<"出现两个连续的红色节点:"<< root->_kv.first << endl;
			return false;
		}
		return check(root->_left, 1) && check(root->_right, 1);
	}
private:
	Node* _root = nullptr;
};

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

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

相关文章

[笔记]深入解析Windows操作系统《番外》windows关键进程解释

文章目录 前言一、Linux起源与发展二、什么是shell1.什么是Shell 总结 前言 一、Linux起源与发展 二、什么是shell 1.什么是Shell 总结 以上就是今天要讲的内容&#xff0c;本文仅仅简单介绍了linux命令行的使用。 参考&#xff1a; shells 概念 centOS7中的几个Ctrl组合…

Spring Cloud学习(七)【Docker 容器】

文章目录 初识 DockerDocker 介绍Docker与虚拟机Docker架构安装 Docker Docker 基本操作镜像相关命令容器相关命令数据卷 Dockerfile 自定义镜像镜像结构Dockerfile DockerComposeDockerCompose介绍安装DockerCompose Docker镜像仓库常见镜像仓库服务私有镜像仓库 初识 Docker …

【机器学习】八、规则学习

知识图谱与基本概念 基本概念 规则学习定义&#xff1a;从训练数据中学习出一组能用于对未见示例进行判别的规则。 规则定义&#xff1a;规则一般是&#xff1a;语义明确、能描述数据分布所隐含的客观规律或领域概念。 逻辑规则定义&#xff1a;⊕←?1⋀?2⋀?3…⋀??⊕…

Redis 5大数据类型命令解读

目录 Redis key的命令 1、redis字符串&#xff08;String&#xff09; 2、redis列表(List) 3、redis哈希表(Hash) 4、redis集合(Set) 5、redis有序集合(ZSet) Redis 命令网站&#xff1a;redis中文文档 Redis key的命令 命令说明示例keys *查看当前库所有的keykeys *…

Please No More Sigma(构造矩阵)

Please No More Sigma 给f(n)定义如下&#xff1a; f(n)1 n1,2; f(n)f(n-1)f(n-2) n>2; 给定n&#xff0c;求下式模1e97后的值 Input 第一行一个数字T&#xff0c;表示样例数 以下有T行&#xff0c;每行一个数&#xff0c;表示n。 保证T<100&#xff0c;n<100000…

linux安装并配置git连接github

git安装 sudo apt-get install git git信息配置 git config --global uer.name "yourname" git config --global user.email "youremail" 其中&#xff0c;yourname是你在github上配置的用户名&#xff0c;youremail是你在github上设置的邮箱 查看git…

计算机指令考前小记

RTL寄存器传送语言&#xff1a;简化对指令功能的说明 R[r]&#xff1a;存储器r的内容M[addr]&#xff1a;存储单元addr的内容M[R[r]]&#xff1a;寄存器r的内容所指的存储单元的内容 汇编指令movw 4(%ebp),%ax的RTL语言为&#xff1a;R[ax] <- M[R[ebp]4] 将寄存器EBP的内…

sqli-labs关卡14(基于post提交的双引号闭合的报错注入)通关思路

文章目录 前言一、回顾上一关知识点二、靶场第十四关通关思路1、判断注入点2、爆显位3、爆数据库名4、爆数据库表5、爆数据库列6、爆数据库关键信息 总结 前言 此文章只用于学习和反思巩固sql注入知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不…

3D模型人物换装系统二(优化材质球合批降低DrawCall)

3D模型人物换装系统 介绍原理合批材质对比没有合批材质核心代码完整代码修改总结 介绍 本文使用2018.4.4和2020.3.26进行的测试 本文没有考虑法线贴图合并的问题&#xff0c;因为生成法线贴图有点问题&#xff0c;放在下一篇文章解决在进行优化 如果这里不太明白换装的流程可以…

SpringBoot之手写starter

SpringBoot之手写starter 在开始之前呢&#xff0c;我们需要了解一些概念 1、starter介绍 spring boot 在配置上相比spring要简单许多, 其核心在于spring-boot-starter, 在使用spring boot来搭建一个项目时, 只需要引入官方提供的starter, 就可以直接使用, 免去了各种配置。…

2390 高校实验室预约系统JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个高校实验室预约系统的设计和实现。该系统包括管理员、教师和学生三种用户&#xff0c;具有基础数据管理、学生管理、教师管理、系统公告管理、实验室管理、实验室预约管理和系统管理等模块。通过数据库设计和界面设计&#xff0c;实现了用户友好的操作体验…

【Linux】虚拟机连不上外网 (ping www.baidu.com不通)

进入linux系统&#xff0c;打开终端&#xff0c;ping www.baidu.com 发现ping不通 首先我连接的是nat模式 查看是否连接上自己本机的网 切换root用户 使用 ifconfig 命令查看是eth0 还是 ens33 vi /etc/sysconfig/network-scripts/ifcfg-ens33 BOOTPROTOstatic ONBOOTyes …

模板——“C++”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容是C中的模板初阶的内容&#xff0c;下面&#xff0c;让我们进入C模板的世界吧&#xff01;&#xff01;&#xff01; 1. 泛型编程 2. 函数模板 3. 类模板 泛型编程 如何实现一个通用的交换函数呢&#xff1f;…

如何解决Windows电脑 Create folder error,Access is denied.

如何解决 Create folder error, Error: mkdir C:\Program Files\nodejs\21.1.0/: Access is denied. Waring: Name : http://npm.taobao.org/mirrors/node/v21.1.0/win-x64/node.exe Code : -2 Error : Create folder error, Error: mkdir C:\Program Files\nodejs\\21.1.0/…

MySQL主从环境搭建

MySQL主从环境搭建 主机MySQL配置 修改主机配置文件 vim /etc/my.cnf主要是在my.cnf配置文件中增加以下内容&#xff1a; #主服务器唯一ID server-id1 #启用二进制日志 log-binmysql-bin # 设置不要复制的数据库(可设置多个) binlog-ignore-dbmysql binlog-ignore-dbinform…

解决删除QT后Qt VS Tools中Qt Options中未删除的错误

在Qt VS Tools的Qt Options已经配置好Qt Versions后如果删除QT程序之后会出现Default Qt/Win version任然存在&#xff0c;这是如果再添加一个话就不能出现重名了&#xff0c;如果新建一个其他名字的话其实在vs中还是不能正常运行qt&#xff0c;会出现点击ui文件vs会无故重启或…

【Liunx】服务器解决 跨域问题

首先打开后端的站点 在站点设置内打开 "配置文件" 然后在 “server_name 本机ip ”下方添加跨域配置,添加成功后重启nginx即可 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, OPTIONS, PUT, DELETE; add_header A…

ZYNQ_project:ram_dual_port

伪双端口ram&#xff1a;写端口&#xff1a;clk_w,en_A,we_A,addr_A,din_A;读端口:clk_r,en_B,addr_B;dout_B. 设计读写模块&#xff0c;写入256个数据&#xff0c;再读出256个数据。 输入时钟100Mhz&#xff0c;输出时钟50Mhz。 多bit数据&#xff0c;高速时钟域到低速时钟…

05-Spring中Bean的生命周期

Bean的生命周期 生命周期就是对象从创建开始到最终销毁的整个过程 , Spring其实就是一个管理Bean对象的工厂,它负责对象的创建和销毁等 Bean生命周期的管理可以参考Spring的源码&#xff1a;AbstractAutowireCapableBeanFactory类的doCreateBean()方法 研究生命周期的意义&am…

消息中心常见解决方案分享

解决方案 1、问题2、设计3、流程 看了大部分的消息中心解决方案&#xff0c;发现大家的中心思想都大差不差&#xff0c;区别基本都是在符合自身业务场景的做了一些定制化处理。本文为我对消息中心基本骨架的知识梳理&#xff0c;亦在帮助大家对消息中心设计有一个基本的理解。 …