红黑树——插入底层实现【C++】面试重灾区!!

news2024/11/9 2:39:12

目录

前言

一,概念

定义 

二,insert

情况一:

情况二:

情况三:

insert代码

三, 红黑树验证(面试题)

产生随机数验证


每日一图区:

前言

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即$log_2 (N)$。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

 红黑树相对于AVL树的优势包括:

  1. 插入和删除操作更快:红黑树相对于AVL树的平衡条件更加宽松,因此在插入和删除节点时需要进行的旋转操作更少。这使得红黑树的插入和删除操作更快。

  2. 更好的平衡性能:红黑树的平衡性能比AVL树稍差,但是在实际应用中,红黑树的平衡性能已经足够好了。红黑树的插入和删除操作相对较快,这在某些场景下更重要。

  3. 更少的旋转操作:红黑树的旋转操作比AVL树少。旋转操作是一种比较耗时的操作,因此红黑树的插入和删除操作相对更快。

  4. 更好的空间效率:红黑树相对于AVL树需要更少的额外空间来存储平衡因子或颜色信息。这使得红黑树的空间效率更高。

  5. 更广泛的应用:红黑树相对于AVL树应用更广泛。红黑树在很多语言的标准库中都有实现,而AVL树的应用相对较少。

需要注意的是,红黑树和AVL树都是平衡二叉搜索树,选择使用哪种树结构取决于具体的应用场景和需求。

一,概念

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

红黑树的性质:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

其中,NIL表示一个路径的出口。

定义 

enum color{RED, BLACK};

template <class data_type>
struct RBT_Data
{
	data_type _kv;
	RBT_Data<data_type>* left = nullptr;
	RBT_Data<data_type>* right = nullptr;
	RBT_Data<data_type>* parent = nullptr;
	color _col;  // 颜色

	RBT_Data(const data_type& p)
		:_kv(p)
		,_col(RED) // 颜色默认红
	{}
};

template <class data_type>
class RB_Tree
{
	typedef RBT_Data<data_type> RBT_Data;
	RBT_Data* root;
};

关于,创建新节点该怎么选颜色。

  • 如果我们选择黑色,那么我们一定会违反性质四(对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 ),我们为了保持红黑树的结构,就需要调整其他所有的路径。
  • 如果我们选择红色,我们可能会违法性质三(如果一个节点是红色的,则它的两个孩子结点是黑色的 ),而且影响的只是局部,不会影响其他的兄弟树。

因此我们会选择红色作为默认颜色

在AVL树中我们关注树之间的高度,而到了红黑树我们需要关注节点之间的颜色

二,insert

 插入新节点,我们需要检测新节点是否破坏红黑树的性质。

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

情况一:

特点:uncle存在且为红,parent为红色,cur也为红

 用一个模板图总结该情况:

情况二:

特征:没有uncle, parent为红, cur为红

所以在情况二下,比较重要的就是旋转方法+变色,旋转如果有忘记了,可以参考本篇文章:

保姆级认识AVL树【C++】(精讲:AVL Insert)-CSDN博客

情况三:

特征:没有uncle或者uncle是黑色,parent为红,cur为红。相比较于情况二,情况三的旋转方法是双旋。

这里有一个区分情况二与情况三的小技巧,那就是看grandfather , parent, cur 三节点的线路。如果是直线,则情况二; 折线则情况三

insert代码

