C++红黑树的底层原理及其实现原理和实现

news2024/11/13 14:25:56

小编在学习完红黑树之后,发现红黑树的实现相对于AVL树来说会简单一点,并且大家在学了C++中的set和map容器之后,会明白set和map的容器的底层就是运用的红黑树,因为相对于AVL树,红黑树的旋转次数会大大减少,并且也会保持树的相对的平衡,相对于搜索二叉树,也会大大的提高查找效率,所以在此请大家跟着小编一起学习今天的内容红黑树!~~~~

在此学习之前大家应该先要明白红黑树的底层原理和红黑树的构造

一、红黑树的底层原理

先附上一张图让大家知道红黑树的构造

上面大家就可以看到红黑树的每个节点都是一红一黑这样排序,并且红色节点不可以连续出现超过两次,好滴,了解到这块就可以告诉大家红黑树的底层原理了,为面了解红黑树和实现红黑树做铺垫。

底层原理:

这个思考题大家可以想一想为什么红黑可以保证这样的特性,我在下面给大家讲解:

二、红黑树的实现原理

对于红黑树的实现过程,相对于搜索二叉树不同也就是插入过程的调整,让红黑树一直满足它的底层逻辑,不能破坏红黑树的底层结构让它一直维持在最长路径不会超过最短路径的二倍,让它保持平衡,那实现红黑树我们就要新增一个节点的颜色和父节点的指针新增节点的颜色就是为了让它红色的节点不要连续出现,父节点的指针也是为了后续的调整做准备(后面会讲),那有了这些我们该如何实现一颗红黑树呢?在这里我直接告诉大家:

实现:每次插入一个红色节点,但是保证根节点是黑色的,如果插入的过程中,遇到了连续的红色节点,因为不满足红黑树的条件,所以我们要对此进行调整(后面会讲如何调整),调整的过程中需要保证每条路径的黑色节点个数相同并且把连续的红色节点调整开来,这样就可以达到调整的目的,并且保持红黑树的特性

三、红黑树的实现

1、大家先来实现一下这个红黑树的基础结构,在红黑树的实现过程中相对于搜索二叉树我们加入了节点的颜色和父节点的指针,代码如下:

enum Color      // 记录红黑树节点颜色的枚举类型
{
	RED,
	BLACK
};

template<class T1, class T2>
struct RBTreeNode
{
	pair<T1, T2> _data;
	struct RBTreeNode<T1, T2>* _left;
	struct RBTreeNode<T1, T2>* _right;
	struct RBTreeNode<T1, T2>* _parent;   // 存的是当前节点的父节点
	int col;

	RBTreeNode(const pair<T1, T2>& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, col(RED)
		, _parent(nullptr)
	{}
};

2、接下来我们来实现一下红黑树的插入过程,也是最重要的过程,在不断插入数据的同时我们需要做到每次插入的节点应该是合法的,如果不合法的话,就需要我们做到调整来保证这个红黑树的特性不变。那该如何调整呢,这里有三种情况需要我们逐个分析,我把三种情况都放到下面供大家参考和学习:

在开始学习时先告诉大家这里调整会用到的小知识:

a、uncle节点存在并且为红色节点

b、uncle节点存在并且为黑色 或者 uncle节点不存在

调整完之后如果不符合红黑树的特点仍然向上继续调整,不过此时cur节点变为parent节点

c、需要进行双旋的操作,并且黑色节点存在并且为黑 或者 黑色节点不存在

调整完之后如果不符合红黑树的特点,仍然向上继续调整,不过此时cur节点变为parent节点

好啦以上就是插入过程中的调整过程,让每次插入之后这颗树仍然符合红黑树的底层逻辑,那接下来大家就知道如何实现插入部分的代码了吧,我把代码和注释放到下面供大家参考和对照:

注意:每次调整完之后如果上面节点不满足红黑树的特性,仍然需要往上继续调整

提醒:这里的旋转过程和AVL树的旋转方法一样,如果大家不明白请看我上篇博客,里面详细讲解了如何单旋和双旋的操作。

