C++:平衡二叉搜索树之红黑树

news2024/9/23 15:23:20

一、红黑树的概念

红黑树, 和AVL都是二叉搜索树, 红黑树通过在每个节点上增加一个储存位表示节点的颜色, 可以是RED或者BLACK, 通过任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树能够确保没有一条路径会比其他路径长出两倍,因此红黑树是接近平衡的

与AVL树相比,红黑树的插入和删除操作更具优势,因为红黑树是接近平衡的,AVL树是绝对平衡的, 因此插入删除等操作时,红黑树需要旋转的次数是比AVL树少的。

二、红黑树的性质

  1. 每个节点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子节点是黑色的
  4. 一条路径上没有连续的红色节点, 每条路径上黑色节点的数量是相同的

下面就是一个红黑树的示例:

根据红黑树的性质我们知道:一个红黑树中,最短路径 * 2 >= 最长路径

理论上:最短路径:全黑 最长路径:一黑一红交替

三、红黑树节点的定义

下面是红黑树节点的定义代码:

//节点的颜色定义
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)
	{}
};

四、红黑树的插入

红黑树是在二叉搜索树的基础上加上使其近似平衡的限制条件, 因此红黑树的插入可以分成以下两步:

1、按照二叉搜索树的性质插入新节点

2、检测新节点插入后,红黑树的性质是否遭到破坏

新节点的默认颜色是红色,因此,如果双亲节点是黑色就没有违反任何红黑树的性质,不需要调整,但是如果新插入节点的双亲节点是红色时, 就违反了不能有相连的红色节点的规则,此时需要对红黑树进行分情况讨论:

cur为当前节点, p为父节点, g为祖父节点, u为叔父节点

4.1情况一:cur 为红, p为红, g为黑, u存在且为黑

注意:这里的树,可能是一棵完整的树, 也可能是一个子树

此时abcde子树都为空

这个时候我们只需要进行变色处理, 将p 和 u节点变为黑色, 将g改为红色

调整完以后,如果g是根节点, 需要将g改为黑色, 因为红黑树的性质规定根节点的颜色必须是黑色。

如果g是子树,并且g的双亲节点是红色的话就需要继续向上调整, 如下图所示:

4.2情况二: cur为红, p为红, g为黑, u不存在/u存在且为黑

这里说明u的情况有两种:

1.u不存在, 此时cur一定是新增节点, 如果cur不是新增节点的话, 那么cur和p一定有一个节点的颜色是黑色, 但是那样就不满足每条路径黑色节点的数目相同这一性质。

p是g的左子节点, cur是p的左子节点, 那么就对g进行右单旋:

变色处理:p, g变色 – p变黑色, g变红色

p是g的右子节点, cur是p的右子节点, 那么就对g进行左单旋:

变色处理:p, g变色 – p变黑色, g变红色

p是g的左子节点, cur是p的右子节点,先对p进行左单旋, 再对g做右单旋:

变色处理:cur, g变色 – cur变黑色, g变红色

先进行左单旋;

再进行右单旋:

p是g的右子节点, cur是p的左子节点, 先对p进行右单旋, 再对g进行左单旋:

变色处理:cur, g变色 – cur变黑色, g变红色

先进行右单旋:

再进行左单旋:

2.u存在且为黑, 那么cur节点原来的颜色一定是黑色, 现在是红色的原因就是因为cur的子树在调整的过程中将cur的颜色更新成了红色。

p是g的左子节点, cur是p的左子节点, 那么就对g进行右单旋:

变色处理:p, g变色 – p变黑色, g变红色

p是g的右子节点, cur是p的右子节点, 那么就对g进行左单旋:

变色处理:p, g变色 – p变黑色, g变红色

p是g的左子节点, cur是p的右子节点,先对p进行左单旋, 再对g做右单旋:

变色处理:cur, g变色 – cur变黑色, g变红色

先进行左单旋:

再进行右单旋:

p是g的右子节点, cur是p的左子节点, 先对p进行右单旋, 再对g进行左单旋:

变色处理:cur, g变色 – cur变黑色, g变红色

先进行右单旋:

再进行左单旋:

左单旋代码实现:

//左单旋
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		parent->_right = SubRL;
		if (SubRL)
		{
			SubRL->_parent = parent;
		}

		Node* parentP = parent->_parent;
		SubR->_left = parent;
		parent->_parent = SubR;
		if (parentP == nullptr)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			if (parentP->_left == parent)
			{
				parentP->_left = SubR;
				SubR->_parent = parentP;
			}
			else
			{
				parentP->_right = SubR;
				SubR->_parent = parentP;
			}
		}

	}

右单旋代码实现:

//右单旋
	void RotateR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		parent->_left = SubLR;
		if (SubLR)
			SubLR->_parent = parent;

		Node* parentP = parent->_parent;
		SubL->_right = parent;
		parent->_parent = SubL;

		if (parentP == nullptr)
		{
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else
		{
			if (parentP->_left == parent)
			{
				parentP->_left = SubL;
				SubL->_parent = parentP;
			}
			else
			{
				parentP->_right = SubL;
				SubL->_parent = parentP;
			}
		}
	}

