C++进阶--红黑树的实现

news2025/5/10 12:52:20

文章目录

  • 红黑树的实现
    • 红黑树的概念
      • 红黑树的规则
      • 红黑树的效率
    • 红黑树的实现
      • 红黑树的结构
      • 红黑树的插入
        • 变色+单旋(变色)+双旋(变色)
      • 红黑树的查找
      • 红黑树的验证
    • 总结:
    • 结语

很高兴和大家见面,给生活加点impetus!!开启今天的变成之路!!
在这里插入图片描述
今天我们来学习红黑树,重点了解是如何进行旋转的,即红黑树的插入接口
作者:٩( ‘ω’ )و260
我的专栏:C++进阶,C++初阶,数据结构初阶,题海探骊,c语言
欢迎点赞,关注!!

红黑树的实现

红黑树的概念

红黑树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表示结点的颜⾊,可以是红色或者黑色。通过对任何⼀条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有⼀条路径会比其他路径长出2倍,因而是接近平衡的

红黑树的规则

红黑树的规则主要是有四点:

每个结点不是红色就是黑色
根结点一定是黑色的
如果一个结点是红色的,那么他的孩子结点必须是黑色的,即一条路径上不会有连续的红结点
对于红黑树任意一个路径上,黑结点的个数是相同的

下面我们来看几个红黑树:
在这里插入图片描述
这几个二叉搜索树都是满足这四个规律的,这时,就为红黑树。

细节说明:第四点:每一条路径,这个路径并非到达叶子结点,即一条路径只要能够到达nullptr的话,这就算作是一条路径了

来看下图:
在这里插入图片描述
这个点稍不注意其实就会看错的,为了避免这种情况,在《算法导论》中提出了⼀条每个叶子结点(NIL)都是黑色的规则。这里的叶子结点并非叶子结点,而是空结点。NIL的存在是为了标明每一条路径,来看下图:
在这里插入图片描述
这里再来提问一个问题,红黑树是如何保证最长路径不超过最短路径的二倍的?
由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径就就是全是黑色结点的路径,假设最短路径长度为bh(black height)
由规则2和规则3可知,任意⼀条路径不会有连续的红色结点,所以极端场景下,最长的路径就是一黑一红间隔组成,那么最长路径的长度为2bh
综合红黑树的4点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在的。假设任意⼀条从根到NULL结点路径的长度为x,那么bh<=h<=2
bh。

综上:只要我们满足了红黑树的四点规则,就能够满足最长路径不超过最短路径的二倍

红黑树的效率

我们假设红黑树的结点个数为N,h为最短路径的长度那么2 h- 1 < = N < 22h -1。因为红黑树具备二叉搜索树的性质,也就是意味着红黑树增删查改最坏也就是走最长路径2*logN,所以红黑树的时间复杂度为O(logN)

红黑树与AVL树的区别:红黑树是通过结点颜色近似平衡,AVL树是通过高度差严格控制平衡。AVL的旋转次数肯定是多于红黑树的,而红黑树的高度肯定是多于AVL树。

解释:为什么这里h的最短路径长度算出来是这个呢?
当我们是最短路径的时候,我们假设这个为完全二叉树,当为最长路径的时候,也同理,这样就能够使用二叉树方面的知识了(即二叉树高度与结点个数的关系:N=2h -1)

来看下图:
在这里插入图片描述

红黑树的实现

首先红黑树是具备二叉搜索树的性质的,而且与AVL树的差别就是控制树的平衡的方式不同,所以在代码部分与AVL树的部分代码类似:

红黑树的结构

我们来定义红黑树的结构:

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;//主要是旋转的时候需要使用parent指针
	Colour _col;
	RBTreeNode(const pair<K,V>& kv)
		:_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED),_kv(kv)
	{}
tmplate<class K,class V>
class RBTree{//红黑树结构
public:
	typedef RBTreeNode<K,V> Node;
private:
	Node* _root=nullptr;
};
}

细节:为什么我们要将结点颜色初始化为红色?
当我们插入一个值,肯定是需要将这个值转换成一个结点,插入结点,如果插入一个黑色结点,就会影响红黑树的其他所有的分支,因为必须保证红黑树中每一条路径的黑色结点数量相同。所以为了不保证影响其他路径,插入的结点我们选择为红色!!

