【C++】红黑树的插入分析及验证

news2024/11/24 20:41:16

文章目录

    • 1. 红黑树概念
    • 2. 红黑树性质
    • 3. 结构定义
      • 关于默认节点为红/黑色的讨论
    • 4. insert
      • 情况1—— uncle节点存在且为红色(g p c左斜形成一条直线)
      • 情况2——uncle节点不存在/存在且为黑色(g p c 左斜形成直线 右单旋)
        • uncle节点不存在
        • uncle节点存在并且为黑色
      • 情况3——uncle节点不存在/存在且为黑色(g p c 形成左折线 双旋)
        • uncle节点不存在
        • uncle节点存在并且为黑色
      • 情况1—— uncle节点存在且为红色(g p c右斜形成一条直线)
      • 情况2——uncle节点不存在/存在且为黑色(g p c 右斜形成直线 左单旋)
      • 情况3——uncle节点不存在/存在且为黑色(g p c 形成右折线 双旋)
    • 5.判断是否为红黑树
    • 6. 整体代码

1. 红黑树概念

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

2. 红黑树性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
(不能出现连续的红色节点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
(每条路径上都有相同数量的黑色节点)
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
(走到NULL才算一条路径)

3. 结构定义

使用枚举来记录红色与黑色,用_col表示当前节点颜色


但是在构造函数中为什么默认是红色呢?为什么不能是黑色呢?

关于默认节点为红/黑色的讨论

若在红框中插入黑色节点则违反规则4 即每条路径上都有相同数量的黑色节点,还需要再次将不同路径上都添加黑色节点,影响太大


若在红框中插入红色节点,则有可能违反规则3(存在两个连续的红色节点)
当前情况违反规则3


若插入红色节点后,父节点为黑色,则不违反规则3


所以默认节点为红色更利于去解决问题

4. insert

grandfather节点省略为g ,uncle节点省略为u ,parent节点省略为p,cur节点省略为c

情况1—— uncle节点存在且为红色(g p c左斜形成一条直线)

当插入红色节点后,与父节点形成连续的红色节点
把parent节点变成黑色,uncle节点置为黑色,并将grandfather节点置为红色
若grandfather节点的父节点为黑色,则不需要继续处理
若grandfather节点的父节点为红色,把当前的grandfather节点作为新增节点cur,继续向上调整


情况2——uncle节点不存在/存在且为黑色(g p c 左斜形成直线 右单旋)

uncle节点不存在

当uncle节点不存在时,则cur作为新增节点,
因为红黑树也是一种AVL树,此时g的平衡因子已经为-2,所以需要旋转
又因为左边高,所以进行右单旋

uncle节点存在并且为黑色

首先进行变色,将新增节点的上面两个节点置为黑色,再将cur节点置为红色
同时需要进行右旋转


在这里插入图片描述
将c作为g的左子树,将g作为p的右子树
将g置为红色
将p置为黑色

RotateR/RotateL的实现,与AVL树的类似,只需把原来的代码的平衡因子去掉即可
不懂查看:AVL树的实现

情况3——uncle节点不存在/存在且为黑色(g p c 形成左折线 双旋)

因为 grandfather(g) parent( p) cur( c) 节点为一条折线,所以为双旋

uncle节点不存在

作为这样的折线存在,所以要进行双旋,先对p进行右单旋,在对旋转后的根进行左单旋

uncle节点存在并且为黑色

首先进行变色,将新增节点上面的两个节点由红色置为黑色
再将cur节点由黑色置为红色


在进行左单旋,将cur的左子树节点 作为p的右子树,将p作为cur的左子树


在这里插入图片描述
进行右单旋,将cur的右子树节点作为g的左子树,将g作为cur的右子树
最终cur变为黑色,g变为红色


情况1—— uncle节点存在且为红色(g p c右斜形成一条直线)

在这里插入图片描述

当插入红色节点后,与父节点形成连续的红色节点
把parent节点变成黑色,uncle节点置为黑色,并将grandfather节点置为红色


在这里插入图片描述

若grandfather节点的父节点为红色,把当前的grandfather节点作为新增节点cur,继续处理


与上述左斜形成直线的写法相同

情况2——uncle节点不存在/存在且为黑色(g p c 右斜形成直线 左单旋)

在这里插入图片描述

这里以节点不存在举例


此时的uncle节点处于NULL
将parent节点置为黑色,将grandfather节点置为红色
并进行旋转,将1作为6的左子树,将6作为8的左子树
相当于进行左单旋

情况3——uncle节点不存在/存在且为黑色(g p c 形成右折线 双旋)

首先进行变色,将新增节点上面的两个节点由红色置为黑色
再将cur节点由黑色置为红色


在这里插入图片描述
在进行右单旋,将cur的右子树节点 作为p的左子树,将p作为cur的右子树


在这里插入图片描述
进行左单旋,将cur的左子树节点作为g的右子树,将g作为cur的左子树
最终cur变为黑色,g变为红色

5.判断是否为红黑树

规则中要求根节点为黑色,所以当根为红色时就返回false

连续的红色节点
若当前节点为红时,检查儿子是否为红,但是儿子节点有可能为空
所以采用当前节点为红时,若父节点也为红,则返回false

使用blacknum用于记录每条路径的黑色节点个数
blacknum作为一个形参传值调用,下一层的++不会影响上一层
如果当前节点的颜色为黑色,则blacknum++

6. 整体代码

#pragma once
#include<iostream>
#include<cassert>	
using namespace std;
enum colour
{
	RED,//红色 默认为0
	BLACK,//黑色 默认为1
};
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;//表示颜色

	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:
	   bool insert(const pair<K, V>& kv)
	   {
		   if (_root == nullptr)
		   {
			   _root = new Node(kv);
			   _root->_col = BLACK;//若刚插入一个节点,则该节点颜色是黑色
			   return true;
		   }
		   Node* parent = nullptr;//用于记录cur的前一个节点
		   Node* cur = _root;
		   while (cur)
		   {
			   //若插入的值比当前树的值小 插入左边
			   if (cur->_kv.first > kv.first)
			   {
				   parent = cur;
				   cur = cur->_left;
			   }
			   //若插入的值比当前树的值大 插入右边
			   else if (cur->_kv.first < kv.first)
			   {
				   parent = cur;
				   cur = cur->_right;
			   }
			   else
			   {
				   //若插入的值,在树中有相同的值 ,则插入失败
				   return false;
			   }
		   }
		   cur = new Node(kv);
		   //再次判断parent当前节点值与插入值大小
		   if (parent->_kv.first > kv.first)
		   {
			   parent->_left = cur;
		   }
		   else
		   {
			   parent->_right = cur;
		   }
		   //cur的上一个节点即为 刚才的记录cur前一个节点的parent
		   cur->_parent = parent;

           //当父节点不为NULL并且父节点为红色
		   while (parent != nullptr && parent->_col == RED)
		   {
			   Node* grandfather = parent->_parent;//爷爷节点

			   //若父节点为爷爷节点的左子树,则unlce为爷爷节点的右子树
			   if (grandfather->_left == parent)
			   {
				   Node* uncle = grandfather->_right;
				   //       g
				   //    p     u
				   // c
				   //情况1:u存在并且为红,直接变色即可,并继续往上处理
				   if (uncle && uncle->_col == RED)
					   //若uncle节点为红色,将parent与uncle都置为黑色,爷爷节点置为红色
				   {
					   parent->_col = BLACK;
					   uncle->_col = BLACK;
					   grandfather->_col = RED;

					   //继续往上调整
					   cur=grandfather;
					   parent = cur->_parent;
				   }
				   //情况2+3:u不存在或者u存在且为黑,旋转+变色
				   else
				   {	 
					   //情况2
					   //g p c 作为一条直线 所以为单旋
					   //    g
					   //  p   u
	 				   //c 
					   if (cur == parent->_left)
					   {
						   //右旋转
						   RotateR(grandfather);
						    
						   //最终p作为最终的根 为黑  g为红 
						   parent->_col = BLACK;
						   grandfather->_col = RED;
					   }
					   //情况3
					   //g p c 作为一条折线 所以为双旋
					   //    g
					  //   p   u
					  //     c 
					   else
					   {
						   //左单旋
						   RotateL(parent);
						   //右单旋
						   RotateR(grandfather);
						   //最终cur作为最终的根 为黑  g为红 
						   cur->_col = BLACK;
						   grandfather->_col = RED;
						   //父节点继续保持红色
						   parent->_col = RED;
					   }
					   break; 
				   }
			   }

			   else//grandfather->_right == parent
				   若父节点为爷爷节点的右子树,则unlce为爷爷节点的左子树
			   {
				   //   g
				   // u   p
				   //       c
				   Node* uncle = grandfather->_left;

				   //情况1:u存在并且为红,直接变色即可,并继续往上处理
				   if (uncle && uncle->_col == RED)
					   //若uncle节点为红色,将parent与uncle都置为黑色,爷爷节点置为红色
				   {
					   parent->_col = BLACK;
					   uncle->_col = BLACK;
					   grandfather->_col = RED;

					   //继续往上调整
					   cur = grandfather;
					   parent = cur->_parent;
				   }

				   //情况2+3:u不存在或者u存在且为黑,旋转+变色
				   else
				   {
					   //情况2
					   //g p c 作为一条直线 所以为单旋
					   //    g
					   //  u   p
					   //        c  
					   if (cur == parent->_right)
					   {
						   //左旋转 
						   RotateL(grandfather);

						   //最终p作为最终的根 为黑  g为红 
						   parent->_col = BLACK;
						   grandfather->_col = RED;
					   }
					   //情况3
					   //g p c 作为一条折线 所以为双旋
					   //   g
					  //  u   p
					  //    c 
					   else
					   {
						   //右单旋
						   RotateR(parent);
						   //左单旋
						   RotateL(grandfather);
						   //最终cur作为最终的根 为黑  g为红 
						   cur->_col = BLACK;
						   grandfather->_col = RED;
					   }
					   break;
				   }
			   }
		   }
		   //为了避免grandfather节点正好为根时,会被更新成红色的情况
		   _root->_col = BLACK;
		   return true;
	   }

	   void inorder()//中序遍历
	   {
		   _inorder(_root);
		   cout << endl;
	   }
	   //判断一颗二叉树是否为红黑树
	   bool isbalance()
	   {
		   //检查根是否为黑
		   if (_root && _root->_col == RED)
		   {
			   cout << "根节点颜色是红色" << endl;
			   return false;
		   }
		   //连续的红色节点
		   return _check(_root,0);
	   }

	private:
		bool _check(Node* root,int blacknum)
		{
			if (root == nullptr)
			{
				//为空时,blacknum代表一条路径的黑色节点个数
				cout << blacknum << " ";
				return true;
		    }
			//blacknum代表黑色节点的个数
			if (root->_col == BLACK)
			{
				blacknum++;
			}
			//若当前节点为红 父节点也为红
			if (root->_col == RED
				&&root->_parent 
				&&root->_parent->_col==RED)
			{
				cout << "存在连续的红色节点" << endl;
				return false;
			}
			//遍历整棵树
			return	_check(root->_left,blacknum) && _check(root->_right,blacknum);
		}
		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 != nullptr)
			   {
				   subRL->_parent = parent;
			   }
			   Node* ppnode = parent->_parent;//记录parent的前一个节点

			   subR->_left = parent;
			   parent->_parent = subR;

			   if (ppnode == nullptr)//说明parent是根即代表整棵树
			   {
				   _root = subR;//subR作为新的根
				   _root->_parent = nullptr;//subR的父节点指向原来的parent,所以要置nullptr
			   }
			   else//说明旋转的是一部分,所以要跟ppnode相连接
			   {
				   if (ppnode->_left == parent)//若连接在左子树上
				   {
					   ppnode->_left = subR;
				   }
				   else//若连接在右子树上
				   {
					   ppnode->_right = subR;
				   }
				   subR->_parent = ppnode;//将subR父节点置为ppnode
			   }
		   }

		   void RotateR(Node* parent)//右单旋
		   {
			   Node* subL = parent->_left;
			   Node* subLR = subL->_right;
			   parent->_left = subLR;
			   if (subLR != nullptr)
			   {
				   subLR->_parent = parent;
			   }
			   Node* ppnode = parent->_parent;//记录parent的父节点
			   subL->_right = parent;
			   parent->_parent = subL;

			   if (ppnode == nullptr)//若旋转整棵树
			   {
				   _root = subL;
				   _root->_parent = nullptr;
			   }
			   else//若旋转整棵树的部分子树
			   {
				   if (ppnode->_left == parent)
				   {
					   ppnode->_left = subL;
				   }
				   else
				   {
					   ppnode->_right = subL;
				   }
				   subL->_parent = ppnode;//使subL的父节点为ppnode
			   }

		   }
	private:
		Node* _root = nullptr;
};

