C++——红黑树(带头结点)

news2024/10/25 9:05:48

红黑树

  • 红黑树的概念
    • 红黑树的定义
    • 红黑树的性质
    • 红黑树的优点
    • 操作原理
    • 例图:
  • 红黑树的实现
    • 红黑树的框架
    • 红黑树的插入实现
      • 头结点的作用
      • 红黑树的插入步骤(简易理解版带图)
    • 红黑树的插入具体代码详解
    • 红黑树的旋转代码
    • 红黑树的查验

红黑树的概念

红黑树的定义

红黑树是一种自平衡的二叉搜索树,具有以下几个重要特性和规则,确保其在插入、删除和查找操作中的时间复杂度保持在 (O(\log n))。

红黑树的基本概念

节点颜色:每个节点都有一个颜色属性,可以是红色或黑色。

节点结构:每个节点除了存储数据,还包含指向左右子节点和父节点的指针,以及颜色信息。

红黑树的性质

红黑树遵循以下五个性质:

节点颜色

  • 每个节点要么是红色,要么是黑色。

根节点

  • 根节点始终是黑色。

叶子节点

  • 所有的叶子节点(NIL节点,也称为哨兵节点)都是黑色。

红色节点的性质

  • 如果一个节点是红色,则其两个子节点必须是黑色(即没有两个连续的红色节点)。

黑色高度

  • 从任何节点到其每个叶子节点的所有路径都包含相同数量的黑色节点,这个数量称为该节点的“黑色高度”。

红黑树的优点

平衡性:由于上述性质,红黑树保持了相对平衡,能够在最坏情况下保证操作的时间复杂度为 (O(\log n))。

灵活性:红黑树允许插入和删除操作的灵活性,同时不会导致树的不平衡。

操作原理

在红黑树中进行插入和删除操作时,可能会违反上述性质,需要通过旋转和重新着色来恢复这些性质。主要操作包括:

旋转

  • 左旋和右旋是两种基本操作,用于调整树的结构,以保持平衡。

重新着色

  • 在插入或删除后,通过改变节点的颜色来恢复红黑树的性质。

例图:

在这里插入图片描述

红黑树的实现

红黑树的框架

红黑树的框架主要包括颜色, 节点,和树的封装三个部分,分开写方便调整
//颜色
enum Color
{
	Black,
	Red
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data = T(), Color col = Red)
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _col(col)
	{}

	RBTreeNode<T>* _pLeft;
	RBTreeNode<T>* _pRight;
	RBTreeNode<T>* _pParent;
	T _data;
	Color _col;   // 节点的颜色
};


template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree()
	{
		_pHead = new Node;
		_pHead->_pLeft = _pHead;
		_pHead->_pRight = _pHead;
	}
	 // 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
    // 本次实现红黑树不存储重复性元素
	bool Insert(const T& data);
    
    // 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
    Node* Find(const T& data);
    
    // 获取红黑树最左侧节点
	Node* LeftMost();
    
    // 获取红黑树最右侧节点
	Node* RightMost();
    
    // 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee();
private:
	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack);
    // 左单旋
	void RotateL(Node* par);
    // 右单旋
	void RotateR(Node* par);
    // 为了操作树简单起见:获取根节点
	Node*& GetRoot();
private:
	Node* _pHead;
}

红黑树的插入实现

头结点的作用

头结点并不是真正的根节点,而是作为一个边界条件,简化了边界检查的情况,可以使所有叶子节点指向它,用它的父节点指向真正的根节点

  1. 哨兵节点的功能
  • 简化边界条件处理:使用头节点作为哨兵节点,使得所有叶子节点都指向它。这意味着在树的遍历和操作中,不需要单独处理空指针或叶子节点的情况,从而简化了许多逻辑。
  1. 统一树结构
  • 保持平衡性:头节点作为树的根节点的一部分,可以确保树始终保持一个一致的结构,特别是在插入和删除操作时。每个节点都会有两个子节点(左和右),即使是最底层的节点也会指向头节点。
  1. 提升性能
  • 减少比较和判断:在查找、插入等操作中,直接返回头节点可以避免多次空指针检查,提高了操作效率。例如,在查找最小或最大节点时,可以直接从根节点开始,直到遇到头节点为止。
  1. 辅助树的验证
  • 有效性检查:在进行红黑树性质验证时,头节点可以作为一个起始点,帮助检查整个树是否满足红黑树的性质,如路径上的黑色节点数量相同等。
  1. 支持其他操作
  • 提供根节点访问:通过 _pHead,可以方便地访问树的根节点。这对于旋转操作、插入、删除等都是必需的。

