C++:红黑树

news2024/12/23 19:18:59

红黑树的概念

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

 红黑树的性质

⭐1.每个节点不是红色就是黑色。

⭐2.根节点是黑色的。

⭐3.如果一个节点是红色的,则它的两个孩子结点是黑色的。也就意味着,红黑树没有连续的红色节点。

⭐4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。也就是说每条路径都有相同数量的黑色节点。

⭐5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点。

从性质上分析红黑树的近似平衡

一颗红黑树最短的路径是这条路径全黑。最长是一红一黑相间路径。

对于近似平衡来说:

①最优情况就是左右平衡,此时每条路径都是全黑或者是一红一黑相间,形成满二叉树。

②差的情况就是左右越不平衡,情况就越差。比如左子树全黑,而右子树是一黑一红相间。假设左子树全黑的路径长度位h = logN,因为红黑树要求每条路径的黑色节点的数量是相同的,而右子树是一红一黑相间的,那就说明右子树的长度是左子树的两倍h = 2*logN,这是最差的情况了,再差点就不是红黑树了。

红黑树节点的定义

红黑树节点的定义,跟AVL树的区别就是红黑树不需要平衡因子,而加入了颜色红跟黑。在定义当中,构造函数初始化列表对颜色_col默认初始化为红色是因为权衡了上面所述红黑树性质中的性质3和性质4。

性质3是说明了红黑树没有连续的红色节点,性质4说明的是每条路径都有相同数量的黑色节点。我们在定义节点的时候,需要给节点的颜色初始化,要么是红色,要么是黑色。

如果我们选择了黑色,那么在插入新节点之前,我们是往红黑树插入的嘛,本身就是红黑树,再插入一个黑色节点的话,那么必定会破坏掉红黑树的规则,是一定被破坏掉的,那么就一定需要对这颗红黑树进行调整。

如果我们选择红色,那么有可能会破坏掉红黑树的规则,也可能不会造成破坏。因为新增的节点的父节点是黑色的,那么就不会造成影响,而父节点是红色的话,那就需要调整。

因此,综上所述,默认初始化为红色是比较好的选择。

//使用枚举
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;
	Colour _col;

	RBTreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)  //跟AVL树一样,使用parent节点是为了旋转
		,_col(RED)  //默认是红色
	{}
};

红黑色的插入操作

红黑树是在二叉搜索树的基础上加入了平衡限制条件的树,因此红黑树的插入分为两步:

第一步:按照二叉搜索树的规则插入新节点。

第二步:检测新节点插入后,红黑树的性质是否造到破坏。

判断依据:因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

本文约定好:cur为当前节点,p(parent)为父节点,g(grandther)为祖父节点,u(uncle)为叔叔节点。

①cur为红,p为红,g为黑,u存在且为红

这种情况是插入的节点cur是红色的(默认),cur的双亲节点和叔叔节点是红色的,祖先节点是黑色的。因为不能连续存在红色节点,那么就需要把颜色调整一下即可,不需要旋转。

调整的方法为:将p节点和u节点的颜色改成黑色,同时为了防止g不是一棵单独的树,先把它变成红色,再进行判断,如果是单独的树,那么就把g的颜色变回黑色,如果是一棵子树,那么就往上继续调整。

②cur为红,p为红,g为黑,u不存在/u存在且为黑

这种情况下是需要旋转+变色的。因为当cur为红色,p为红色,g为黑色,而u的情况是:

如果u不存在,cur肯定是新增的节点,因为在新增之前,p和g组成的树是一棵红黑树,在cur新增之后,不符合红黑树的性质。

这种情况下光凭变色是解决不了问题的,因为u为空说明有一条路径只有根节点,而另一条路径上会存在连续红,只凭变色,无论怎么变,都会导致各路径的黑色节点数量不相同,所以需要先根据p和cur的位置来决定旋转的方向而旋转,再变色。

如果u存在,则u是黑色,并且cur原本的颜色一定是黑色的,而现在cur的颜色是红色,那就肯定是第一种情况调整后,导致了现在的cur的颜色变了