红黑树的插入

红黑树的插入的大致过程:
1:插入⼀个值按二叉搜索树规则进行插入,插入后我们只需要观察是否符合红黑树的4条规则
2:如果是空树插入,新增结点是黑色结点。如果是非空树插⼊,新增结点必须红色结点,因为非空树插⼊,新增黑色结点就破坏了规则4,规则4是很难维护的
3:非空树插⼊后,新增结点必须红色结点,如果父亲结点是黑色的,则没有违反任何规则,插入结束,因为此时并没有违反这四条规则
4:非空树插⼊后,新增结点必须红色结点,如果⽗亲结点是红色的,则违反规则3

此时我们应该怎么办呢?
主要分为一下几种方法来处理违法规则三的情况。

变色+单旋(变色)+双旋(变色)

我们再来分一下类别,其实标题的这三种方法还可以分类:
具体的处理方法要看uncle的情况:
uncle有三种情况:

1:存在且为红
2:存在且为黑
3:不存在

而2,3点可以归为一类,1归为一类具体来看下面的图示:
1:叔叔存在且为红
下图中假设我们把新增结点标识为c(cur),c的父亲标识为p(parent),p的父亲标识为g(grandfather),p的兄弟标识为u(uncle)。
在这里插入图片描述
当叔叔(uncle)存在且为红的时候,我们直接将p和u变黑,g变红,此时仍然满足红黑树的性质。
那么cur和parent分不分插入的位置呢?其实是不分的,即这种情况cur在左还是在右,parent在左还是在右,处理方法都是相同的。
来看下图:
在这里插入图片描述
下面这四种情况使用这一种方法都是可以解决的。

一种情况是叔叔存在且为红
c为红,p为红,g为黑,u存在且为红,则将p和u变黑,g变红。在把g当做新的c,继续往上更新。直到我们更新到根结点的位置。
只变色,不旋转。所以无论c是p的左还是右,p是g的左还是右,都是上面的变色处理方式。

分析:因为p和u都是红色,g是黑色,把p和u变黑,左右子树路径各增加⼀个黑色结点,g再变红,相当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题,需要继续往上更新是因为,g是红色,如果g的父亲还是红色,那么就还需要继续处理;如果g的父亲是黑色,则处理结束了;如果g就是整棵树的根,再把g变回黑色。因为一棵树的根结点必须是黑色。

这里我们来画出抽象展开图:
在这里插入图片描述
2:叔叔不存在或者说是叔叔存在且为黑
先来看图示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们先来总结一下:当叔叔存在且为黑或者叔叔不存在时,此时需要旋转,旋转就分为单旋或双旋:如果此时高的一遍呈现直线(单旋),如果高的一遍呈现曲线(双旋),即一种是左边高的左边高,一般是左边高的右边高(一种是一个方向一高到底,一种是两边都出现)

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增结点,u存在且为黑,则c⼀定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。
p必须变黑,才能解决,连续红色结点的问题,u不存在或者是黑色的,这里单纯的变色无法解决问题,需要旋转+变色
在这里插入图片描述

如果p是g的左,c是p的左,那么以g为旋转点进行右单旋,再把p变黑,g变红即可。p变成课这颗树新的根,这样⼦树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红⾊或者空都不违反规则
反之:
如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则
注意:这里我们都是一边高,接下来我们来看双旋的情形
在这里插入图片描述

如果p是g的左,c是p的右,那么先以p为旋转点进行左单旋,再以g为旋转点进行右单旋,再把c变黑,g变红即可。c变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为c的父亲是黑色还是红是或者空都不违反规则。
反之:
如果p是g的右,c是p的左,那么先以p为旋转点进行右单旋,再以g为旋转点进行左单旋,再把c变黑,g变红即可。c变成课这颗树新的根,这样⼦树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为c的⽗亲是黑色还是红色或者空都不违反规则

总结:如果是第一种情况(叔叔存在且为红),是需要继续向上更新的,但是第二种情况,可以直接结束循环,因为只要旋转之后,我们新的根一定是黑色的

