C++进阶 | [4.3] 红黑树

news2024/12/26 16:36:21

摘要:什么是红黑树,模拟实现红黑树

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

1. 红黑树的性质

注意:红黑树是一种搜索二叉树。性质如下:

① 红黑树的根节点为 Black ;

Red 节点不连续,即 Red 节点一定有 Black 的孩子节点;

③ 每条路径含有相同数量的 Black 节点;(关于这点下文会详细解释)

④ 每个节点要么是 Black 的要么是 Red 的;

nullptr 节点为 Black(在红黑树中这样的节点被称为NIL节点,这样是为了方便找准路径,非硬性要求)

  • 路径 - path从根节点到空,称为一条路径。
    最短路径:如果一条路径的所有节点均为 Black ,则该路径为最短路径;
    最长路径: Black Red Black Red Black Red Black ……Red NIL 这样总是黑红节点相间的路径为最长路径。(注意:路径的结尾为空节点,而空节点一定是 Black 的,又因为红黑树的每条路径含有数量相同的 Black 节点,因此可得最长路径=最短路径×2-1

如图所示,path1和path2为该红黑树的最短路径;path8为该红黑树的最长路径。红黑树中的叶节点为特殊的叶节点,即 nullptr 节点,在此图中写作“NIL”。
与AVL树比较,AVL树是更接近满二叉树的严格平衡,而红黑树是近似平衡。


2. 模拟实现红黑树

1)框架

同样的,先定义红黑树的节点。根据红黑树对节点的“颜色”要求,这里采用枚举(enum)类型来表达节点的“颜色”。示例代码如下。

ps.默认新创建的节点为红色(下文会解释这样做的原因)。

enum Colour
{
	RED, BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(T data = T())
		:_data(data)
		, _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _col(RED)//默认新创建的节点都是红色的
	{}
	T _data;
	RBTreeNode<T>* _pLeft;
	RBTreeNode<T>* _pRight;
	RBTreeNode<T>* _pParent;
	Colour _col;
};

// 模拟实现红黑树的插入--注意:为了后序封装map和set,在实现时给红黑树多增加了一个头结点
template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree()
	{
		_pHead = new Node;
		_pHead->_pLeft = _pHead;
		_pHead->_pRight = _pHead;
	}

private:
	Node* _pHead;
};

2)insert

红黑树的插入函数实现的思路同AVL树是类似的。

-🟡Part1.插入新节点-   红黑树首先是一个BST,先按BST的规则插入新节点。代码如下。注意:当我们插入新的节点时,总是默认新插入的节点为红色。理由是:如果在红黑树中插入黑色节点将对整棵红黑树造成影响,因为红黑树要求每条路径上含有数量相同的黑色节点,一旦插入黑色节点必然要应对红黑树的调整,而插入红色节点,若该新节点的parent节点不为红色节点则不用调整这棵红黑树。

// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false.注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
	if (_pHead->_pLeft == _pHead && _pHead->_pRight == _pHead)
	{
		Node* _pNewNode = new Node(data);
		_pHead->_pLeft = _pHead->_pRight = _pNewNode;
		_pNewNode->_pParent = _pHead;
		_pNewNode->_col = BLACK;//跟节点为黑色
		return true;
	}

	Node* pCur = _pHead->_pLeft;
	Node* pParent = pCur->_pParent;
	while (pCur)
	{
		pParent = pCur;
		if (pCur->_data > data)
		{
			pCur = pCur->_pLeft;
		}
		else if (pCur->_data < data)
		{
			pCur = pCur->_pRight;
		}
		else
		{
			return false;
		}
	}
	Node* _pNewNode = new Node(data);
	if (pParent->_data > data)
		pParent->_pLeft = _pNewNode;
	else
		pParent->_pRight = _pNewNode;
	_pNewNode->_pParent = pParent;

	//……
	
	return true;
}

-🟡Part2.调整红黑树-  由于插入的新节点默认为红色,因此当插入导致出现连续的红色节点时,需要调整红黑树。 