红黑树的插入步骤(简易理解版带图)

关于二叉树的旋转详情请看下面这篇文章,此文中不会详细讲解旋转
AVL树

红黑树的插入过程涉及多个步骤,以确保树的性质得到维护。下面是详细的插入步骤:

  1. 插入新节点
  • 将待插入的节点作为红色节点插入到树中(遵循普通二叉搜索树的插入规则)。
    新节点的初始颜色设置为红色。
  1. 调整树的结构
  • 在插入完成后,可能会违反红黑树的性质,因此需要进行调整。主要需要检查以下情况,并进行修正:

2.1 父节点是黑色

  • 如果新节点的父节点是黑色,则树的性质没有被破坏,无需进一步调整。

在这里插入图片描述

2.2 父节点是红色

  • 如果新节点的父节点是红色,那么就会出现两个连续的红色节点(违反了红黑树的性质),此时需要进行调整。

在这里插入图片描述

  1. 根据叔叔节点的颜色进行调整
  • 找到新节点的祖父节点。

根据叔叔节点(父节点的兄弟)的颜色,分为以下几种情况:

3.1 叔叔节点是红色

在这里插入图片描述

  • 将父节点和叔叔节点都变为黑色。
  • 将祖父节点变为红色。
  • 将新节点指向祖父节点,重复检查祖父节点。(向上循环检查)
    在这里插入图片描述

3.2 叔叔节点是黑色或不存在

在这里插入图片描述

  • 根据新节点的位置(左子树或右子树),进行相应的旋转操作。
    3.2.1 左-左情况(LL)
    在这里插入图片描述

  • 如果新节点是父节点的左子节点,执行右旋转(将祖父节点旋转到右边)。
    在这里插入图片描述

3.2.2 右-右情况(RR)
在这里插入图片描述

  • 如果新节点是父节点的右子节点,执行左旋转(将祖父节点旋转到左边)。
    在这里插入图片描述

3.2.3 左右情况(LR)
在这里插入图片描述

  • 如果新节点是父节点的右子节点且父节点是祖父节点的左子节点,先对父节点执行左旋转,然后再对祖父节点执行右旋转。

在这里插入图片描述

3.2.4 右左情况(RL)

在这里插入图片描述

  • 如果新节点是父节点的左子节点且父节点是祖父节点的右子节点,先对父节点执行右旋转,然后再对祖父节点执行左旋转。
    在这里插入图片描述
  1. 将根节点设为黑色
  • 在进行完所有调整后,确保根节点的颜色为黑色,以保持红黑树的性质。

以上图都是简单版本,方便理解,加上子节点等也是差不多的,除此之外还需要考虑根节点改变的情况

红黑树的插入具体代码详解

1, 首先处理根节点插入的情况, GetRoot()函数获取真正的根节点指针的引用。

bool Insert(const T& data)
{
	Node*& proot = GetRoot();
	Node* newnode = new Node(data);
	if (proot == nullptr)
	{
		proot = newnode;
		proot->_col = Black;
		proot ->_pParent = _pHead;
		return true;
	}
	
}

2,处理已经有根节点的情况,找寻位置,并插入节点,注意链接父节点

bool Insert(const T& data)
{
	Node*& proot = GetRoot();
	Node* newnode = new Node(data);
	if (proot == nullptr)
	{
		proot = newnode;
		proot->_col = Black;
		proot ->_pParent = _pHead;
		return true;
	}

	Node* cur = proot;
	Node* par = nullptr;

	while (cur)
	{
		par = cur;
		if (cur->_data > data)
		{
			cur = cur->_pLeft;
		}
		else if (cur->_data < data)
		{
			cur = cur->_pRight;
		}
		else
		{
			delete newnode;
			return false;
		}
	}

	if (par->_data > data)
	{
		par->_pLeft = newnode;
	}
	else
	{
		par->_pRight = newnode;
	}

	cur = newnode;
	cur->_pParent = par;
}
	

3, 判断是否需要变色或者旋转,这时候需要祖父节点和叔叔节点,根据以上讲解的情况分情况处理。