插入函数整体代码实现:

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			//如果是个空树就将新节点作为根节点
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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);
		cur->_col = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;
		//处理
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			// g
			//p  u结构
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					//变色处理
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑-》旋转加变色
				{
					if (cur == parent->_left)
					{
						//cur为parent的左子结点时进行右旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						//cur为父亲的右子节点时对g进行左单旋
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//双旋 -》 右左双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

五、红黑树相关接口的实现

查找函数的实现:

依旧是根据二叉搜索树的查找规则:

//查找函数
	Node* Find(const K& key)
	{
		if (_root == nullptr)
			return nullptr;
		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;
	}

容量高度相关:

返回有效节点个数:

//有效节点个数
	int Size()
	{
		_Size(_root);
	}

    int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

二叉树高度:

//平衡二叉树高度
	int Height()
	{
		_Height(_root);
	}

    int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

中序遍历:

//中序遍历
	void Inorder()
	{
		_inorder(_root);
		cout << endl;
	}

    void _inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_inorder(root->_right);
	}

六、红黑树的验证

我们要通过代码来验证我们所实现的红黑树是否是符合红黑树的性质的。

红黑树的验证分为以下两步:

  1. 验证其是否满足二叉搜索树也就是中序遍历是否有序
  2. 验证其是否满足红黑树的性质

验证代码:

1、验证是否为二叉搜索树:

//中序遍历
	void Inorder()
	{
		_inorder(_root);
		cout << endl;
	}

    void _inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_inorder(root->_right);
	}

2、验证是否满足红黑树的性质:

//判断是否为红黑树
	bool IsBalance()
	{
		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, const int refNum)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
		{
			blackNum++;
		}

		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	} 

通过以上几段代码就能完成对我们实现的红黑树验证。

七、红黑树与AVL数的比较

红黑树和AVL树都是平衡二叉搜索树, 进行增删查改的操作时的时间复杂度都是O(logN), 红黑树不追求绝对平衡, AVL树追求绝对平衡,相对而言红黑树在维持平衡时的旋转次数要少于AVL树, 因此在经常进行插入和删除的结构中红黑树的性能一般要优于AVL树, 因此是实际中使用红黑树更多。

八、红黑树的应用

  1. C++ STL库中的map/set 、mutil_map/mutil_set的实现
  2. java库
  3. linux内核

不止以上三处使用, 这里仅是为了举例。


关于红黑树的介绍到这里就结束了, 希望大家通过这篇文章能都红黑树有一定的理解, 以下是红黑树的具体代码实现链接:

红黑树具体实现代码

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

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

相关文章

黑马头条vue2.0项目实战(九)——编辑用户资料

目录 1. 创建组件并配置路由 2. 页面布局 3. 展示用户信息 4. 修改昵称 5. 修改性别 6. 修改生日 7. 修改头像 7.1 图片上传预览 7.2 使用纯客户端的方式处理用户头像上传预览 7.3 头像裁切 7.4 纯客户端的图片裁切上传流程 7.5 Cropper.js 图片裁剪器的基本使用 …

盘点.软件测试工程师常见的面试题。面试官说我要定你了!

今天我们来聊聊软件测试工程师的面试题有哪些&#xff0c;2024年的8月份马上就过完了&#xff0c;金九银十就业季&#xff0c;跳槽季马上又要来了&#xff0c;毕竟在面试的战场上&#xff0c;知己知彼方能百战不殆。嗯嗯&#xff0c;不错说的真好&#xff01; 1、您所熟悉的测试…

24/8/15算法笔记 项目练习冰湖

import gym from matplotlib import pyplot as plt %matplotlib inline import os os.environ[SDL_VIDEODRIVER]dummy #设置环境变量 SDL_VIDEODRIVERdummy 是在使用基于 SDL (Simple DirectMedia Layer) 的应用程序时&#xff0c;告诉应用程序不使用任何视频驱动程序。这通常…

云计算实训29——mysql主从复制同步、mysql5.7版本安装配置、python操作mysql数据库、mycat读写分离实现

一、mysql主从复制及同步 1、mysql主从自动开机同步 2、配置mysql5.7版本 mysql-5.7.44-linux-glibc2.12-x86_64.tar 启动服务、登录 对数据库进行基本操作 3、使用python操纵mysql数据库 4、编辑python脚本自动化操纵mysql数据库 二、mycat读写分离实现 1.上传jdk和mycat安装…

【C语言】双链表

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、什么是双链表&#xff1f; 二、双链表温习 1. 双链表的结构…

Windows 11 Build 27686 上手体验:2TB FAT32、更好的沙盒等功能

Windows 11 Build 27686 现已在 Insider Program 的 Canary 频道发布&#xff0c;其中包含一些有趣的内容。该版本确认了微软更改 FAT32 大小限制的计划&#xff0c;并将其大小限制从 32GB 提高到 2TB。与此同时&#xff0c;沙盒也得到了改进。让我们来详细了解一下此次更新。 …

Unity 麦扣 x 勇士传说 全解析 之 怪物基类(2)(附各模块知识的链接,零基础也包学会的牢弟)(案例难度:★★☆☆☆)