bool insert(const pair<K, V>& p)
	{
		RBT_Data* new_a_d = new RBT_Data(p);
		if (!root)
		{
			root = new_a_d;
			root->_col = BLACK;	
		}
		else
		{
			RBT_Data* cur = root;
			RBT_Data* parent = nullptr;
			while (cur)
			{
				if (p.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->left;
				}
				else if (p.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->right;
				}
				else
				{
					delete(new_a_d); // 插入失败,删除新建结点
					return false;
				}
			}
			if (p.first < parent->_kv.first)
			{
				parent->left = new_a_d;
			}
			else
			{
				parent->right = new_a_d;
			}
			new_a_d->parent = parent;

			// 调整颜色
			cur = new_a_d;
			RBT_Data* par = cur->parent;
			if (cur == root)
			{
				cur->_col = BLACK;
			}

			while (par && par->_col == RED)
			{
				RBT_Data* gf = par->parent;
				RBT_Data* uncle = nullptr;
				if (gf && par == gf->right)
				{
					uncle = gf->left;}
				else if (gf && par == gf->left)
				{
					uncle = gf->right;}
				else
				{
					assert(false);}

				if ( uncle && uncle->_col == RED)// 有u且为红
				{
					gf->_col = RED;
					uncle->_col = BLACK;
					par->_col = BLACK;

					cur = gf;  // 切换为祖先,进入循环向上
					par = cur->parent;
				}
				else if (!uncle ||
					(uncle && uncle->_col == BLACK))
				{   // 情况2 + 3,判断,是否是折线还是直线
					if (gf->left == par && par->left == cur)
					{  // 右单选
						RotateR(gf);
					}
					else if (gf->right == par && par->right == cur)
					{  // 左单旋
						RotateL(gf);
					}
					else if (gf->left == par && par->right == cur)
					{  // 需要左双旋
						RotateLR(gf);
					}
					else if (gf->right == par && par->left == cur)
					{  // 需要右双旋
						RotateRL(gf);
					}
					else
					{
						assert(false);
					}
					break;
				}
				else
				{
					assert(false);
				}
			}

			if ( root->_col == RED)
			{
				root->_col = BLACK;
			}
			return true;
		}
}

左,右和双旋实现代码跟AVL章节中大差不差,这里给出左单旋的实现,大家照葫芦画瓢一下:

void RotateL(RBT_Data* parent)
	{
		assert(parent->right);
		RBT_Data* par = parent;
		RBT_Data* par_R = par->right;
		RBT_Data* par_RL = par->right->left;
		RBT_Data* ppnode = par->parent;

		par->right = par_RL;
		if (par_RL)
			par_RL->parent = par;

		par_R->left = par;
		par->parent = par_R;
		par_R->parent = ppnode;

		if (!ppnode)
		{
			root = par_R;
		}
		else if (ppnode->left == par)
		{
			ppnode->left = par_R;
		}
		else
		{
			ppnode->right = par_R;
		}

		par->_col = RED;
		par_R->_col = BLACK;
	}

三, 红黑树验证(面试题)

验证红黑树性质

目标:

1. 根是否是黑

2. 没有连续红节点

3. 每条路径所经历的黑节点相同。 

代码:

public:
bool IsBalance()
	{
		if (root && root->_col == RED)
		{
			return false;
		}

		int BlackNum = 0; // 所经黑节点的次数
		int standard = 0;  //设置一个最长路径
		RBT_Data* cur = root;
		while (cur)
		{
			if (cur->_col == BLACK)
				standard++;
			cur = cur->left;
		}
		return _IsBalance(root->left, BlackNum, standard) && _IsBalance(root->right, BlackNum, standard);
	}

private:
bool _IsBalance(const RBT_Data* cur, int BlackNum, int standard)
	{
		if (cur == nullptr)
		{
			return true;
		}
		if (cur->_col == BLACK)
			BlackNum++;

		if (cur->_col == RED && cur->_col == cur->parent->_col)
		{
			return false;
		}

		return  _IsBalance(cur->left, BlackNum, standard) && _IsBalance(cur->right, BlackNum, standard);
	}

产生随机数验证

void Random_Test()
{
	srand(time(0));
	const size_t N = 10000000;
	RB_Tree<int, int> t;
	for (size_t i = 0; i < N; i++)
	{
		size_t x = rand();
		t.insert(make_pair(x, x));
	}

	cout << t.IsBalance() << endl;
}

四,删除

红黑树的删除本节不做讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》 http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

