RBTree(红黑树)

news2025/2/7 20:50:49

目录

红黑树的概念

 红黑树的性质

红黑树节点的定义

红黑树的插入

1. 按照二叉搜索的树规则插入新节点

 2. 检测新节点插入后,红黑树的性质是否造到破坏

红黑树的检测

红黑树的删除

红黑树和AVL树的比较


红黑树的概念

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

 红黑树的性质

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

 2. 根节点是黑色的 

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

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

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

 思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点 个数的两倍?

红黑树节点的定义

//节点的颜色
enum Colour
{
	BLACK
	, RED
};

//节点定义
template<class T>
struct RBTreenode
{    
	T  _data;  //储存的数据
	RBTreenode<T>* _left;  //左孩子
	RBTreenode<T>* _right;  //右孩子
	RBTreenode<T>* _parent;    //父亲
	Colour _col;              //颜色
 
    //构造函数
	RBTreenode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}

};

再我们定义节点的时候,需要用一个枚举类型来定义我们的节点的颜色。然后还是三叉链,左孩子,右孩子,父亲。

红黑树的插入

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

1. 按照二叉搜索的树规则插入新节点

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 (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if(kv.first<cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new node(kv);

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

	cur->_parent = parent;

    // ~~~~~~~~~~~~~~~~~~~~~~
    //判断颜色
	return true;
}

 2. 检测新节点插入后,红黑树的性质是否造到破坏

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

        约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

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

看到上图,cur插入的时候本身就为红,如果说它的父亲为红,叔叔存在且为红的话,我们就需要把p和u 变为黑,再让g 变为红,然后我们还需要向上更新,因为这幅图可能是一颗子树,它的祖先也需要更新颜色,如下图。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

 说明: u的情况有两种

1.如果u节点存在,则cur一定是新插入的节点,因为如果cur不是新插入的节点,则cur和p一定有一个的节点是黑色,就不满足性质4:每条路径黑色节点个数相同。

2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur由黑色变为了红色。

对于这种情况,我们就需要进行旋转了,对,没错,和AVL树里面的旋转操作是一样的,代码我就复制一下。

p为g的左孩子,cur为p的左孩子,则进行右单旋;

相反,p为g的右孩子,cur为p的右孩子,则进行左单旋。

我们在旋转完成之后,需要和上图一样,再把相应的颜色变换就行了。

右单旋:

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

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

	node* pParent = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

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

		subL->_parent = pParent;
	}
}

左单旋:

void RotateL(node* parent)
{
	node* subR = parent->_right;
	node* subRL = subR->_left;
	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

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

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

像上面这种情况,我们就需要旋转两次,和AVL树的操作差不多。

p为g的左孩子,cur为p的右孩子,则对p进行左单旋;

相反,p为g的右孩子,cur为p的左孩子,则对p进行右单旋。

注意:这样它们就转化为了情况二,所以,我们再对它们进行情况二的右单旋或左单旋即可。旋转完之后,再修改它们的颜色即可。

我们在插入的时候,对每种情况进行相应的处理即可。

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 (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if(kv.first<cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new node(kv);

	if (kv.first < parent->_kv.first)
		parent->_left = cur;
	else
		parent->_right = 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)
				{
					//     g
				   //   p    u
			      // c
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					//      g
					//   p    u
					//     c
					RotateL(parent);
					RotateR(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}
		else  //parent == grandfather->_right
		{
			//     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);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			
			}
		}
		_root->_col = BLACK;
	}
	return true;
}

以上就是整个的插入代码。

红黑树的检测

红黑树的检测分为两步:

1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

2. 检测其是否满足红黑树的性质

中序遍历

	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 refNum)
{
	if (_root == nullptr)
	{
		if (blackNum != refNum)
		{
			cout << "存在路径黑色节点不同" << endl;
			return false;
		}
		return true;
	}

	if (_root->_col == BLACK)
		blackNum++;

	if (_root->_col == RED && _root->_parent->_col == RED)
	{
		cout << "存在两个连续节点都为红色" << endl;
		return false;
	}

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

红黑树的删除

https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

删除操作我们只做了解就行了。

红黑树和AVL树的比较

        红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。

总代码

enum Colour
{
	BLACK
	,RED
};

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)
	{}

};

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* parent = nullptr;
		node* cur = _root;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if(kv.first<cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new node(kv);

		if (kv.first < parent->_kv.first)
			parent->_left = cur;
		else
			parent->_right = 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)
					{
						//     g
					   //   p    u
				      // c
						RotateR(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//      g
						//   p    u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
			else  //parent == grandfather->_right
			{
				//     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);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				
				}
			}
			_root->_col = BLACK;
		}
		return true;
	}

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

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

		node* pParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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

			subL->_parent = pParent;
		}
	}

	void RotateL(node* parent)
	{
		node* subR = parent->_right;
		node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

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

	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

	int size()
	{
		return _size(_root);
	}

	int height()
	{
		return _height(_root);
	}


	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		
		if (_root->_col == RED)
			return false;

		int refnum = 0;
		node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				refnum++;
			cur = cur->_left;
		}

		return Check(_root, 0, refnum);
	}

