C++_红黑树

news2024/12/20 12:15:06

       

目录

1、红黑树的规则

2、红黑树节点的定义 

3、红黑树插入节点的调整操作

3.1 情况一

3.2 情况二

3.3 情况三 

4、红黑树的实现

结语


前言:

        在C++中,红黑树是二叉搜索树的另一种优化版本,他与AVL树的区别在于保持树的平衡方式不同,AVL树保持平衡的方式是在节点中多存储一个成员来记录平衡因子,红黑树保持平衡的方式也是增加了一个成员,但是该成员的作用是记录节点的两种状态(颜色):红色--黑色。当然只记录颜色并不能保持平衡,红黑树还规定最长路径的节点个数不会超过最短路径的节点个数的两倍,因此红黑树不会因为插入有序数据而演变成“单支树”。

1、红黑树的规则

        红黑树有如下规则:

        1、顾名思义,红黑树的节点只能有黑色和红色两种状态。

        2、根结点默认为黑色。

        3、红色节点的两个孩子只能是黑色节点。

        4、插入的节点默认为红色节点。

        5、每条路径的黑色节点都相同。

        红黑树正确示意图:

         红黑树错误示意图:

2、红黑树节点的定义 

        通过上述对红黑树的简述,可以给出红黑树的节点代码:

#define _CRT_SECURE_NO_WARNINGS 1

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;//若是AVL树这里记载的是平衡因子,红黑树是颜色

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)//默认插入的节点是红色
	{}
};

        可以发现,红黑树的节点代码几乎和AVL树一模一样,只是控制平衡的条件有区别,仅此而已。 

3、红黑树插入节点的调整操作

        红黑树的插入函数可以分两个步骤:

        1、找到合适的位置插入,即二叉搜索树插入的逻辑(小于根节点的放在左边,大于根节点的放在右边)。

        2、因为插入的节点默认为红色,则插入节点后,查看当前树是否破坏了红黑树的规则,即观察其节点的父母节点是否为红色,如果是则需要进行调整操作(规则3)。

        在分析之前,先确定好节点之间的关系名称(cur表示新插入的节点,parant表示父母节点,uncle表示叔叔节点,gparent表示祖父节点):

3.1 情况一

        当叔叔节点存在且为红,父母节点为红,祖父节点为黑:

        最后可以发现,经过一系列的调整后符号红黑树的规则。

3.2 情况二

        情况二又分两种情况:1、叔叔节点为黑色。2、不存在叔叔节点。

        1、其他条件和情况一相同,但是叔叔节点是黑色的:

        从上图可以发现,情况二多了旋转的步骤,并且在旋转之后将parent变黑,gparent变红,最终结果满足红黑树的规则。

        2、若不存在叔叔节点:

        综上所述,情况二可以总结为:旋转+变色(parent变黑,gparent变红)。 

3.3 情况三 

        情况三即以上情况的插入点不一样,以上情况的插入节点都是插入在“边缘处”,通俗来讲就是左子树插入的节点都是作为左孩子插入的,而右子树插入的节点都是作为右孩子插入的,但是实际中总会出现在右子树中插入的节点是以左孩子的形式插入的(如下图),拿上述情况二的第二种情况举例,若插入的cur在parent的左边,那么以上的处理方法显然不能解决问题,具体操作图如下:

4、红黑树的实现

        红黑树的实现代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

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;//若是AVL树这里记载的是平衡因子,红黑树是颜色

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

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

		//当parent为红色则违法规则,需要调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)//父母在祖父的左边
			{
				Node* uncle = grandfather->_right;
				// 情况1:叔叔节点存在且为红,叔叔和父母进行变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2:叔叔不存在/叔叔存在且为黑,旋转+变色
				{
					//父母在祖父的左边,而cur在父母的左边,说明是“边缘”情况
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//对应所述的情况三,需要旋转两次,并且cur变成了根结点
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else // 父母在祖父的右边
			{
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2:叔叔不存在/叔叔存在且为黑,旋转+变色
				{
					//以下逻辑同上思路,只不过逻辑相反
					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 InOrder()
	{
		_InOrder(_root);
	}

	bool IsRedB()//检查红黑树
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}

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

		// 连续红色节点
		return _Check(_root, 0, benchmark);
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	void _Destroy(Node* root)//释放空间
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _Check(Node* root, int blackNum, int benchmark)//红黑树的验证
	{
		if (root == nullptr)//走到空,就判断黑色节点数量是否一样
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == BLACK)//重新记录每条路径下的黑色节点
		{
			++blackNum;
		}

		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)//连续红色节点也会报错
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		//每次递归都把黑色节点数量传给下一个栈帧
		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

	void _InOrder(Node* root)//中序遍历打印
	{
		if (root == nullptr)
		{
			return;
		}

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

	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 (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}

private:
	Node* _root = nullptr;
};

int main()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}

	t1.InOrder();//j检查打印出来的数据是否有序
	cout << endl << t1.IsRedB() << endl;
	return 0;
}

         运行结果:

        从上面代码可以看出,检验一棵树是否为红黑树,从以下几个方面进行判断:函数IsRedB的作用是判断根结点是否为黑色,然后随便遍历一条路径,记录该路径下黑色节点的数量(规则5)。 

        接着把记录下来的黑色节点数量传给函数_check,并且让_check函数把所有路径下的黑色节点记录下来,一一的去跟之前记录好的数据进行对比,若有不相等的情况说明该树不是红黑树,另外_check函数内还进行了红色节点是否连续的判断(规则3)。