此时要注意边界检查和死循环, 旋转之后要结束循环,还要记得变色。一下代码主要分为有叔叔节点且为红色和另一种情况处理,在此之下在细分。
bool Insert(const T& data)
{
	Node*& proot = GetRoot();
	Node* newnode = new Node(data);
	if (proot == nullptr)
	{
		proot = newnode;
		proot->_col = Black;
		proot ->_pParent = _pHead;
		return true;
	}

	Node* cur = proot;
	Node* par = nullptr;

	while (cur)
	{
		par = cur;
		if (cur->_data > data)
		{
			cur = cur->_pLeft;
		}
		else if (cur->_data < data)
		{
			cur = cur->_pRight;
		}
		else
		{
			delete newnode;
			return false;
		}
	}

	if (par->_data > data)
	{
		par->_pLeft = newnode;
	}
	else
	{
		par->_pRight = newnode;
	}

	cur = newnode;
	cur->_pParent = par;
	
	Node* ppar = par->_pParent;
	Node* uncle = (ppar->_pLeft == par) ? ppar->_pRight : ppar->_pLeft;
	while (par != _pHead && par->_col == Red)
	{
		if (uncle && uncle->_col == Red)
		{
			//    pp
			//  p    u
			//   c
			//叔叔节点存在并且为红色,改变颜色,不需要旋转
			par->_col = Black;
			uncle->_col = Black;
			ppar->_col = Red;

			//改变现在节点
			cur = ppar;
			par = cur->_pParent;
			ppar = par->_pParent;
			uncle = (ppar->_pLeft == par) ? ppar->_pRight : ppar->_pLeft;
		}
		else
		{
			//叔叔节点不存在或者为黑色,需要旋转
			// ll型
			//     pp
			//   p
			//  c
			if (par->_pLeft == cur && ppar->_pLeft == par)
			{
				RotateR(ppar);
				par->_col = Black;
				ppar->_col = Red;
				break;
			}
			// lr型
			//     pp
			//   p
			//     c
			else if (ppar->_pLeft == par && par->_pRight == cur)
			{
				RotateL(par);
				RotateR(ppar);

				cur->_col = Black;
				ppar->_col = Red;
				break;
			}
			// rr型
			//     pp
			//		    p
			//			    c
			else if (ppar->_pRight == par && par->_pRight == cur)
			{
				RotateL(ppar);

				par->_col = Black;
				ppar->_col = Red;
				break;
			}
			// rl型
			//     pp
			//		    p
			//		  c
			else if (ppar->_pRight == par && par->_pLeft == cur)
			{
				RotateR(par);
				RotateL(ppar);

				cur->_col = Black;
				ppar->_col = Red;
				break;
			}
		}
	}

	GetRoot()->_col = Black;
	return true;
}

红黑树的旋转代码

详细讲解请看上一篇AVL树的文章

// 左单旋
void RotateL(Node* par)
{
	assert(par);
	Node*& proot = GetRoot();
	Node* ppar = par->_pParent;
	Node* rchild = par->_pRight;
	Node* rlchild = rchild->_pLeft;

	rchild->_pLeft = par;
	par->_pRight = rlchild;

	rchild->_pParent = ppar;
	par->_pParent = rchild;
	if (rlchild)
		rlchild->_pParent = par;

	if (par == proot)
	{
		proot = rchild;
		rchild->_pParent = _pHead;
	}
	else
	{
		if (ppar->_pLeft == par)
		{
			ppar->_pLeft = rchild;
		}
		else
		{
			ppar->_pRight = rchild;
		}
	}
}
// 右单旋
void RotateR(Node* par)
{
	assert(par);
	//    p
	//  l
	//n	   lr
	Node*& proot = GetRoot();
	Node* ppar = par->_pParent;
	Node* lchild = par->_pLeft;
	Node* lrchild = lchild->_pRight;

	lchild->_pRight = par;
	par->_pLeft = lrchild;
	//    l
	// l     p
	//    lr

	lchild->_pParent = ppar;
	par->_pParent = lchild;
	if (lrchild)
		lrchild->_pParent = par;

	if (par == proot)
	{
		proot = lchild;
		lchild->_pParent = _pHead;
	}
	else
	{
		if (ppar->_pLeft == par)
		{
			ppar->_pLeft = lchild;
		}
		else
		{
			ppar->_pRight = lchild;
		}
	}
}

红黑树的查验