private:

	bool Check(node* _root, int blackNum, const int refNum)
	{
		if (_root == nullptr)
		{
			if (blackNum != refNum)
			{
				cout << "存在路径黑色节点不同" << endl;
				return false;
			}
			return true;
		}

		if (_root->_col == BLACK)
			blackNum++;

		if (_root->_col == RED && _root->_parent->_col == RED)
		{
			cout << "存在两个连续节点都为红色" << endl;
			return false;
		}

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


	int _height(node* _root)
	{
		if (_root == nullptr)
			return 0;
		int lefth = _height(_root->_left);
		int righth = _height(_root->_right);

		return lefth > righth ? lefth + 1 : righth + 1;
	}

	int  _size(node*_root)
	{
		if (_root == nullptr)
			return 0;

		return _size(_root->_left) + _size(_root->_right) + 1;
	}
	void _Inorder(node* _root)
	{
		if (_root == nullptr)
		{
			return;
		}

		_Inorder(_root->_left);
		cout << _root->_kv.first << ":" << _root->_kv.second << endl;
		_Inorder(_root->_right);
	}
	node* _root=nullptr;

};

我们后面还会用红黑树来模拟实现map和set。

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

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

相关文章

linux自动化一键批量检查主机端口

1、准备 我们可以使用下面命令关闭一个端口 sudo iptables -A INPUT -p tcp --dport 端口号 -j DROP我关闭的是22端口&#xff0c;各位可以关其它的或者打开其它端口测试&#xff0c;谨慎关闭22端口&#xff01;不然就会像我下面一样握手超时&#x1f62d;&#x1f62d;&…

Python大数据可视化:基于python大数据的电脑硬件推荐系统_flask+Hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 价格区间界面 用户信息界面 品牌管理 笔记本管理 电脑主机…

AEO海关认证的注意事项

AEO海关认证的注意事项繁多且至关重要&#xff0c;企业需细致准备&#xff0c;确保万无一失。 首先&#xff0c;企业需深入研读相关政策文件&#xff0c;如《中华人民共和国海关注册登记和备案企业信用管理办法》及《海关高级认证企业标准》&#xff0c;以政策为指引&#xff0…

MySQL如何只取根据某列连续重复行的第一条记录

前言 MySQL如何只取根据某列连续重复行的第一条记录&#xff0c;条件&#xff1a;某列、连续、验重 建表准备 DROP TABLE IF EXISTS test; CREATE TABLE test (id bigint NOT NULL,time datetime NULL DEFAULT NULL,price int NULL DEFAULT NULL,PRIMARY KEY (id) USING BT…

c++编译过程初识

编译过程 预处理&#xff1a;主要是执行一些预处理指令&#xff0c;主要是#开头的代码&#xff0c;如#include 的头文件、#define 定义的宏常量、#ifdef #ifndef #endif等条件编译的代码&#xff0c;具体包括查找头文件、进行宏替换、根据条件编译等操作。 g -E example.cpp -…

JS中的闭包和上下文

变量提升 和 函数提升 这里要提到一个提升的概念&#xff0c;即在JS中&#xff0c;在解析代码之前还有一个预处理的过程&#xff0c;这个过程中会把部分变量和函数声明提前到代码的最顶部&#xff0c; 会在其他所有代码之前执行。虽然当我们按照规范&#xff08;严格模式或者T…

GitLab 将停止为中国区用户提供服务,60天迁移期如何应对? | LeetTalk Daily

“LeetTalk Daily”&#xff0c;每日科技前沿&#xff0c;由LeetTools AI精心筛选&#xff0c;为您带来最新鲜、最具洞察力的科技新闻。 GitLab作为一个广受欢迎的开源代码托管平台&#xff0c;近期宣布将停止服务中国大陆、澳门和香港地区的用户提供服务。根据官方通知&#x…

【2024年最新】BilibiliB站视频动态评论爬虫

废话不多说&#xff0c;直接先放git仓库&#xff1a;GitHub - linyuye/Bilibili_crawler: bilibili爬虫&#xff0c;基于selenium获取oid与cookie&#xff0c;request获取api内容 〇&#xff1a;概念简述 oid&#xff1a;视频/动态的uuid&#xff0c;b站对于发布内容的通用唯…

汽车IVI中控开发入门及进阶(46):FFmpeg