1.怪物的动画逻辑一览 2.怪物的受伤死亡逻辑一览 using System.Collections; using System.Collections.Generic; using System.Xml; using UnityEngine;public class Monster : MonoBehaviour {[Header("速度")]public float normalSpeed;public float chaseSpeed;…

Xinstall神器来袭:落地页与App无缝衔接,用户转化不再是难题

在移动互联网时代&#xff0c;App的推广与运营成为了各行各业的关键一环。然而&#xff0c;许多推广者在落地页跳转App这一环节上遇到了不小的挑战。用户点击落地页后&#xff0c;往往需要经过繁琐的步骤才能跳转到App&#xff0c;这不仅降低了用户体验&#xff0c;还严重影响了…

红明谷CTF 2022

MissingFile 前置知识 DPAPI&#xff1a; 全称&#xff1a;Data Protection Application Programming Interface DPAPI blob 一段密文&#xff0c;可以使用Master Key对其解密 Master Key 64字节&#xff0c;用于解密DPAPI blob&#xff0c;使用用户登录密码、SID和16字…

周易测算系统前景分析

周易测算系统作为一种结合传统文化与现代技术的服务平台。其市场前景分析如下&#xff1a; 市场需求增长&#xff1a;随着人们对传统文化的重视和对个性化服务的追求&#xff0c;周易测算系统市场需求正逐渐增长。特别是在互联网的背景下&#xff0c;周易预测服务与互联网的结…

java: 错误: 不支持发行版本

报这样的错误大概率是 idea 中的版本配置不统一导致的 通过确保四个位置的版本一致&#xff0c;解决版本不同的问题 第一个位置&#xff1a;Project ,确保 SDK 和 Project language level 的版本适配 第二个位置&#xff1a;Modules,确保需要运行的模块的 Language level 正确…

本地下载安装WampServer结合内网穿透配置公网地址远程访问详细教程

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…

AI浪潮下的教育革新:把握机遇,拥抱变化!

在全球范围内&#xff0c;AI技术正以前所未有的速度改变着我们的生活和工作方式。据麦肯锡数据预测&#xff0c;到2045年左右&#xff0c;有50%的工作将被AI替代。与此同时&#xff0c;具有创造力、深度思考的高阶智力的人才&#xff0c;将享受到AIGC带来的效率优势&#xff0c…

Maven的依赖范围

依赖的jar包&#xff0c;默认情况下&#xff0c;可以在任何地方使用&#xff0c;可以通过scope来设置作用范围 作用范围&#xff1a; 主程序范围有效&#xff08;main文件夹范围内&#xff09;测试程序范围有效&#xff08;test文件夹范围内&#xff09;是否参与打包运行&…

HTML静态网页成品作业(HTML+CSS)——古诗词网设计制作(5个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码CSS部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有5个页面。 &#x1…

产线一直在用的 RabbitMQ 搭建教程(含负载均衡配置,验证脚本,监控案例),偷偷抄出来的,建议收藏备用

本文介绍公司一直在用的 rabbitmq 集群安装部署过程&#xff0c;版本不算太新&#xff0c;但一直稳定运行&#xff0c;对其他版本安装也有一定的参考价值&#xff0c;建议收藏备用。 简介 官网&#xff1a;https://www.rabbitmq.com/ RabbitMQ 是一个开源的遵循 AMQP(Advance…

超详细的Vue新手向教程

一&#xff0c;前言 本篇文章的正文部分为渐近式介绍Vue基础中的OptionAPI和相关指令&#xff0c;在两大内容之外&#xff0c;本文结尾处会附上vue的特性。 ps:本文总字数过22000&#xff0c;足够详细&#xff0c;尽量新手向的同时也包含一些原理性知识&#xff0c;部分内容…

TILs 评分:TCGA 肿瘤浸润淋巴细胞病理切片深度学习评分!图片下载与可视化

生信碱移 病理切片的TILs评分 TCGA 数据库是最大的肿瘤组学公开数据库之一。尽管如此&#xff0c;更多的研究往往仅局限于关注 TCGA 中各类肿瘤样本的上游组学信息或基本病理特征&#xff0c;而忽略了对样本数字化 H&E 病理染色图像的进一步应用。 ▲ TCGA中肿瘤样本的病…

Centos系统中创建定时器完成定时任务

Centos系统中创建定时器完成定时任务 时间不一定能证明很多东西&#xff0c;但是一定能看透很多东西&#xff0c;坚信自己的选择&#xff0c;不动摇&#xff0c;使劲跑&#xff0c;明天会更好。 在 CentOS 上&#xff0c;可以使用 systemd 定时器来创建一个每十秒执行一次的任务…

拟合与插值|线性最小二乘拟合|非线性最小二乘拟合|一维插值|二维插值

挖掘数据背后的规律是数学建模的重要任务&#xff0c;拟合与插值是常用的分析方法 掌握拟合与插值的基本概念和方法熟悉Matlab相关程序实现能够从数据中挖掘数学规律 拟合问题的基本提法 拟合问题的概念 已知一组数据(以二维为例)&#xff0c;即平面上n个点 ( x i , y i ) …