接下来我们来进行代码实现:

bool Insert(const pair<K,V>& kv)
{
	if(_root==nullptr)
	{
		_root=new Node(kv);
		_root->_col=BLACK;//根结点一定是黑色的
	}
	//循环找到合适的位置放kv
	Node*cur=_root;
	Node*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->_col=RED;//插入的结点必须是红结点
	if(parent->_left==cur)
	{
		parent->_left=cur;
		cur->_parent=parent;
	}else{
		parent->_right=cur;
		cur->_parent=parent;
	}
	//此时需要看违背了红黑树的四条规则没,违反了就要进行处理操作
	while(parent&&parent->_col)//因为插入的结点是红色,连续红结点的话parent也是红色
	{
		Node*grandfather=parent->_parent;
		if(grandfather->_left==parent)
		{
			Node*uncle=grandfather->_right;
			if(uncle&&uncle->_col==RED)
			{
				uncle->_col=parent->_col=BLACK;
				grandfather->_col=RED;
				//继续往上遍历
				cur=grandfather;
				parent=cur->_parent;
			}
			else{//分单旋和双旋的情况
				if(cur==parent->_left)//单旋+变色
				{
					RotateR(grandfather);
					parent->_col=BLACK;//新的根
					grandfather->_col=RED;
				}
				else if(cur==parent->_right)//双旋+变色
				{
					RoteteL(parent);
					RotateR(grandfather);
					cur->_col=BLACK;//新的根
					grandfather->_col=RED;
				}
				break;//涉及的旋转,就要退出循环
			}
		}
		else//grandfather->_right==parent
		{
			Node*uncle=grandfather->_left;
			if(uncle&&uncle->_col==RED)
			{
				uncle->_col=parent->_col=BLACK;
				grandfather->_col=RED;
				//继续往上遍历
				cur=grandfather;
				parent=cur->_parent;
			}
			else{//分单旋和双旋的情况
				if(cur==parent->_right)//单旋+变色
				{
					RotateR(grandfather);
					parent->_col=BLACK;//新的根
					grandfather->_col=RED;
				}
				else if(cur==parent->_left)//双旋+变色
				{
					RoteteL(parent);
					RotateR(grandfather);
					cur->_col=BLACK;//新的根
					grandfather->_col=RED;
				}
				break;//涉及的旋转,就要退出循环
			}
		}
	}
	
	_root->_col=BALCK;//让新的根重新为黑结点
	return true;
}

到这里,二叉树的插入操作就讲解完啦,有关左旋与右旋的代码,在以后会出一个章节来讲解哦。

红黑树的查找

我们直接按照二叉搜索树的查找规则来进行即可,因为红黑树也是二叉搜索树。

	Node* Find(const K& key)//查找结点
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

这里的代码简单,不做过多讲解

红黑树的验证

红黑树的检验:我们只需要检查是否满足红黑树的四点要求即可。
关键是每一条路径上的黑结点相同,这个该怎么来检验呢?

我们可以先来遍历任意一条路径,拿这条参考路径上的黑结点与任意路径上的黑结点个数进行比较,我们不用改变链表结构,只用使用局部变量来传递一个递归参数即可

来看代码:

bool IsBalanceTree()
{
	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)
}
bool check(Node*root,int blackNum,int refNum)
{
	if(root==nullptr)//说明此时走完了一个路径
	{
		if(blackNum!=refNum) return false;//路径上的黑结点个数不同
		return true;
	}
	if(root->_col==RED)
	{
		if(root->parent->_col==RED) return false;//连续的红结点
	}
	if(root->_col==BLACK)
	{
		refNum++;
	}
	return check(root->_left,blackNum,refNum)&&check(root->_right,blackNum,refNum);
}

这里我们需要注意一个点,当我们遍历到红结点的时候,我们不用去管他的孩子,因为孩子有三种(不存在,存在且为红,存在且为黑),不好判断,我们直接判断他的父亲即可

红黑树的删除这里我们不做讲解。

总结:

今天学习了红黑树,从概念,规则,实现角度出发,核心是插入接口的实现,其次就是验证红黑树的思路,是通过叔叔的角度来分析是否旋转,还是说只是变色即可,旋转的时候需要分清位置关系,而且是单旋还是双旋。