概述: FFmpeg 是领先的多媒体框架,能够解码、编码、 转码、复用、解复用、流、过滤和播放 几乎所有人类和机器创建的东西。它支持最模糊的古老格式,直到最前沿。无论它们是由某个标准委员会、社区还是公司设计的。它还具有高度的可移植性:FFmpeg 在各种构建环境、机器架构…

计算属性 简写和 完整写法

计算属性渲染不加上括号 methods方法和computed属性区别&#xff1a; computed只计算一次&#xff0c;然后缓存&#xff0c;后续直接拿出来使用&#xff0c;而methods每次使用每次计算&#xff0c;不会缓存 计算属性完整写法&#xff1a; 既获取又设置 slice 截取 成绩案例 …

WebRTC Simulcast 大小流介绍与优化实践

Simulcast 是 WebRTC 中的一种标准化技术 &#xff0c;简称大小流。通过 Simulcast&#xff0c;客户端可以同时发送同一视频的多个版本。每个版本都以不同的分辨率和帧率独立编码&#xff0c;带宽较多的拉流端可以接收较高质量的视频流&#xff0c;带宽有限的拉流端则可以接收较…

kong网关使用pre-function插件,改写接口的返回数据

一、背景 kong作为api网关&#xff0c;除了反向代理后端服务外&#xff0c;还可对接口进行预处理。 比如本文提及的一个小功能&#xff0c;根据http header某个字段的值&#xff0c;等于多少的时候&#xff0c;返回一个固定的报文。 使用到的kong插件是pre-function。 除了上…

轮播图带详情插件、uniApp插件

超级好用的轮播图 介绍访问地址参数介绍使用方法&#xff08;简单使用&#xff0c;参数结构点击链接查看详情&#xff09;图片展示 介绍 带有底部物品介绍以及价格的轮播图组件&#xff0c;持续维护&#xff0c;uniApp插件&#xff0c;直接下载填充数据就可以在项目里面使用 …

Vite内网ip访问,两种配置方式和修改端口号教程

目录 问题 两种解决方式 结果 总结 preview.host preview.port 问题 使用vite运行项目的时候&#xff0c;控制台会只出现127.0.0.1&#xff08;localhost&#xff09;本地地址访问项目。不可以通过公司内网ip访问&#xff0c;其他团队成员无法访问&#xff0c;这是因为没…

老旧小区用电安全保护装置#限流式防火保护器参数介绍#

摘要 随着居民住宅区用电负荷的增加&#xff0c;用电安全问题日益突出&#xff0c;火灾隐患频繁发生。防火限流式保护器作为一种新型电气安全设备&#xff0c;能够有效预防因电气故障引发的火灾事故。本文介绍了防火限流式保护器的工作原理、技术特点及其在居民住宅区用电系统…

Ftrans数据摆渡系统 搭建安全便捷跨网文件传输通道

一、专业数据摆渡系统对企业的意义 专业的数据摆渡系统对企业具有重要意义&#xff0c;主要体现在以下几个方面‌&#xff1a; 1、‌数据安全性‌&#xff1a;数据摆渡系统通过加密传输、访问控制和审计日志等功能&#xff0c;确保数据在传输和存储过程中的安全性。 2、‌高…

LabVIEW生物医学信号虚拟实验平台

介绍了一款基于LabVIEW的多功能生物医学信号处理实验平台的设计和实现。平台通过实践活动加强学生对理论的理解和应用能力&#xff0c;特别是在心电图(ECG)和脑电图(EEG)的信号处理方面。实验平台包括信号的滤波、特征提取和频谱分析等功能&#xff0c;能直观体验和掌握生物医学…

大数据实验三

Python and anaconda 实验三数据预处理和轨迹聚类参考地址&#xff1a; https://www.hifleet.com/wp/communities/data/hangyundashujujishukechengshiyanzhinanshujuyuchulijiguijijuleichixugengxinzhong#post-2212https://www.hifleet.com/wp/communities/data/hangyundas…

【python因果库实战14】因果生存分析3

标准化生存分析 参见《因果推断》一书第17.5节&#xff08;“参数化的g公式”&#xff09;。 在参数化标准化中&#xff0c;也称为“参数化g公式”&#xff0c;时间步k处的生存率是对协变量X水平和处理分配a条件下的条件生存率的加权平均&#xff0c;权重为每个分层中个体的比…

云边端一体化架构

云边端一体化架构是一种将云计算、边缘计算和终端设备相结合的分布式计算模型。该架构旨在通过优化资源分配和数据处理流程&#xff0c;提供更高效、更低延迟的服务体验。 下面是对这个架构的简要说明&#xff1a; 01云计算&#xff08;Cloud Computing&#xff09; — 作为中心…