	bool Insert(const pair<T1, T2>& x)
	{
		if (_root == nullptr)       // 第一次插入节点 根节点是黑色的
		{
			_root = new Node(x);
			_root->col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data.first < x.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_data.first > x.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				// 走到else说明搜索二叉树里面有这个值,所以就不用插入
				return false;
			}
		}
		if (parent->_left == cur && parent->_data.first > x.first)  // 确定插入的值在哪边
		{
			parent->_left = new Node(x);
			parent->_left->_parent = parent;
			cur = parent->_left;
		}
		else
		{
			parent->_right = new Node(x);
			parent->_right->_parent = parent;
			cur = parent->_right;
		}
		while (parent && parent->col == RED)                    // 如果插入的值的父节点是红色 说明就需要调整
		{                                             // 并且此时肯定存在祖父节点 因为根节点必须为红色
			Node* grandfather = parent->_parent;      // 通过祖父节点确定叔叔的位置
			Node* uncle = nullptr;
			if (grandfather->_left == parent)
				uncle = grandfather->_right;
			else
				uncle = grandfather->_left;

			if (uncle && uncle->col == RED)           // 情况一  叔叔存在并且为红色
			{
				parent->col = uncle->col = BLACK;
				grandfather->col = RED;
				// 节点继续向上更新
				cur = grandfather;
				parent = cur->_parent;
			}
			else if ((uncle && uncle->col == BLACK) || uncle == nullptr)    // 情况二 叔叔存在且为黑色 或者叔叔不存在
			{
				if (grandfather->_left == parent && parent->_left == cur)
				{
					RotateR(grandfather);
					parent->col = BLACK;
					grandfather->col = RED;
					break;
				}
				else if (grandfather->_right == parent && parent->_right == cur)
				{
					RotateL(grandfather);
					parent->col = BLACK;
					grandfather->col = RED;
					break;
				}
				else if (grandfather->_left == parent && parent->_right == cur)
				{
					RotateL(parent);
					parent = grandfather->_left;
					cur = parent->_left;
				}
				else if (grandfather->_right == parent && parent->_left == cur)
				{
					RotateR(parent);
					parent = grandfather->_right;
					cur = parent->_right;
				}
				else
					assert(false);
			}
			else
				assert(false);
			_root->col = BLACK;
			
		}
		return true;
	}

好啦,上面就是红黑树中最难的插入节点的部分,调整部分相信大家看完一定会收获满满,如果哪里还不明白可以私信我或者评论区留言。

3、接下来就是红黑树的销毁和遍历部分,这部分和搜索二叉树一样,我就不在这里做详细讲解了,大家如果有什么不理解的可以评论区留言或者私信我,我帮大家解答,代码及注释我就放到下面了,供大家参考:

	// 查找二叉树的中序遍历刚好是顺序排序
	void InOrder()
	{
		// 中序遍历需要递归来解决,所以这个 _root 不好传 如果直接用_root 的话 _root 就会被改变
		Node* cur = _root;
		_InOrder(cur);
		cout << endl;
	}

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

	~RBTree()
	{
		// 后序删除空间  左 右 根
		_RBTreeDestory(_root);
		_root = nullptr;
	}
	void _RBTreeDestory(Node* root)
	{
		if (root == nullptr)
			return;
		_RBTreeDestory(root->_left);
		_RBTreeDestory(root->_right);
		delete root;
	}

4、为了验证自己写的红黑树是否满足它的特性,我在这里已经写好了验证方式的代码,我把它传到下面,大家写完自己的红黑树可以用下面的代码测试一下是否为一颗满足条件的红黑树,代码及注释:


	bool IsRBTree()
	{
		Node* root = _root;
		if (_root->col == RED)         // 性质一:根节点是黑色
			return false;

		// 性质二:每条路径上的黑色节点数相同
		int BKNum = 0;
		while (root)                   // 先计算一条路径上的黑色节点
		{
			if (root->col == BLACK)
				BKNum++;
			root = root->_left;
		}

		// 性质三:红色节点不能连续出现

		return __IsRBTree(_root, BKNum);
	}