这种情况下就跟u不存在的情况一样,采用旋转+变色来解决问题。此时,u存在不存在已经没什么关系了(单独是看构造红黑树的情况来说)。

③cur为红,p为红,g为黑,u不存在/u存在且为黑

这种情况的颜色是跟情况二的一样,区别就是节点的位置不一样。

这种情况是跟第二中情况差不多,就是多了一步旋转,先左旋再右旋,或者先右旋再左旋。

整体代码如下:

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;
		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);
		cur->_col = RED;//多写一步,防止写错代码。
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//创建完二叉搜索树
		//开始创建红黑树,使用颜色来判断是否需要调整
		//循环往上走,循环条件:当走到的parent不为空,并且parent是红色的
		//即我们列举是三种情况,parent都是红的,就需要重新调整
		//如果parent是黑色的,那就不需要了。直接就是一棵红黑树,不进入循环
		while (parent && parent->_col == RED)
		{
			//保存祖先节点,即g节点
			Node* grandfther = parent->_parent;
			//判断父节点是在祖先节点的哪边
			if (parent == grandfther->_left)
			{
				//父节点在左边,那么叔叔节点就在右边
				Node* uncle = grandfther->_right;
				//情况一:uncle存在且为红。改变颜色即可
				if (uncle && uncle->_col == RED)
				{
					//变色。
					parent->_col = uncle->_col = BLACK;
					grandfther->_col = RED;

					//往上走
					cur = grandfther;
					parent = cur->_parent;
				}
				else  //uncle不存在 或者 存在但是黑色
				{
					//情况二  p是g的左孩子,cur是p的左孩子,以g为轴右单旋
					if (cur == parent->_left)
					{
						//右单旋
						RotateR(grandfther);
						//变色  右单旋后,parent为根节点,变黑色。cur和g节点为红色
						parent->_col = BLACK;
						grandfther->_col = RED;
					}
					else  //情况三  p是g的左孩子,cur是p的右孩子.
					{
						//先以p为轴左旋转
						RotateL(parent);
						//变成情况二,再以g为轴右单旋
						RotateR(grandfther);
						//变色  cur变成根节点,为黑色。p和g是红色
						cur->_col = BLACK;
						grandfther->_col = RED;

					}

					break;
				}
			}
			else  //parent是在grandfther的右边
			{
				//叔叔节点就在祖先节点的左边
				Node* uncle = grandfther->_left;
				//情况一:uncle存在且为红。改变颜色即可
				if (uncle && uncle->_col == RED)
				{
					//变色。
					parent->_col = uncle->_col = BLACK;
					grandfther->_col = RED;

					//往上走
					cur = grandfther;
					parent = cur->_parent;
				}
				else  //uncle不存在 或者 存在但是黑色
				{
					//情况二  p是g的右孩子,cur是p的右孩子。
					if (cur == parent->_right)
					{
						//左单旋
						RotateL(grandfther);
						//变色  右单旋后,parent为根节点,变黑色。cur和g节点为红色
						parent->_col = BLACK;
						grandfther->_col = RED;
					}
					else  //情况三  p是g的右孩子,cur是p的左孩子.
					{
						//先以p为轴右旋转
						RotateR(parent);
						//变成情况二,再以g为轴左单旋
						RotateL(grandfther);
						//变色  cur变成根节点,为黑色。p和g是红色
						cur->_col = BLACK;
						grandfther->_col = RED;

					}

					break;
				}
			}
		}
		//最后将根节点置为黑
		_root->_col = BLACK;
		return true;
	}

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

			}
			else                        
			{
				ppNode->_right = subL;
			}

			
			subL->_parent = ppNode;
		}
	}

	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;
		}

	}

private:
		Node* _root = nullptr;
};

旋转请看:AVL树这篇文章有详细解析。红黑树的旋转直接复用AVL树的旋转的代码即可。

红黑树与AVL树的对比

⭐相同点:红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N)。

⭐不同点:红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言降低了插入和旋转的次数。而AVL树是高度平衡的二叉搜索树,旋转的次数比红黑树的要频繁。

所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