结语

        以上就是关于红黑树的讲解,红黑树的重点还是在于了解红黑树的调整逻辑,理清叔叔节点和父母节点他们的位置关系,最核心的还是叔叔节点,他的存在与否,是红色还是黑色都影响最终的调整规律。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

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

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

相关文章

STM32CubeMX实战教程: TIM6、TIM7 - 基本定时器

基本定时器的作用 基本定时器&#xff0c;主要用于实现定时和计数功能。作用包括&#xff1a; 定时功能&#xff1a;可以产生周期性的中断&#xff0c;用于实现定时任务。例如&#xff0c;可以设置一个定时器每隔一定时间&#xff08;如1秒&#xff09;产生一次中断&#xff0…

状态码转文字!!!(表格数字转文字)

1、应用场景&#xff1a;在我们的数据库表中经常会有status这个字段&#xff0c;这个字段经常表示此类商品的状态&#xff0c;例如&#xff1a;0->删除&#xff0c;1->上架&#xff0c;0->下架&#xff0c;等等。 2、我们返回给前端数据时&#xff0c;如果在页面显示0…

【JavaWeb】

Javaweb 数据库相关概念MySQL数据库MySQL数据模型SQLDDL--操作数据库图形化客户端工具DML--操作数据DQL数据库约束 数据库设计多表查询事务 数据库相关概念 数据库 存储数据的仓库&#xff0c;数据是有组织的进行存储 英文&#xff1a;DataBase&#xff0c;简称DB 数据库管理系…

高维中介数据:基于交替方向乘子法(ADMM)的高维度单模态中介模型的参数估计(入门+实操)

全文摘要 用于高维度单模态中介模型的参数估计&#xff0c;采用交替方向乘子法&#xff08;ADMM&#xff09;进行计算。该包提供了确切独立筛选&#xff08;SIS&#xff09;功能来提高中介效应的敏感性和特异性&#xff0c;并支持Lasso、弹性网络、路径Lasso和网络约束惩罚等不…

详解:npm升级到pnpm对比优化点!!

npm3之前 依赖树层级过深&#xff0c;导致依赖路径过长并且相同依赖模块会被重复安装,占用电脑磁盘空间 npm3之后 修改为扁平化处理 算法复杂存在多项目间依赖相同副本的情况导致没有明确被依赖的包也可以直接引用&#xff0c;管理复杂 pnpm node_modules改成非扁平化结构&a…

uni-grid-item在小程序和APP中for循环不生效

<uni-grid-item v-for"(item, index) in list" :key"index"></uni-grid-item> 如上图类型的代码在H5是可以正常生效的 但是在小程序和APP中不生效&#xff0c;我也没有搜索到答案&#xff0c;但是我最后一个格子是固定的&#xff0c;我发现是…

ubuntu基础操作(1)-个人笔记

搜狗输入法Linux官网-首页搜狗输入法for linux—支持全拼、简拼、模糊音、云输入、皮肤、中英混输https://pinyin.sogou.com/linux 1.关闭sudo密码&#xff1a; 终端&#xff08;ctrl alt t&#xff09;输入 sudo visudo 打开visudo 找到 %sudo ALL(ALL:ALL) ALL 这一行…

【go从入门到精通】go基本类型和运算符用法

大家好&#xff0c;这是我给大家准备的新的一期专栏&#xff0c;专门讲golang&#xff0c;从入门到精通各种框架和中间件&#xff0c;工具类库&#xff0c;希望对go有兴趣的同学可以订阅此专栏。 --------------------------------------------------------------------------…

架构设计方法(4A架构)-应用架构