下期预告:用红黑树封装map与 set

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

IM 系统通信系统是什么

IM&#xff08;Instant Messaging&#xff09;系统是一种实时通信系统&#xff0c;允许用户通过互联网或内部网络即时发送文本消息、文件、音频、视频和其他类型的数据给其他用户或群组。这类系统通常支持在线状态显示、消息确认、消息历史记录和多端同步等功能&#xff0c;以提…

WEB3 在 React搭建的Dapp中通过redux全局获取并存储用户ETH与自定义token与交易所存储数量

上文 web3 在React dapp中全局管理web3当前登录用户/智能合约等信息中 我们简单操作&#xff0c;将web3的公共信息都存在了window对象上 然后 我们先来启动一下环境 终端输入 ganache -d打开项目 终端输入 truffle migrate --reset在区块链上发布一下智能合约 然后 我们在…

FedAT:分层机制更新的联邦学习

文章链接&#xff1a;FedAT: A Communication-Efficient Federated Learning Method with Asynchronous Tiers under Non-IID Data 发表会议: SC’21 (International Conference for High Performance Computing, Networking, Storage, and Analysis) 高性能计算&#xff0c;体…

Redis安装与常用命令

目录 一、Reids简介 二、Redis安装 2.1 Linux安装 2.2 Windows安装 三、Redis常用命令 3.1 Redis字符串 3.2 Redis哈希(Hash) 3.3 Redis列表&#xff08;List&#xff09; 3.4 Redis集合&#xff08;Set&#xff09; 一、Reids简介 Redis&#xff08;Remote Dictiona…

11.Z-Stack协议栈使用

f8wConfig.cfg文件 选择信道、设置PAN ID 选择信道 #define DEFAULT_CHANLIST 0x00000800 DEFAULT_CHANLIST 表明Zigbee模块要工作的网络&#xff0c;当有多个信道参数值进行或操作之后&#xff0c;把结果作为 DEFAULT_CHANLIST值 对于路由器、终端、协调器的意义&#xff1…

【Linux网络】Linux网络抓包工具tcpdump

一、tcpdump介绍 tcpdump 是一个Linux的网络抓包工具。它允许用户拦截和显示发送或收到过网络连接到该计算机的TCP/IP和其他数据包。tcpdump 适用于大多数的类Unix系统操作系统(如linux,BSD等)。 二、安装&用法说明 1&#xff09;安装 $ yum -y install tcpdump2&#…

【知网检索征稿】第九届社会科学与经济发展国际学术会议 (ICSSED 2024)

第九届社会科学与经济发展国际学术会议 (ICSSED 2024) 2024 9th International Conference on Social Sciences and Economic Development 第九届社会科学与经济发展国际学术会议(ICSSED 2024)定于2024年3月22-24日在中国北京隆重举行。会议主要围绕社会科学与经济发展等研究…

Everything结合内网穿透搭建在线资料库,一秒实现随时随地访问

Everythingcpolar搭建在线资料库&#xff0c;实现随时随地访问 文章目录 Everythingcpolar搭建在线资料库&#xff0c;实现随时随地访问前言1.软件安装完成后&#xff0c;打开Everything2.登录cpolar官网 设置空白数据隧道3.将空白数据隧道与本地Everything软件结合起来总结 前…

(免费领源码)Java#MYSQL在线学习平台09650-计算机毕业设计项目选题推荐

目 录 摘要 1 绪论 1.1 选题背景及意义 1.2国内外现状分析 1.3论文结构与章节安排 2 在线学习平台系统分析 2.1 可行性分析 2.2 系统业务流程分析 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 在线学习平台总体设计 …

nginx 常用优化配置项教程

本文目录 跨域配置 动静分离 反向代理-负载均衡 配置SLL证书 资源压缩 缓存机制 IP黑白名单 防盗链 大文件传输优化 跨域问题 产生原因 产生跨域问题的主要原因就在于 「同源策略」 &#xff0c;为了保证用户信息安全&#xff0c;防止恶意网站窃取数据&#xff0c;…