红黑树的检查通过先取一路记录黑色节点数量,然后采用递归处理每条路线检查是否黑色节点数量一致,并且为了方便,稍微封装了一下,外界调不用参数

	bool IsValidRBTRee()
	{
		size_t pathblack = 0;

		Node* root = GetRoot();
		Node* par = root;
		while (par)
		{
			if (par->_col == Black)
				pathblack++;
			par = par->_pLeft;
		}

		return _IsValidRBTRee(root, 0, pathblack);
	}
private:
	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
	{
		if (pRoot == nullptr)
		{
			if (blackCount != pathBlack)
			{
				cout << "路径上黑色节点不同!" << endl;
				return false;
			}
			return true;
		}

		if (pRoot->_col == Black)
		{
			blackCount++;
		}

		return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)
			&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
	}

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

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

相关文章

基于矢量瓦片技术的GIS引擎

矢量地图是通过对点线面坐标信息集合的管理和渲染实现优于栅格画面质量的一种gis展示技术&#xff0c;涉及不同坐标系变换&#xff0c;视窗比例尺换算等。当你遇到海量坐标数据和属性信息需要管理时你就不得不在有限内存和庞大数据间左右为难&#xff0c;将地图矢量数据进行分块…

LabVIEW提高开发效率技巧----时序分析

一、什么是时序分析&#xff1f; 时序分析是优化LabVIEW程序性能的重要步骤。它通过分析程序各个部分的执行时间&#xff0c;帮助开发者找到程序运行中的瓶颈&#xff0c;并进行有针对性的优化。在LabVIEW中&#xff0c;Profile Performance and Memory工具是进行时序分析的关…

浏览器中使用模型

LLM 参数越来越小&#xff0c;使模型跑在端侧成为可能&#xff0c;为什么要模型跑在端侧呢&#xff0c;首先可以节省服务器的算力&#xff0c;现在 GPU 的租用价格还是比较的高的&#xff0c;例如租用一个 A10 的卡1 年都要 3 万多。如果将一部分算力转移到端侧通过小模型进行计…

Linux中真实的调度算法,进程地址空间,命令行参数

文章目录 Linux中真正的调度算法补充 命令行参数什么是命令行参数&#xff1f;命令行参数的用途如何在不同的编程语言中使用命令行参数命令行参数好处 Linux中真正的调度算法 这是Linux2.6的内核中进程队列的数据结构 其中有这两个指针*active,*expired&#xff0c;而Linux为…

论文及其创新点学习cvpr2022 On the Integration of Self-Attention and Convolution

代码地址 https://github.com/LeapLabTHU/ACmix https://gitee.com/mindspore/models 论文创新点&#xff0c;将注意力机制 和卷积 相结合 # encoding: utf-8author: duhanyue start time: 2024/10/13 10:04 import torch import torch.nn as nn def position(H, W, is_cudaT…

邮票鉴赏系统| 邮票鉴赏系统平台|基于java和vue的邮票鉴赏系统设计与实现(源码+数据库+文档)

邮票鉴赏系统\ 目录 基于java和vue的邮票鉴赏系统设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c;阿里…

用 Gemini Google 生成图片的魔法

用 Gemini Google 生成图片的魔法指南 你是否曾经想过&#xff0c;用一些简单的文本描述来生成一张图片&#xff1f;这听起来像是科幻小说中的魔法&#xff0c;但实际上&#xff0c;这就是 Gemini Google 的魔力&#xff01;在这篇文章中&#xff0c;我将向你详细介绍如何使用…

【HarmonyOS NEXT】实现页面水印功能

关键词&#xff1a;鸿蒙、水印、Watermark、页面、触摸问题 注&#xff1a;本期文章同样适用 OpenHarmony 的开发 在app开发过程中时常会出现敏感信息页面&#xff0c;为保护信息安全和及时的数据追踪&#xff0c;通常会采用给页面加水印的形式&#xff0c;那么本期文章会介绍…

自回归视觉生成里程碑!比ControlNet 和 T2I-Adapter 快五倍!北大腾讯提出CAR:灵活、高效且即插即用的可控框架

文章链接&#xff1a;https://arxiv.org/pdf/2410.04671 项目链接&#xff1a;https://github.com/MiracleDance/CAR 亮点直击 CAR是首个为自回归模型家族设计的灵活、高效且即插即用的可控框架。CAR基于预训练的自回归模型&#xff0c;不仅保留了原有的生成能力&#xff0c;还…

sherpa-ncnn 语言模型简单对比