void test_RBTree1()
{
	int a[]={16, 3, 7, 11, 9, 26, 18, 14, 15};
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16,14 };
	RBTree<int, int>v1;
	for (auto e : a)
	{
		v1.insert(make_pair(e, e));
	}
	v1.inorder();
	cout << v1.isbalance() << endl;
	
}

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

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

相关文章

第7章链接:编译器驱动程序

示例程序由两个源文件组成&#xff0c;main.c 和 swap.c。 main函数初始化一个两元素的整数数组&#xff0c;然后调用swap函数来交换这一对数。 main.c void swap();int buf[2] {1, 2};int main() {swap();return 0; }swap.c extern int buf[];int *bufp0 &buf[0]; i…

【Java】Java中线程安全有哪些实现思路?

文章目录 1、使用 synchronized 关键字2、使用 ReentrantLock 类3、使用 ConcurrentHashMap 类4、使用 Atomic 类5、使用 ThreadLocal 类总结 在 Java 多线程编程中&#xff0c;线程安全是一个非常重要的概念。 线程安全通常指程序在多线程并发执行时&#xff0c;仍然能够保持正…

迄今为止,最强ChatGPT写论文技巧,总共6步,手把手告诉你

目录 第一步&#xff1a;现象确认 第三步&#xff1a;定位优质学术资源 第四步&#xff1a;对比分析 第五步&#xff1a;深挖启示 & 第六步&#xff1a;写论文 代写论文&#xff0c;不知道有多少朋友听说过&#xff0c;这是一门严格来说有点小众&#xff0c;但盈利空间…