每日一题 2103. 环和杆 (简单)

简单题&#xff0c;直接统计就行 class Solution:def countPoints(self, rings: str) -> int:n len(rings) // 2cnt [[0, 0, 0] for _ in range(10)]for i in range(0, 2*n, 2):if rings[i] "R":cnt[int(rings[i 1])][0] 1if rings[i] "G":cnt[i…

阿里云多款ECS产品全面升级 性能最多提升40%

“阿里云始终围绕‘稳定、安全、性能、成本、弹性’的目标不断创新&#xff0c;为客户创造业务价值。”10月31日&#xff0c;杭州云栖大会上&#xff0c;阿里云弹性计算计算产品线负责人张献涛表示&#xff0c;通过持续的产品和技术创新&#xff0c;阿里云发布了HPC优化实例等多…

conda命令克隆(复制)环境方法及问题解决

背景&#xff1a;与同事进行工作交接&#xff0c;在服务器上想直接拷贝他的环境过来&#xff0c;直接复制anaconda3/envs里的环境包过来后续安装包的时候由于路径是原来的路径会安装到对方环境里&#xff0c;因此使用conda命令来进行环境克隆。 &#xff08;Linux/服务器中&am…

【广州华锐互动】有机化合物结构3D虚拟展示帮助学生更好地理解和掌握复杂化学知识

随着科技的发展&#xff0c;我们的学习方式正在发生深刻的变化。其中&#xff0c;有机化合物结构3D虚拟展示作为一种新兴的教学方式&#xff0c;正在逐渐被广泛接受和应用。这种新型的展示方式对于提高学生的学习效率&#xff0c;增强学习体验&#xff0c;以及推动化学教育的发…

十六章反射与注解总结

16.1 反射 反射&#xff08;Reflection&#xff09;是指在运行时获取类的信息&#xff0c;并可以动态调用类的方法、访问或修改类的属性&#xff0c;以及构造对象的能力。 Java的反射提供了一套API&#xff0c;允许你在运行时检查类的结构、调用类的方法、获取和设置类的属性&…

curl(一)基础

一 基础入门 ① 本文讲解curl的版本 curl支持的协议 国密curl ② 升级curl 后续&#xff1a; 注意新版本的新特性备注&#xff1a; 升级的必要性 yum 升级 curl 源码编译安装curl ③ curl排错参数 1、-v 或 --verbose --> 重点 2、--trace 和 --trace-ascii --…

小红书薯多功能引流脚本,精准引流工具,内置私信点赞关注评论回复各项功能【引流软件+引流教程】

软件介绍&#xff1a; 多个高级功能精准引流 精准留痕引流&#xff0c;功能非常多 设备需求&#xff1a; 适用于安卓7.0版本&#xff0c;鸿蒙&#xff0c;模拟器均可使用 文章分享者&#xff1a;Linxiaoyu2022 文章的来源&#xff1a;vipwz.blog.csdn.net 有任何互联网问…

Java作业二

一、使用方法编写求圆面积和周长的程序&#xff0c;运行时提示输入圆半径&#xff0c;然后输出计算结果。运行效果如下图所示&#xff1a; import java.util.Scanner;public class Test {public static void main(String[] args) {Scanner input new Scanner(System.in);Syste…

安卓抓包之小黄鸟

下载安装 下载地址: https://download.csdn.net/download/yijianxiangde100/88496463 安装apk 即可。 证书配置:

SQL INNER JOIN 关键字(内部连接)

SQL INNER JOIN 关键字&#xff08;内部连接&#xff09; 内部链接INNER JOIN关键字选择两个表中具有匹配值的记录。 SQL INNER JOIN 语法 SELECT column_name(s) FROM table1 INNER JOIN table2 ON table1.column_name table2.column_name; 注释&#xff1a;INNER JOIN 与 …