也就是因为红黑树在修改操作方面的性能比AVL树好,因此红黑树都用在了C++的STL库(map/set、mutil_map/mutil_set),Java库、Linux内核等等地方。
 

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

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

相关文章

「期末复习」线性代数

第一章 行列式 行列式是一个数&#xff0c;是一个结果三阶行列式的计算&#xff1a;主对角线的乘积全排列与对换逆序数为奇就为奇排列&#xff0c;逆序数为偶就为偶排列对换&#xff1a;定理一&#xff1a;一个排列的任意两个元素对换&#xff0c;排列改变奇偶性&#xff08;和…

【Unity3D】Unity 3D 连接 MySQL 数据库

1.Navicat准备 test 数据库&#xff0c;并在test数据库下创建 user 数据表&#xff0c;预先插入测试数据。 2.启动 Unity Hub 新建一个项目&#xff0c;然后在Unity编辑器的 Project视图 中&#xff0c;右击新建一个 Plugins 文件夹将连接 MySQL的驱动包 导入&#xff08;附加驱…

Java链表模拟实现+LinkedList介绍

文章目录一、模拟实现单链表成员属性成员方法0&#xff0c;构造方法1&#xff0c;addFirst——头插2&#xff0c;addLast——尾插3&#xff0c;addIndex——在任意位置插入3.1&#xff0c;checkIndex——判断index合法性3.2&#xff0c;findPrevIndex——找到index-1位置的结点…

Java围棋游戏的设计与实现

技术&#xff1a;Java等摘要&#xff1a;围棋作为一个棋类竞技运动&#xff0c;在民间十分流行&#xff0c;为了熟悉五子棋规则及技巧&#xff0c;以及研究简单的人工智能&#xff0c;决定用Java开发五子棋游戏。主要完成了人机对战和玩家之间联网对战2个功能。网络连接部分为S…

Mac下拉式终端的安装与配置 (iTerm2)

Mac下拉式终端的安装与配置 使用效果如图所示 安装前置软件 iTerm2 很可惜&#xff0c;如此炫酷的功能在原终端中并不能实现&#xff0c;我们需要借助iTerm2这个软件来实现。 官网链接&#xff1a;iTerm2 - macOS Terminal Replacement 我们点击download下载即可 配置 当我…

代码随想录第十天(28)

文章目录28. 找出字符串中第一个匹配项的下标看答案KMPnext数组&#xff08;前缀表&#xff09;最长公共前后缀如何计算前缀表前缀表与next数组时间复杂度分析28. 找出字符串中第一个匹配项的下标 莫得思路……好久没做题&#xff0c;都已经忘得差不多了 看答案 其实就是自己…

ModelScope 垂类检测系列模型介绍

文章目录ModelScope介绍垂类模型介绍调用方式1 Demo Service2 Notebook3 本地使用* 二次开发总结ModelScope介绍 ModelScope 是阿里达摩院推出的 中文版模型即服务&#xff08;MaaS, Model as a Service&#xff09;共享平台。该平台在2022年的云栖大会上发布&#xff0c;之前…

Windows安装系列:SVN Server服务

一、下载与安装 1、下载VisualSVN-Server-5.1.1-x64.msi 地址&#xff1a;Download | VisualSVN Server 2、找到最新版本SVN 5.1.1&#xff0c;直接双击它&#xff0c;弹出如下安装界面 3、点击Next 4、勾选我接受&#xff0c; 点击"Next" 5、默认选项&#xff0c…

stack、queue和priority_queue

目录 一、栈&#xff08;stack&#xff09; 1.stack的使用 2.容器适配器 3.stack的模拟实现 二、队列&#xff08;queue&#xff09; 1.queue的使用 2.queue的模拟实现 三、双端队列&#xff08;deque&#xff09; 1.vector&#xff0c;list的优缺点 2.认识deque 四…

如何调试段错误?

刚接触指针的时候&#xff0c;经常会遇到段错误。 rootTurbo:linklist# ls link.c link.h main main.c rootTurbo:linklist# ./main 链表初始化成功 Segmentation fault (core dumped) rootTurbo:linklist#所谓段错误&#xff0c;就是访问了不能访问的内存。 比如内存不存在…

1.8 正则表达式