1、应用架构&#xff08;AA&#xff09;&#xff1a;业务价值与产品之间的桥梁&#xff0c;是企业架构的一个子集 2、应用架构包含“应用系统模块、应用服务、应用系统集成”3个关键要素 3、收集AS-IS应用架构&#xff0c;描绘现状&#xff0c;并识别改进机会点 4、描述对新系统…

xss.haozi:0x00

0x00没有什么过滤所以怎么写都没有关系有很多解 <script>alert(1)</script>

新书速览|Photoshop+CorelDRAW商业广告设计入门到精通:视频教学版

8章实例剖析商业案例&#xff0c;帮你提升设计效率。商业实战案例&#xff0c;真正掌握设计技能&#xff01; 本书内容 《PhotoshopCorelDRAW商业广告设计入门到精通&#xff1a;视频教学版》以创作精美、类型多样的案例&#xff0c;全面地讲解Photoshop与CorelDRAW软件相结合…

什么是五更泻及治疗方法

什么是五更泻 有些人总是在黎明之前肚脐周围的腹部疼痛发作&#xff0c;肚子咕咕作响&#xff0c;马上就想大便&#xff0c;拉出来的大便不成形&#xff0c;甚至有未消化的食物&#xff0c;便后会感觉舒服很多&#xff0c;还常伴有小腹冷痛、喜温、腰酸肢冷、舌淡苔白等症状。…

李沐动手学习深度学习——4.5练习

1. 在本节的估计问题中使用λ的值进行实验。绘制训练和测试精度关于λ的函数。观察到了什么&#xff1f; 修改代码运行如图所示&#xff0c;可以发现对于lamda值的变化而言&#xff0c;对于训练loss和测试loss的影响不大。但是如果λ 太大后&#xff0c;train和test的loss会变得…

欧拉回路(Eulerian Path)

1.定义 如果图 G G G(有向图或者无向图)中所有边一次仅且一次行遍所有顶点的通路称作欧拉通路。 如果图 G G G中所有边一次仅且一次行遍所有顶点的回路称作欧拉回路。 具有欧拉回路的图成为欧拉图(简称 E E E图)。具有欧拉通路但不具有欧拉回路的图成为半欧拉图。 顶点可以经…

【Docker】Windows11操作系统下安装、使用Docker保姆级教程

【Docker】Windows11操作系统下安装、使用Docker保姆级教程 大家好 我是寸铁&#x1f44a; 总结了一篇【Docker】Windows11操作系统下安装、使用Docker保姆级教程的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 什么是 Docker&#xff1f; Docker 是一个开源平台&…

yolov8-更换卷积模块-ContextGuidedBlock_Down

源码解读 class ContextGuidedBlock_Down(nn.Module):"""the size of feature map divided 2, (H,W,C)---->(H/2, W/2, 2C)"""def __init__(self, nIn, dilation_rate2, reduction16):"""args:nIn: the channel of input fea…

统信UOS及麒麟KYLINOS操作系统上如何切换键盘布局

原文链接&#xff1a;如何切换键盘布局 | 统信UOS | 麒麟KYLINOS Hello&#xff0c;大家好啊&#xff0c;最近有朋友在群里提到他的键盘输入“Y”会显示“Z”&#xff0c;输入“Z”会显示“Y”。这个问题听起来可能有些奇怪&#xff0c;但其实并不罕见。出现这种情况的原因&…

广东Lenovo SR588服务器维修升级硬盘内存

本案例描述了对联想SR588服务器进行硬件升级的过程&#xff0c;包括更换固态硬盘作为系统盘&#xff0c;以及增加内存容量至128GB。升级后&#xff0c;服务器性能得到显著提升&#xff0c;同时通过重新配置RAID阵列和操作系统的重新安装&#xff0c;确保了系统的稳定性和数据的…

RAC集群日常维护

RAC的启停 cd /u01/app/19.3.0/grid/bin 停止 ./crsctl stop crs 检查 ./crsctl check crs 启动&#xff0c;可以两个节点同时启动 ./crsctl start crs 检查 ./crsctl check crs ./crsctl status res -t oracle的RAC日常维命令 集群状态检查命令 cractl status res …

数字革命的浪潮:Web3如何改变一切

随着数字技术的不断发展&#xff0c;人类社会正迎来一场前所未有的数字革命浪潮。在这个浪潮中&#xff0c;Web3技术以其去中心化、安全、透明的特性&#xff0c;正在逐渐改变着我们的生活方式、商业模式以及社会结构。本文将深入探讨Web3技术如何改变一切&#xff0c;以及其所…