bool __IsRBTree(Node* root, int k)
	{
		// 前序遍历判断黑色元素个数
		if (root == nullptr)
			return k == 0;

		if (root->col == BLACK)
			k--;

		if (root->col == RED)                // 如果此节点是红色,那他的父节点就不能为红色
		{
			if (root->_parent->col == RED)
				return false;
		}

		__IsRBTree(root->_left,k);
		__IsRBTree(root->_right,k);
	}

好啦,以上就是今天的红黑树的所有内容,相信大家一定会有所收获,如果还有疑惑的话,评论区可以留言或者私信我,我来帮大家节点,好啦,下期再见各位!~~~

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

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

相关文章

MySQL笔记(大斌)

乐观锁和悲观锁是什么&#xff1f; 数据库中的并发控制是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观锁和悲观锁是并发控制主要采用的技术手段。 悲观锁&#xff1a;假定会发生并发冲突&#xff0c;会对操作的数据进行加锁&a…

好的渲染农场应该具备哪些功能?

对于3D艺术家和工作室来说&#xff0c;渲染往往是制作过程中最耗时的部分。这一关键阶段需要强大的计算资源和高效的工作流程&#xff0c;以确保生产时间表得以满足。一个好的渲染农场对于提高生产力和确保项目在不牺牲质量的情况下按时完成至关重要。随着对详细3D视觉效果的需…

UEFI——PCD的简单使用

一、PCD的定义及概念 在UEFI固件接口中&#xff0c;PCD&#xff08;Platform Configuration Database&#xff09;是一个用于存储和访问平台特定配置信息的机制。PCD允许UEFI驱动程序和应用程序在运行时获取和设置平台相关的参数&#xff0c;而无需硬编码这些值。PCD变量可以被…

计算机毕业设计推荐-基于Java的网上电子图书管理系统【Java-python-大数据定制】

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于Java的网上电子图书管理…

魏牌蓝山智驾版,长城的智能化「大反攻」

‍作者 |老缅 编辑 |德新 8月下旬&#xff0c;魏牌全新蓝山上市&#xff0c;定位「长城首款NOA智能六座旗舰SUV」。 新车分智驾Max和智驾Ultra两个版本&#xff0c;售价分别为29.98万元和32.68万元。 魏建军为蓝山的上市&#xff0c;时隔6年再度回到了发布会的舞台&#xff…

时序预测基础模型又中顶会!真心建议各位往这个方向发论文

时序领域又有新突破啦&#xff01;谷歌最新提出TimesFM&#xff0c;仅需200M参数&#xff0c;零样本预测性能超越有监督&#xff01;成功入选ICML 2024&#xff01; TimesFM是一种全新的时间序列通用基础模型&#xff0c;这类模型相比传统时序模型&#xff0c;拥有整合和利用广…

HDLC 和 PPP 原理与配置

HDLC简介 HDLC协议是一种通用的协议&#xff0c;工作在OSI参考模型的数据链路层。数据报文加上头开销和尾开销后封装成HDLC帧。 HDLC具有以下特点&#xff1a; •HDLC协议只支持点到点链路&#xff0c;不支持点到多点。 •HDLC协议不支持IP地址协商&#xff0c;不支持认证。 •…

【数据结构-二维前缀和】【列维护优化】力扣3212. 统计 X 和 Y 频数相等的子矩阵数量

给你一个二维字符矩阵 grid&#xff0c;其中 grid[i][j] 可能是 ‘X’、‘Y’ 或 ‘.’&#xff0c;返回满足以下条件的 子矩阵 数量&#xff1a; 包含 grid[0][0] ‘X’ 和 ‘Y’ 的频数相等。 至少包含一个 ‘X’。 示例 1&#xff1a; 输入&#xff1a; grid [[“X”,“…

用相图分析 bbr,inflight 守恒的收敛速度

以下的代码绘制了 bbr 的收敛相图&#xff1a; #!/opt/homebrew/bin/python3import numpy as np import matplotlib.pyplot as plt from scipy.integrate import odeintdef model(vars, t, C, g):x, y varsdxdt C * (g * x) / (g * x y) - xdydt C * (g * y) / (g * y x)…