常见问题解答:如何有效使用 Facebook 商务管理平台(BM)?

Facebook 商务管理平台&#xff08;BM&#xff09;是一个功能强大的工具&#xff0c;可帮助广告主在 Facebook 平台上管理和优化广告投放。然而&#xff0c;对于初次接触 BM 的用户来说&#xff0c;可能会遇到一些问题和困惑。本文将回答一些常见问题&#xff0c;帮助您更有效地…

如何使用分布式存储系统促进 AI 模型训练

在处理小型数据集和简单算法时&#xff0c;传统的机器学习模型可以存储在独立机器或本地硬盘驱动器上。然而&#xff0c;随着深度学习的发展&#xff0c;团队在处理更大的数据集和更复杂的算法时越来越多地遇到存储瓶颈。 这凸显了分布式存储在人工智能&#xff08;AI&#xf…

半监督学习笔记

聚类假设 假设输入数据点形成簇&#xff0c;每个簇对应于一个输出类&#xff0c;那么如果点在同一个簇中&#xff0c;则它们可以认为属于同一类。聚类假设也可以被视为低密度分离假设&#xff0c;即&#xff1a;给定的决策边界位于低密度地区。两个假设之间的关系很容易看出。一…

C++ 智能指针的原理、分类、使用

1. 智能指针介绍 为解决裸指针可能导致的内存泄漏问题。如&#xff1a; a&#xff09;忘记释放内存&#xff1b; b&#xff09;程序提前退出导致资源释放代码未执行到。 就出现了智能指针&#xff0c;能够做到资源的自动释放。 2. 智能指针的原理和简单实现 2.1 智能指针的原…