结语

今天的内容就分享到这里,不足之处欢迎留言指正,感谢大家的支持!!
古之成大事者,不惟有超世之才,亦必有坚忍不拔之志!加油!!
在这里插入图片描述

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

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

相关文章

WPF之值转换器

文章目录 目录什么是值转换器IValueConverter接口Convert方法ConvertBack方法 创建和使用值转换器定义转换器类在XAML中使用转换器转换器参数&#xff08;ConverterParameter&#xff09; 常用转换器实现布尔值转可见性&#xff08;BoolToVisibilityConverter&#xff09;数值转…

qml中的TextArea使用QSyntaxHighlighter显示高亮语法

效果图&#xff0c;左侧显示行号&#xff0c;右侧用TextArea显示文本内容&#xff0c;并且语法高亮。 2025年5月8号更新 1、多行文本注释 多行文本注释跟普通的高亮规则代码不太一样&#xff0c;代码需要修改&#xff0c;这里以JavaScript举例。 先制定多行文本注释规则&…

Transformer编码器+SHAP分析,模型可解释创新表达!

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基本介绍 基于SHAP分析的特征选择和贡献度计算&#xff0c;Matlab2023b代码实现&#xff1b;基于MATLAB的SHAP可解释Transformer编码器回归模型&#xff0c;敏感性分析方法。 详细介绍 引言 在正向渗透&#xff08…

[特殊字符]适合母亲节的SVG模版[特殊字符]

宝藏模版 往期推荐&#xff08;点击阅读&#xff09;&#xff1a; 趣味效果&#xff5c;高大上&#xff5c;可爱风&#xff5c;年终总结I&#xff5c;年终总结II&#xff5c;循环特效&#xff5c;情人节I&#xff5c;情人节II&#xff5c;情人节IIII&#xff5c;妇女节I&…

浅蓝色调风格人像自拍Lr调色预设,手机滤镜PS+Lightroom预设下载!

调色教程 浅蓝色调风格人像自拍 Lr 调色是利用 Adobe Lightroom 软件针对人像自拍照进行后期处理的一种调色方式。它通过对照片的色彩、对比度、亮度等参数进行精细调整&#xff0c;将画面的主色调打造为清新、柔和的浅蓝色系&#xff0c;赋予人像自拍独特的清新、文艺风格&…

isp流程介绍(yuv格式阶段)

一、前言介绍 前面两章里面&#xff0c;已经分别讲解了在Raw和Rgb域里面&#xff0c;ISP的相关算法流程&#xff0c;从前面文章里面可以看到&#xff0c;在Raw和Rgb域里面&#xff0c;很多ISP算法操作&#xff0c;更像是属于sensor矫正或者说sensor标定操作。本质上来说&#x…

数巅智能携手北京昇腾创新中心深耕行业大模型应用

当前&#xff0c;AI技术正在加速向各行业深度渗透,成为驱动产业转型和社会经济发展的重要引擎。构建开放协作的AI应用生态体系、推动技术和应用深度融合&#xff0c;已成为行业发展的重要趋势。 近日&#xff0c;数巅智能与北京昇腾人工智能计算中心&#xff08;北京昇腾创新中…

【LangChain高级系列】LangGraph第一课

前言 我们今天直接通过一个langgraph的基础案例&#xff0c;来深入探索langgraph的核心概念和工作原理。 基本认识 LangGraph是一个用于构建具有LLMs的有状态、多角色应用程序的库&#xff0c;用于创建代理和多代理工作流。与其他LLM框架相比&#xff0c;它提供了以下核心优…

常见降维算法分析

一、常见的降维算法 LDA线性判别PCA主成分分析t-sne降维 二、降维算法原理 2.1 LDA 线性判别 原理 &#xff1a;LDA&#xff08;Linear Discriminant Analysis&#xff09;线性判别分析是一种有监督的降维方法。它的目标是找到一个投影方向&#xff0c;使得不同类别的数据在…

计算机二级(C语言)已过