在昨天把系统搞崩溃前&#xff0c;对sherpa-ncnn的中文模型做了一个简单的对比。这次使用的分别是sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13&#xff08;以下简称bilingual-zh-en-2023-02-13&#xff09;和sherpa-ncnn-streaming-zipformer-small-bilingual…

服务器数据恢复—EMC存储RAID5磁盘阵列数据恢复案例

服务器数据恢复环境&#xff1a; 一台EMC某型号存储设备&#xff0c;该存储中有一组由12块&#xff08;包括2块热备盘&#xff09;STAT硬盘组建的raid5阵列。 服务器故障&#xff1a; 该存储在运行过程中突然崩溃&#xff0c;raid瘫痪。数据恢复工程师到达现场对故障存储设备进…

GPT联网分析到底有多强?实测效果告诉你答案!

文章目录 零、前言一、gpt-4o操作指导gpt4o 二、感受 零、前言 早上在聊到博主在选择平台时&#xff0c;要选择哪个平台发展。 通过GPT查询并分析了小红书&#xff0c;微信视频号&#xff0c;抖音和B站的用户群体。 由此可举一反三&#xff0c;如何让GPT联网分析&#xff0c;…

部署私有仓库以及docker web ui应用

官方地址&#xff1a;https://hub.docker.com/_/registry/tags 一、拉取registry私有仓库镜像 docker pull registry:latest 二、运⾏容器 docker run -itd -v /home/dockerdata/registry:/var/lib/registry --name "pri_registry1" --restartalways -p 5000:5000 …

如何针对项目中的技术难点准备面试?——黑马点评为例

最核心的&#xff0c;包装和准备 个人项目&#xff0c;怎么包装&#xff1f;一定要写出代码才可以吗&#xff1f; 你可以在系统A中实现就可以&#xff0c;了解其中实现的细节&#xff0c;怎么跟面试官对线等等&#xff0c;这些话术到位了之后&#xff0c;再把它融入到系统B&a…

《CUDA编程》7.全局内存的合理使用

上一章简单的介绍了一下各种内存&#xff0c;本章开始详细讲解各个内存的合理使用&#xff0c;在所有设备中&#xff0c;全局内存的访问速度最慢&#xff0c;是CUDA程序的一个性能瓶颈&#xff0c;所以值得特别关注 1 全局内存的合并与非合并访问 对全局内存的访问将触发内存事…

LabVIEW如何实现高精度定时器

在LabVIEW中实现高精度定时器通常需要考虑以下几个方面&#xff1a;定时器的精度要求、操作系统的调度机制、硬件资源&#xff08;如计时器、触发器&#xff09;等。以下是几种常见的实现方式&#xff1a; ​ 1. 使用 Wait(ms) 或 Wait Until Next ms Multiple VI 这两个函数…

【无人机设计与控制】PID_积分滑模_积分反步四旋翼无人机轨迹跟踪控制算法

摘要 本文基于四旋翼无人机设计与控制&#xff0c;提出了一种结合PID控制、积分滑模控制以及积分反步控制的轨迹跟踪算法。该算法通过调节无人机的运动轨迹&#xff0c;提升其在复杂环境下的稳定性与抗扰动能力。实验结果表明&#xff0c;该算法能有效改善无人机的轨迹跟踪精度…

【python实操】python小程序之计算对象个数、游戏更新分数

引言 python小程序之计算对象个数、游戏更新分数 文章目录 引言一、计算对象个数1.1 题目1.2 代码1.3 代码解释1.3.1 代码结构1.3.2 模块解释1.3.3 解释输出 二、游戏更新分数2.1 题目2.2 代码2.3 代码解释2.3.1 定义 Game 类2.3.2 创建 Game 实例并调用方法 三、思考3.1 计算对…

C++之String类模拟实现(下)

片头 哈喽~小伙伴们&#xff0c;在上一篇中&#xff0c;我们讲解了C的string类的相关函数&#xff0c;这一章中&#xff0c;我们将继续深入学习string类函数&#xff0c;准备好了吗&#xff1f;咱们开始咯~ 五、对内容进行修改 ⑤insert函数 在指定位置插入字符或者字符串 …

基于Raspberry Pi人脸识别自动门

人脸识别自动门 简介 在当今数字化时代&#xff0c;智能家居安全变得越来越重要。今天&#xff0c;我要向大家介绍一个结合了安全性与便利性的项目——人脸识别自动门。这个项目通过在门上实施基于面部识别的高级安全系统&#xff0c;使用摄像头验证房主的面部&#xff0c;自…