讯飞星火 VS 文心一言:谁是中文大语言模型的TOP1?

在百度发布文心一言一个多月后&#xff0c;科大讯飞也发布了自己的大模型“讯飞星火大模型”。本篇博客就测评一下这两个在中文圈最受好评的大语言模型&#xff0c;顺便辅以ChatGPT为参考。大家一起来看看到底谁是中文大语言模型的TOP1&#xff1f; 目录 体验网址 1、旅游攻…

Class类文件的结构

1 class文件介绍 Class文件是一组以8个字节为基础单位的二进制流&#xff1b; 各个数据项目严格按照顺序紧凑地排列在文件之中&#xff0c;中间没有添加任何分隔符; 采用一种类似C语言结构体的伪结构来存储数据&#xff0c;只要两种数据类型&#xff1a;“无符号数”和“表”…

execl函数总结以及扩展

为什么要用exec族函数&#xff0c;有什么作用&#xff1f; (1)一个父进程希望复制自己&#xff0c;使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的一一父进程等待客户端的服务请求。当这种请求到达时&#xff0c;父进程调用fork&#xff0c;使子进程处理此请求…

移动机器人运动规划---基于图搜索的基础知识---图和图搜索算法的基本概念

移动机器人运动规划---基于图搜索的基础知识---图和图搜索算法的基本概念 图和图搜索算法的基本概念图的基础概念图搜索基本概念图搜索算法图搜索算法框架 图和图搜索算法的基本概念 图的基础概念 图是有节点和边的一种表达方式 各节点由边连起来 边可以是有向的&#xff0c;…