正则表示式是用来匹配与查找字符串的&#xff0c;从网上爬取数据不可避免的会用到正则表达式。 Python 的表达式要先引入 re 模块&#xff0c;正则表达式以 r 引导。Re库主要功能函数函数说明re.search()在一个字符串中搜索匹配正则表达式的第一个位置&#xff0c;返回match对象…

七大设计原则之里氏替换原则应用

目录1 里氏替换原则2 里氏替换原则应用1 里氏替换原则 里氏替换原则&#xff08;Liskov Substitution Principle,LSP&#xff09;是指如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时&#xff0c;程序 P…

基于蜣螂算法改进的LSTM预测算法-附代码

基于蜣螂算法改进的LSTM预测算法 文章目录基于蜣螂算法改进的LSTM预测算法1.数据2.LSTM模型3.基于蜣螂算法优化的LSTM4.测试结果5.Matlab代码摘要&#xff1a;为了提高LSTM数据的预测准确率&#xff0c;对LSTM中的参数利用蜣螂搜索算法进行优化。1.数据 采用正弦信号仿真数据&…

算法训练——剑指offer(Hash集合问题)

摘要 数据结构中有一个用于存储重要的数据结构&#xff0c;它们就是HashMap,HasSet&#xff0c;它典型特征就是存储key:value键值对。在查询制定的key的时候查询效率最高O(1)。Hashmap&#xff0c;HasSet的底层结构是如图所示。它们的区别就是是否存在重复的元素。 二、HashMa…

搞了个ChatGPT机器人,免费使用最强大的AI,这一晚上几个群都聊high了

前言 最近ChatGPT实在是太火了&#xff01;其实去年年底的时候&#xff0c;ChatGPT已经在程序员圈子里小小的火了一把&#xff0c;再加上年后资本的炒作和各个公司疯狂的蹭热点&#xff0c;就彻底火出圈了。 ChatGPT使用体验 其实自己已经用了一段时间的ChatGPT&#xff0c;…

UART和RS232、RS485的联系和区别、以及对软件编程的影响

1、串口、UART、RS232、RS485概念的理解 (1)狭义上的串口&#xff1a;指的是串口协议&#xff0c;就是时序图、数据收发先后顺序等&#xff0c;是抽象出来的协议&#xff1b; (2)广义上的串口&#xff1a;指的是符合串口协议的接口&#xff0c;UART、RS232、RS485在实际工作中都…

Python:每日一题之剪格子(DFS剪枝)

题目描述 如下图所示&#xff0c;3 x 3 的格子中填写了一些整数。 我们沿着图中的红色线剪开&#xff0c;得到两个部分&#xff0c;每个部分的数字和都是 60。 本题的要求就是请你编程判定&#xff1a;对给定的 mn 的格子中的整数&#xff0c;是否可以分割为两个部分&#xf…

【fastjson2.x 记录】那些从1.x升级到2.x踩过的坑

这篇文章主要记录升级到 fastjson2.x 版本后出现的一些问题 1. jar 包引入问题 问题描述&#xff1a;从 2.x 版本后&#xff0c;fastjson 貌似开始对代码模块进行了拆分&#xff0c;所以在调整配置的时候&#xff0c;会发现有些类突然没了&#xff0c;其实是需要引入其他的扩展…

ChatGPT超详细注册教程,只要3.5!

一、注册必备条件注册 ChatGPT 必备如下三个条件&#xff1a;1、用于注册ChatGPT的邮箱&#xff0c;什么邮箱都可以。2、科学上网工具&#xff0c;注册账号和登录时需要。3、一个国外手机号&#xff0c;用于接收验证码&#xff0c;后面会详细介绍如何花 3.55 元获取。二、注册 …

从10大管理看产品经理的日常工作——产品整体管理

产品经理这个职业有其特殊性&#xff0c;在学校&#xff0c;没有专门的专业或课程教授你如何成为一名产品经理&#xff0c;虽然我们给产品经理划分了初级、中级、高级&#xff0c;但是却没有相应的职称可以证明你的级别&#xff0c;也没有相关的职业证书可以证明你的能力。产品…