读懂以太坊源码(1)-目录结构说明

要了解一个软件工程项目的代码&#xff0c;必须从代码的目录结构入手&#xff0c;从而大致了解软件实现的功能模块&#xff0c;使用了哪些相关的技术&#xff0c;大概的框架是怎么样的&#xff1f; 源码网址&#xff1a;https://github.com/ethereum/go-ethereum 以下是以太坊…

如何提升网站在Google的排名?

事实上&#xff0c;常规提升排名的方法无非就那么几种&#xff0c;关键词优化&#xff0c;高质量内容&#xff0c;网站结构优化&#xff0c;外链&#xff0c;确保网站没问题&#xff0c;这些都是常规的提升排名的方法&#xff0c;只能说没什么特别的&#xff0c;而除了这些常规…

流量焦虑?随身WiFi来救场!2024好的随身WiFi怎么挑,看这一篇文章就够了!包教会你识别随身WiFi哪个好!

相信大家买随身WiFi肯定是想要网速快&#xff0c;并且多用几年&#xff0c;那么在全网铺天盖地的广告、水军、好评的情况下&#xff0c;随身WiFi的品质好坏&#xff0c;我们该如何辨别呢&#xff1f; 主要看三个指标就能轻松分别&#xff0c;记得先收藏再观看&#xff01;一篇…

HIS系统|HIS系统开发源码

在数字医疗时代&#xff0c;医院信息系统&#xff08;HIS&#xff09;的开发至关重要。本文将深入探讨在开发HIS系统时需要关注的主要事项&#xff0c;从系统架构到数据安全&#xff0c;为医疗机构提供实用的开发指南。 1、需求分析与系统规划 在开发HIS系统的初期&#xff0c…

rknntoolkitlite2环境搭建

目录 前言 0、要下载的软件包 一、环境搭建步骤 1.1 安装Miniconda 1.2创建RKNN虚拟环境 1.3 安装rknntoolkitlite2软件包 1.4 安装opencv 前言 RKNN Toolkit Lite2 工具支持运行在 RK3568: Debian10/Debian11&#xff08;aarch64&#xff09;、Ubuntu20/22&#xff08;…

【微信小程序】自定义 tabBar

一、自定义 tabBar 1、案例效果 首先来看一下页面演示效果&#xff0c;页面中有下方标签栏是自定义 tabBar。自定义 tabBar 可以让开发者更加灵活地设置 tabBar 样式&#xff0c;以满足更多个性化的场景。 在此案例中&#xff0c;用到的主要知识点如下&#xff1a; 自定义组…

Spring 事务传播和自调用行为

为了方便讲解&#xff0c;这里的A、B、C类都是Spring管理的Bean。 自调用行为 自调用行为示例 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component…

【Python报错已解决】“ModuleNotFoundError: No module named ‘torch_scatter‘”

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述1.1 报错示例&#xff1a;1.2 报错分析&#xff1a;1.3 解决思路&#xff1a; 二、解决…

【golang-入门】环境配置、VSCode开发环境配置

golang介绍基础信息 windows环境配置安装包下载安装环境变量设置检查 VSCode开发配置插件配置在 Visual Studio Code 中安装通义灵码go hello word 参考资料 golang介绍 基础信息 golang官网&#xff1a;https://go.dev/golang学习网&#xff1a;https://studygolang.com/使用…

本地服务器使用Docker搭建Nacos动态服务管理平台并实现远程访问

文章目录 前言1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Nacos 前言 本文主要介绍如何本地部署动态服务发现、配置管理和服务管理平台 Nacos &#xff0c;并结合…

WCDMA 辅同步信号S_SCH介绍,MATLAB实现

本期主要介绍一下WCDMA辅同步信号S_SCH实现和映射&#xff0c;从公式生成开始介绍&#xff0c;最后用MATLAB实现&#xff0c;让大家了解对比一下3G时代辅同步信号和前面介绍的4G、5G和2G时代的辅同步信号共同点和不同点&#xff0c;不管在什么时候辅同步信号都要遵循一个码要正…