Java经典笔试题—day07

Java经典笔试题—day07 &#x1f50e;选择题&#x1f50e;编程题&#x1f95d;Fibonacci数列&#x1f95d;合法括号序列判断 &#x1f50e;结尾 &#x1f50e;选择题 (1)Java属于&#xff08; &#xff09; A.操作系统 B.办公软件 C.数据库系统 D.计算机语言 D (2)类声明中&a…

大数据Doris(十八):Properties配置项和关于ENGINE

文章目录 Properties配置项和关于ENGINE 一、Properties配置项 二、关于ENGINE Properties配置项和关于ENGINE 一、Properties配置项 在创建表时,可以指定properties设置表属性,目前支持以下属性: replica

Kali-linux系统指纹识别

现在一些便携式计算机操作系统使用指纹识别来验证密码进行登录。指纹识别是识别系统的一个典型模式&#xff0c;包括指纹图像获取、处理、特征提取和对等模块。如果要做渗透测试&#xff0c;需要了解要渗透测试的操作系统的类型才可以。本节将介绍使用Nmap工具测试正在运行的主…

前端面试题汇总大全 -- 持续更新!

文章目录 一、html 系列 ⭐⭐⭐⭐⭐1、H5新增特性和css3新增特性&#xff1f;2、什么是HTML5&#xff0c;以及和HTML的区别是什么&#xff1f;3、说说你对 Dom 树的理解&#xff1f;4、跨域时怎么处理 cookie&#xff1f;5、说说你对 SSG 的理解&#xff1f;6、从输入url&#…

Azure API 管理缺陷突出了 API 开发中的服务器端请求伪造风险

微软最近修补了其 Azure API 管理服务中的三个漏洞&#xff0c;其中两个漏洞启用了服务器端请求伪造 (SSRF) 攻击&#xff0c;这些攻击可能允许黑客访问内部 Azure 资产。 概念验证漏洞用于突出开发人员在尝试为自己的 API 和服务实施基于黑名单的限制时可能犯的常见错误。 W…

JQuery 详细教程

文章目录 一、JQuery 对象1.1 安装和使用1.2 JQuery包装集对象 二、JQuery 选择器2.1 基础选择器2.2 层次选择器2.3 表单选择器 三、JQuery Dom 操作3.1 操作元素3.1.1 操作属性3.1.2 操作样式3.1.3 操作内容 3.2 添加元素3.3 删除元素3.4 遍历元素 四、JQuery 事件4.1 ready 加…

C/C++每日一练(20230513) 二叉树专场(7)

目录 1. 翻转二叉树 &#x1f31f; 2. 二叉树的最小深度 &#x1f31f; 3. 填充每个节点的下一个右侧节点指针 &#x1f31f;&#x1f31f; 附&#xff1a;二叉树的序列化与反序列化 &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一…

消息队列中间件介绍

消息队列介绍 消息队列中间件是大型系统中的重要组件&#xff0c;已经逐渐成为企业系统内部通信的核心手段。它具有松耦合、异步消息、流量削峰、可靠投递、广播、流量控制、最终一致性等一系列功能&#xff0c;已经成为异步RPC的主要手段之一。 目前常见的消息中间件有Active…

知名高校博士:我改了这2个地方,一开始被秒拒的论文很快就成功发表了~

手稿被拒后&#xff0c;你会怎么做&#xff1f;是直接换期刊重投&#xff0c;还是先仔细修改下论文呢&#xff1f; 伊利诺伊大学博士Sara E. Skrabalak分享了自己在论文被秒拒后&#xff0c;修改了文章部分内容就成功发表的经验。我们来看看她到底做了哪些修改吧 ~ Sara E. Sk…