如上图。下一步,继续对圈出的subtree进行展开:👇

  • 情况一:当圈出的subtree的根节点 psibling 为红色,这种情况不需要旋转,直接调整节点颜色即可。从下图的换色中不难看出,这样换色之后,每个路径的黑色节点数量不受影响。注意:当 pGrandparent 变为红色后,可能会与 其parent 形成连续的红色节点,因此需要继续向上操作,即令 pCur = pGrandparent(赋值操作),继续判断,并在必要的情况下调整。(这个“向上的”思路和AVL树的调整是类似的)
    ps.pParent 不一定是 pGrandparent 的节点,pCur 不一定就是 pParent 的节点。这里之所以分析下图中的这一种情况是因为,这类情况的调整不需要旋转,即直接调整节点颜色——pParent和psibling都变红,因此这两个节点相对于pGrandparent的左右位置不重要,pCur不做改变,因此pCur相对于pParent的左右位置也不重要。但需要旋转调整的情况就不一样了,pParent 、pGrandparent 、pCur 这三个节点之间的关系决定了旋转的方向,下面“情况二”会详细讲解。

  • 情况二:当圈出的subtree根节点为黑色,这种情况需要旋转+调色,下面对这种情况继续细分。👇
    ps.这棵被圈出的 subtree 黑色节点数目为n,在情况二这种情况下,psibing已经是一个黑色节点了,因此其子树黑色节点为n-1,如图中所写。(n≥1) 另外,注意 psibing 可能为NIL,即黑色的空节点。

    如下图,第三列表示调色后的结果,调色思路可以归纳为——旋转后的树的新根变为黑色,旋转前的树的旧根变为红色。

示例代码:

{//RBTree
	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false.注意:为了简单起见,本次实现红黑树不存储重复性元素
	bool Insert(const T& data)
	{
		//前面插入新节点的部分省略

		///新插入的节点默认是红色的,因此若在这条含有新插入节点的路径中出现·连·续·红·色·节点则需要调整///
		pCur = _pNewNode;//以新插入的结点为起点向上调整
		while (pCur->_col == RED && pParent->_col == RED)
		{
			Node* pGrandparent = pParent->_pParent;
			Node* pP_sibling = pGrandparent->_pLeft == pParent ? pGrandparent->_pRight : pGrandparent->_pLeft;
			if (pP_sibling && pP_sibling->_col == RED)//调色
			{
				pParent->_col = pP_sibling->_col = BLACK;
				if (pGrandparent != _pHead->_pLeft)
				{
					pGrandparent->_col = RED;
					pCur = pGrandparent;
					pParent = pCur->_pParent;
				}
			}
			else//旋转+调色
			{
				if (pCur == pParent->_pLeft)
				{
					if (pParent == pGrandparent->_pLeft)//右旋
					{
						RotateR(pGrandparent);
						pParent->_col = BLACK;
					}
					else//右左双旋
					{
						RotateR(pParent);
						RotateL(pGrandparent);
						//双旋后pCur成为新的subroot
						pCur->_col = BLACK;
					}
					pGrandparent->_col = RED;
				}
				else if (pCur == pParent->_pRight)
				{
					if (pParent == pGrandparent->_pRight)//左旋
					{
						RotateL(pGrandparent);
						//左旋后pParent成为新的subroot
						pParent->_col = BLACK;
					}
					else//左右双旋
					{
						RotateL(pParent);
						RotateR(pGrandparent);
						pCur->_col = BLACK;
					}
					pGrandparent->_col = RED;
				}
			}
		}
		return true;
	}

private:
	// 左单旋
	void RotateL(Node* subroot)
	{
		Node* pGrandparent = subroot->_pParent;
		Node* subR = subroot->_pRight;
		Node* subR_L = subR->_pLeft;

		if (pGrandparent == _pHead)
		{
			pGrandparent->_pLeft = pGrandparent->_pRight = subR;
		}
		else
		{
			if (subroot == pGrandparent->_pLeft)
			{
				pGrandparent->_pLeft = subR;
			}
			else
				pGrandparent->_pRight = subR;
		}
		subR->_pParent = pGrandparent;

		subR->_pLeft = subroot;
		subroot->_pParent = subR;

		subroot->_pRight = subR_L;
		if (subR_L)
			subR_L->_pParent = subroot;
	}
	// 右单旋
	void RotateR(Node* subroot)
	{
		Node* pGrandparent = subroot->_pParent;
		Node* subL = subroot->_pLeft;
		Node* subL_R = subL->_pRight;

		if (pGrandparent == _pHead)
		{
			pGrandparent->_pLeft = pGrandparent->_pRight = subL;
		}
		else
		{
			if (subroot == pGrandparent->_pLeft)
			{
				pGrandparent->_pLeft = subL;
			}
			else
				pGrandparent->_pRight = subL;
		}
		subL->_pParent = pGrandparent;

		subroot->_pLeft = subL_R;
		if (subL_R)
			subL_R->_pParent = subroot;

		subL->_pRight = subroot;
		subroot->_pParent = subL;
	}
};

ps.psibling 这个节点是有可能为空结点的,写旋转函数的时候要注意!

3)判断红黑树的合法性

要判断红黑树是否合法就需要比对着红黑树的性质一条一条来确认:

① 红黑树的根节点为 Black ;(⭕需要去判断树的根节点的颜色

Red 节点不连续,即 Red 节点一定有 Black 的孩子节点;(⭕需要验证

③ 每条路径含有相同数量的 Black 节点;(⭕需要验证

④ 每个节点要么是 Black 的要么是 Red 的;(✅通过enum类型来确保节点黑或红且无其他颜色可能

nullptr 节点为 Black。(✅这条显然不必验证

思路:选择一条路径并的到这条路径的黑色节点数量,并这条路径的黑色节点数量为标准,判断其他任以路径的黑色节点数量是否与其相等。简单起见,选择最左路径的黑色节点数量为标准数量去比对别的路径的黑色节点数量。

由上,关键问题在于如何计算出所有路径(除选择的标准路径)的外的黑色节点。以下图为例进行分析。

代码示例: 

// 检测红黑树是否为有效的红黑树
bool IsValidRBTRee()
{
	Node* pCur = _pHead->_pLeft;
	if (pCur == nullptr)
		return true;
	if (pCur->_col == RED)
		return false;
	size_t blackNUM = 0;
	while (pCur)
	{
		if (pCur->_col == BLACK)
			++blackNUM;
		pCur = pCur->_pLeft;
	}
	++blackNUM;//算上最后的空节点
	size_t pathblack = 0;
	return _IsValidRBTRee(_pHead->_pLeft, blackNUM, pathblack);
}

bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{
	if (pRoot == nullptr)
	{
		++pathBlack;//算上最后的空节点
		if (blackCount == pathBlack)
			return true;
		else
			return false;
	}

	if (pRoot->_col == BLACK)
	{
		++pathBlack;
		return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)
			&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
	}
	else
	{
		if (
			(pRoot->_pLeft && pRoot->_pLeft->_col == RED)
			|| (pRoot->_pRight && pRoot->_pRight->_col == RED)
			)
		{
			cout << "错误:存在连续的红色节点" << "->" << pRoot->_data << "->该节点附近存在连续红色节点" << endl;
			return false;
		}
		else
		{
			return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)
				&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
		}
	}
}

END

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

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

相关文章

创建XCOM窗体和跳转连接

Xcom 窗体&#xff1a; (groupBox组合框&#xff0c;comboBox下拉框) xcom代码&#xff1a; namespace _01_作业 {// 1kb 1024B 1200B// 1MB public partial class Form1 : Form{public List<string> botelv new List<string> { "600","1200&…

基于Tools体验NLP编程的魅力

大模型能理解自然语言&#xff0c;从而能解决问题&#xff0c;但是就像人类大脑一样&#xff0c;大脑只能发送指令&#xff0c;实际行动得靠四肢&#xff0c;所以LangChain4j提供的Tools机制就是大模型的四肢。 大模型的不足 大模型在解决问题时&#xff0c;是基于互联网上很…

图像大模型中的注意力和因果掩码

AIM — 图像领域中 LLM 的对应物。尽管 iGPT 已经存在 2 年多了&#xff0c;但自回归尚未得到充分探索。在本文中&#xff0c;作者表明&#xff0c;当使用 AIM 对网络进行预训练时&#xff0c;一组图像数据集上的下游任务的平均准确率会随着数据和参数的增加而线性增加。 要运…

Android 大话binder通信

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章 由于 Android 大话binder通信(上) 和 Android 大话binder通信(下) 分为两篇阅读体验不好&#xff0c;顾合并为一篇。 本文摘要 用故事的方式把binder通信的整个过程都描述…

机械原理介绍

机械原理介绍 1 介绍1.1 概述1.2 资料书籍在线资料 2 [机械原理知识整理](https://tomm.muzing.top/) 【muzing整理编写】1 绪论2 机构的结构分析2-2 机构的组成及分类2-3 机构运动简图2-4 机构具有确定运动的条件及最小阻力定律2-5 2-6 机构自由度的计算2-7 平面机构的组成原理…

【深度学习】图生图img3img论文原理,SD EDIT

https://arxiv.org/abs/2108.01073 摘要 引导图像合成技术使普通用户能够以最小的努力创建和编辑逼真的图像。关键挑战在于平衡对用户输入&#xff08;例如&#xff0c;手绘的彩色笔画&#xff09;的忠实度和合成图像的真实感。现有的基于GAN的方法试图通过使用条件GAN或GAN反…

64.WEB渗透测试-信息收集- WAF、框架组件识别(4)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;63.WEB渗透测试-信息收集- WAF、框架组件识别&#xff08;3&#xff09;-CSDN博客 我们在…

面经-计算机基础

1.计算机⽹络 1.1 OSI与TCP/IP各层的结构与功能,都有哪些协议? 计算机网络体系结构 应⽤层 应⽤层 (application-layer &#xff09;的任务是通过应⽤进程间的交互来完成特定⽹络应⽤。 应⽤层协议定 义的是应⽤进程&#xff08;进程&#xff1a;主机中正在运⾏的程序&…

便签 Pro(Mac 智能便签工具)专业版怎么样,值得购买吗?

使用 Mac 的小伙伴平时都是怎么记录工作生活中的碎片信息&#xff1f;用聊天软件&#xff0c;还是系统备忘录呢&#xff1f; 实际体验下来&#xff0c;其实都难以称得上好用。 赶紧来了解一下 Mac 多彩思维速记工具便签 Pro&#xff01;拥有智能边框大小、iCloud 同步、历史记…

昇思25天学习打卡营第1天|MindSpore 全流程操作指南

目录 MindSpore 库相关操作的导入指南 处理数据集 网络构建 模型训练 保存模型 加载模型 MindSpore 库相关操作的导入指南 首先&#xff0c;我们导入了 MindSpore 这个库的整个模块。然后&#xff0c;从 MindSpore 库中引入了 nn 模块&#xff0c;一般来说&#xff0c;它是…

JavaEE—什么是服务器?以及Tomcat安装到如何集成到IDEA中?

目录 ▐ 前言 ▐ JavaEE是指什么? ▐ 什么是服务器&#xff1f; ▐ Tomcat安装教程 * 修改服务端口号 ▐ 将Tomcat集成到IDEA中 ▐ 测试 ▐ 结语 ▐ 前言 至此&#xff0c;这半年来我已经完成了JavaSE&#xff0c;Mysql数据库&#xff0c;以及Web前端知识的学习了&am…

ROS2在rviz2中实时显示轨迹和点

本文是将《ROS在rviz中实时显示轨迹和点》博客中rviz轨迹显示转为ROS2环境中的rviz2显示。 ros2的工作空间创建这里就不展示了。 包的创建 ros2 pkg create --build-type ament_cmake showpath --dependencies rclcpp nav_msgs geometry_msgs tf2_geometry_msgsshowpath.cpp…

【微服务网关——Websocket代理】

1.Websocket协议与原理 1.1 连接建立协议 1.1.1 客户端发起连接请求 客户端通过 HTTP 请求发起 WebSocket 连接。以下是一个 WebSocket 握手请求的例子&#xff1a; GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key…

python 中的 下划线_ 是啥意思

在 Python 中&#xff0c;_&#xff08;下划线&#xff09;通常用作占位符&#xff0c;表示一个变量名&#xff0c;但程序中不会实际使用这个变量的值。 目录 忽略循环变量&#xff1a;忽略函数返回值&#xff1a;在解释器中使用&#xff1a;举例子1. 忽略循环变量2. 忽略不需…

APP逆向 day8 JAVA基础3

一.前言 昨天我们讲了点java基础2.0&#xff0c;发现是又臭又长&#xff0c;今天就是java基础的最后一章&#xff0c;也就是最难的&#xff0c;面向对象。上一末尾也是提到了面向对象&#xff0c;但是面向对象是最重要的&#xff0c;怎么可能只有这么短呢&#xff1f;所以今天…

怎样将word默认Microsoft Office,而不是WPS

设置——>应用——>默认应用——>选择"word"——>将doc和docx都选择Microsoft Word即可

【嵌入式DIY实例】- LCD ST7735显示DHT11传感器数据

LCD ST7735显示DHT11传感器数据 文章目录 LCD ST7735显示DHT11传感器数据1、硬件准备与接线2、代码实现本文介绍如何将 ESP8266 NodeMCU 板 (ESP-12E) 与 DHT11 (RHT01) 数字湿度和温度传感器连接。 NodeMCU 从 DHT11 传感器读取温度(以 C 为单位)和湿度(以 rH% 为单位)值,…

连锁品牌如何做宣传?短视频矩阵工具助轻松千万流量曝光!

今天给大家分享一家烘焙行业连锁品牌&#xff08;可可同学&#xff09;&#xff0c;通过小魔推获得了1031万的话题曝光&#xff0c;旗下的连锁门店登顶同城人气榜单第一名&#xff0c;​让自己的流量和销量获得双增长 01 品牌连锁店如何赋能旗下门店&#xff1f; 作为一家全国…

昇思25天学习打卡营第13天|基于MobileNetV2的垃圾分类

MobileNetv2模型原理介绍 相比于传统的卷积神经网络&#xff0c;MobileNet网络使用深度可分离卷积&#xff08;Depthwise Separable Convolution&#xff09;的思想在准确率小幅度降低的前提下&#xff0c;大大减小了模型参数与运算量。并引入宽度系数α和分辨率系数β使模型满…