非线性结构&#xff1a;树、图 链表和队列的结构特性不一样&#xff0c;链表可以在任何位置插入、删除&#xff0c;而队列只能在队尾入队、队头出队 对长度为n的线性表排序、在最坏情况下时间复杂度&#xff0c;二分查找为O(log2n)&#xff0c;顺序查找为O(n)&#xff0c;哈希查…

2025年3月,​韩先超对国网宁夏进行Python线下培训

大家好&#xff0c;我是韩先超&#xff01;在2025年3月3号和4号&#xff0c;为 宁夏国网 的运维团队进行了一场两天的 Python培训 &#xff0c;培训目标不仅是让大家学会Python编程&#xff0c;更是希望大家能够通过这门技术解决实际工作中的问题&#xff0c;提升工作效率。 对…

[计算机网络]物理层

文章目录 物理层的概述与功能传输介质双绞线:分类:应用领域: 同轴电缆&#xff1a;分类: 光纤&#xff1a;分类: 无线传输介质&#xff1a;无线电波微波&#xff1a;红外线&#xff1a;激光&#xff1a; 物理层设备中继器(Repeater)&#xff1a;放大器&#xff1a;集线器(Hub)&…

幂等操作及处理措施

利用token模式去避免幂等操作 按以上图所示&#xff0c;除了token,应该也可以把传入的参数用MD5加密&#xff0c;当成key放入redis里面&#xff0c;业务执行完后再删除这个key.如还没有执行完&#xff0c;则请不要重复操作。纯属个人理解

Matlab 数控车床进给系统的建模与仿真

1、内容简介 Matlab217-数控车床进给系统的建模与仿真 可以交流、咨询、答疑 2、内容说明 略 摘 要:为提高数控车床的加工精度,对数控 车床进给系统中影响加工精度的主要因素进行了仿真分析研 动系统的数学模型,利用MATLAB软件中的动态仿真工具 究:依据机械动力学原理建立了…

低成本自动化改造的18个技术锚点深度解析

执行摘要 本文旨在深入剖析四项关键的低成本自动化技术&#xff0c;这些技术为工业转型提供了显著的运营和经济效益。文章将提供实用且深入的指导&#xff0c;涵盖老旧设备联网、AGV车队优化、空压机系统智能能耗管控以及此类项目投资回报率&#xff08;ROI&#xff09;的严谨…

我国脑机接口市场规模将破38亿元,医疗领域成关键突破口

当人类仅凭"意念"就能操控无人机编队飞行&#xff0c;当瘫痪患者通过"脑控"重新站立行走&#xff0c;这些曾只存在于科幻电影的场景&#xff0c;如今正通过脑机接口技术变为现实。作为"十四五"规划中重点发展的前沿科技&#xff0c;我国脑机接口…

Edu教育邮箱申请成功下号

这里是第2部分 如你所见&#xff0c;我根本就没有考虑流量的问题&#xff0c; 如果你有幸看到前面的内容&#xff0c;相信你能自己找到这个后续。

【Linux进程控制一】进程的终止和等待

【Linux进程控制一】进程的终止和等待 一、进程终止1.main函数的return2.strerror函数3.库函数exit4.系统调用_exit和库函数exit的区别5.异常信号6.变量errno 二、进程等待1.什么是进程等待&#xff1f;2.wait接口3.status4.waitpid接口 一、进程终止 1.main函数的return 写C…

今日行情明日机会——20250509

上证指数今天缩量&#xff0c;整体跌多涨少&#xff0c;走势处于日线短期的高位~ 深证指数今天缩量小级别震荡&#xff0c;大盘股表现更好~ 2025年5月9日涨停股主要行业方向分析 一、核心主线方向 服装家纺&#xff08;消费复苏出口链驱动&#xff09; • 涨停家数&#xf…

单片机-STM32部分:10、串口UART

飞书文档https://x509p6c8to.feishu.cn/wiki/W7ZGwKJCeiGjqmkvTpJcjT2HnNf 串口说明 电平标准是数据1和数据0的表达方式&#xff0c;是传输线缆中人为规定的电压与数据的对应关系&#xff0c;串口常用的电平标准有如下三种&#xff1a; TTL电平&#xff1a;3.3V或5V表示1&am…