hello树先生——红黑树

news2024/9/19 10:40:10

红黑树

  • 一.什么是红黑树
  • 二.红黑树的实现
    • 1.创建树节点结构
    • 2.插入功能的实现
  • 三.提供一些常见二叉树接口
  • 四.进行平衡测试

一.什么是红黑树

在这里插入图片描述

红黑树是一种自平衡的二叉搜索树,具有以下特性:

  • 节点颜色:每个节点要么是红色,要么是黑色。
  • 根节点:根节点始终是黑色。
  • 红色节点:红色节点的子节点不能是红色(即没有两个连续的红色节点)。
  • 黑色节点:从任何节点到其每个叶子节点的路径上,必须包含相同数量的黑色节点。
  • 叶子节点:所有叶子节点(空节点)都是黑色。
    红黑树的这些特性确保了树的高度是对数级别,从而保证了基本操作(如插入、删除和查找)的时间复杂度为O(log n)。这种结构常用于实现关联数组和集合等数据结构。

相较于AVL树,他的高度可能会更高,但由于没有那么多严格旋转操作,所以插入效率会略高

二.红黑树的实现

1.创建树节点结构

由于我们通过树节点的颜色来区分,是否平衡,所以提前定义一下红黑颜色,这里我们使用枚举法定义颜色。

enum color
{
	RED,
	BLACK
};

节点结构类似于AVL树,三个指针链接和pair类型数据,唯一加了一个颜色特征,插入节点默认红色。

template<class K,class V>
struct RBTreeNode
{
	pair<K, V>  _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	color _col;
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_col(RED),
		_kv(kv)
	{}
	
};

2.插入功能的实现

bool Insert(const pair<K, V>& kv)

插入分为两种大情况,父亲为黑色或红色,如果黑色那么我们插入新节点就不存在破坏规则,如果是红色,则需要进行调整。

  • 如果当前树无节点则,插入根节点

			if (_root == nullptr)
			{
				node* cur = new node(kv);
				_root = cur;
				_root->_col = BLACK;
				return true;
			}
  • 寻找插入位置,并记录父节点位置
//寻找节点
			node* parent = nullptr;
			node* cur = _root;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;

				}
				else if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
  • 构造节点并判断是插在左边还是右边,然后进行链接
			cur = new node(kv);
			
			if (parent->_kv.first < kv.first)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			cur->_parent = parent;
  • 如果父节点存在且为红,则需要调整
while(parent && parent->_col == RED)
{
	//调整逻辑
}

若插入节点的叔叔节点存在且为红色,那么仅需变色操作,找到parent的parent将其反色,将parent与uncle反色,之后将cur交给祖父,重复操作判断
在这里插入图片描述

如图为,父亲是左子树的情况,右子树类似
代码先判断父亲是左还是右子树,从而找到叔叔节点

				node* grandparent = parent->_parent;
				if (grandparent->_left == parent)
				{
					node* uncle = grandparent->_right;

					//叔叔存在且为红
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;

						//继续更新判断

						cur = grandparent;
						parent = cur->_parent;
					}
					//叔叔不存在或者为黑
					else
					{
						//下方继续讲解

					}   
				}

若叔叔不存在或存在且为黑,则分为两种子情况,类似与AVL树的左左高和左右高形状
1.左左高,即插入节点在左侧
在这里插入图片描述
首先将其进行祖父为旋转点旋转,之后将父亲和祖父颜色反转

			if (parent->_left == cur)
			{
				//左插
				//		   g
				//		p      u
				// cur
				RotateR(grandparent);
				grandparent->_col = RED;
				parent->_col = BLACK;
			}

2.左右高,将其旋转两次,parent左旋,grandparent右旋
在这里插入图片描述
然后将祖父和cur进行变色
在这里插入图片描述


				//右插
		    	//		   g
				//		p      u
				//        cur
				RotateL(parent);
				RotateR(grandparent);
				grandparent->_col = RED;
				cur->_col = BLACK;

叔叔是黑色或不存在的情况,进行调整后直接跳出break就好

父亲是右子树完全类似,下面不在讲解,提供代码供参考

				else
				{
					node* uncle = grandparent->_left;

					//叔叔存在且为红
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandparent->_col = RED;

						//继续更新判断

						cur = grandparent;
						parent = cur->_parent;
					}
					//叔叔不存在或者为黑
					else
					{
						if (parent->_right == cur)
						{
							//右插
							//		   g
							//		u      p
							//                cur
							RotateL(grandparent);
							grandparent->_col = RED;
							parent->_col = BLACK;
						}
						else
						{
							//右插
							//		   g
							//		u      p
							//        cur
							RotateR(parent);
							RotateL(grandparent);
							grandparent->_col = RED;
							cur->_col = BLACK;
						}
						break;
					}
				}

最后将根节点统一变为黑色,并返回true

			_root->_col = BLACK;
			return true;

如何旋转在AVL树中详细讲解过

三.提供一些常见二叉树接口

求高度,数量,和中序遍历

	   int height()
		{
			return _height(_root);
		}
		int size()
		{
			return _size(_root);
		}
		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);
		}
		int _height(node* root)
		{
			if (root == nullptr)
			{
				return 0;
			}
			return max(_height(root->_left), _height(root->_right)) + 1;
		}
		int _size(node* root)
		{
			if (root == nullptr)
			{
				return 0;
			}
			return root == nullptr ? 0 : _size(root->_left) + _size(root->_right) + 1;
		}

四.进行平衡测试

主要测试这颗红黑树是否符合

  • 根节点为黑色
  • 每条路径黑色节点数量相同
  • 不能连续两个红色节点出现
    这里我们采取从根节点向下递归,加入两个参数,记录任意一条路径下的黑色节点数量,和一个count统计当前路径下黑色节点的数目
		bool IsBalance()
		{
			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);
		}

然后我们随机产生一些随机数测试一番,顺便记录各功能效率

void test2()
{
	RBTree<int, int> a;
	srand((unsigned int)time(0));
	int N = 1000000;
	size_t ret1 = clock();
	for (int i = 0; i < N; i++)
	{
		int num = rand() + i;
		a.Insert({ num,num });
	}
	
	size_t ret2 = clock();
	cout << "insert->time : " << ret2 - ret1 << endl;

	size_t ret3 = clock();
	for (int i = 0; i < N; i++)
	{
		int num = rand() + i;
		a.find(num);
	}
	size_t ret4 = clock();

	cout << "insert->time : " << ret2 - ret1 << endl;
	cout << "find->time : " << ret4 - ret3 << endl;
	cout << "size-> " << a.size() << endl;
	cout << "height-> " << a.height() << endl;
	cout << a.IsBalance() << endl;
}

在这里插入图片描述

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

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

相关文章

从模型到实践:新时代【数学建模竞赛论文】的结构、规范与创新解析

目录 1. 数学建模竞赛论文的重要作用 1.1 论文是竞赛成果的书面形式 1.2 论文是评判参赛成绩的唯一依据 1.3 论文写作是科技论文写作的基本训练 1.4 数学建模竞赛论文的综合性 1.5 数学建模竞赛论文与学术研究的联系 1.6 数学建模竞赛论文的重要性在评委眼中 1.7 数学建…

Leetcode3248. 矩阵中的蛇

Every day a Leetcode 题目来源&#xff1a;3248. 矩阵中的蛇 解法1&#xff1a;模拟 遍历字符串数组 commands&#xff0c;模拟&#x1f40d;的移动过程。 如果最后&#x1f40d;的位置为 (i, j)&#xff0c;则编号为 (i * n) j。 代码&#xff1a; /** lc appleetcode…

[Hive]五、Hive 源码编译

G:\Bigdata\2.hive\大数据技术之Hive源码编译 第1章 部署Hadoop和Hive 1.1 版本测试 Hadoop3.3.6 和Hive3.1.3 运行hive客户端时报错: java.lang.NoSuchMethodError:com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;Ljava/lang/Object;)V 查看Ha…

计算机的错误计算(八十一)

摘要 讨论双曲正弦函数 sinh(x)的计算精度问题。 例1. 计算 sinh(312.08) . 若在Python下计算&#xff0c;则有&#xff1a; 若在Excel单元格中计算&#xff0c;则有&#xff1a; 事实上&#xff0c;16位的正确值是 0.1712347549914620e136&#xff08;ISRealsoft 提供&…

MLLM(一)| 文/图生视频任务大升级,BigModel 开源了视频模型CogVideoX

CogVideoX的体验地址&#xff1a;https://bigmodel.cn/console/trialcenter?modelCodecogvideox 自2021年起&#xff0c;智谱 AI 技术团队便开始着手布局包括 text-2-img、text-2-video、img-2-text、video-2-text 在内的多模态模型&#xff0c;并陆续研发并开源了CogView、Co…

0基础学习爬虫系列:Python环境搭建

1.背景 当前网络资源更新非常快&#xff0c;然后对应自己感兴趣的内容&#xff0c;每天盯着刷网站又太费时间。我在尝试借助Ai&#xff0c;搭建一套自己知识抓取更新提醒的系统&#xff0c;这样可以用极少的时间&#xff0c;关注到自己感兴趣的信息。 其实&#xff0c;这套逻辑…

C++使用日志库经验总结

1、log4cpp日志源文件路径设置 在 Visual Studio 中&#xff0c;C 项目的日志格式可以通过设置项目的属性来调整。如果你想要使用完整路径来显示诊断消息&#xff0c;可以在项目属性中的“C/C”选项卡下的“高级”属性页中找到“使用完整路径”&#xff08;/FC&#xff09;选项…

幂等的通用实现方案

文章目录 一、幂等的概念1.1 什么是幂等1.2 举个例子 二、幂等问题的解决方案2.1 准备&#xff1a;先添加2张表&#xff08;账户表、充值订单表&#xff09;2.2 方案1&#xff1a;update时将status0作为条件判断解决原理源码 2.3 方案2&#xff1a;乐观锁原理源码 2.4 方案3&am…

FMCW雷达介绍以及FMCW雷达测距

调频连续波雷达测距 FMCW radar: Frequency Modulated Continous Wave信号表示形式 调频连续波形式&#xff08;频域/时域&#xff09; 发射信号和接收信号 数学表达式 测距模型 该文章详细介绍了单target和多target场景下的FMCW雷达测距&#xff01;&#xff01;&#xff01;…

Datawhale X 李宏毅苹果书 AI夏令营 Task3打卡

实践方法论 1 模型偏差 1.1 基本概念 模型偏差&#xff08;Model Bias&#xff09;&#xff0c;也称为“偏差误差”或“系统误差”&#xff0c;是指模型预测值与真实值之间的差异&#xff0c;这种差异并不是由随机误差引起的&#xff0c;而是由模型本身的结构或假设导致的。模…

如何在 Raspberry Pi 5 上设置 Raspberry Pi AI Kit

本指南将帮助您在 Raspberry Pi 5 上安装 Raspberry Pi AI Kit。这将使您能够使用 Hailo AI 神经网络加速器运行 rpicam-apps 摄像头演示。 如果您在开始安装人工智能套件之前需要帮助&#xff0c;本指南提供了安装过程的分步图片。 安装人工智能套件&#xff1a;https://www.…

SGM41511电源管理芯片与STM32L496通讯源码虚拟I2C协议实测成功读写cubemx设置裸机和freertos操作系统源码通用

不用它的I2C设置&#xff0c;容易出错不通讯&#xff0c;只打开GPIO输出就可以&#xff1b; 如果是RTOS的话请打开系统定时器提供参考时间基准&#xff0c;那个定时器都行&#xff1b; 以下是经过验证的代码&#xff0c;同样适用于SGM同类系列电源管理芯片&#xff1b; 准备好…

HTML5好看的花店商城源码1

文章目录 1.设计来源1.1 主界面1.2 界面效果11.3 界面效果21.4 界面效果31.5 界面效果41.6 界面效果51.7 界面效果61.8 界面效果7 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcLeigh…

【补-网络安全】日常运维(二)终端端口占用排查

文章目录 一、利用ipconfig、netstat 命令行统计二 、策略封禁IP 引言:检查频繁,第一步我们梳理完资产,第二步应该对资产终端进行一个排查,诊断把脉,了解清楚系统的端口占用及开放情况 一、利用ipconfig、netstat 命令行统计 1.先用ipconfig定位该终端的IP地址 2.明确IP地址后…

9.2(C++ Day 4)

一、作业 完成算术运算符重载&#xff0c;实现至少两个运算符的成员函数和全局函数的版本 1.&#xff08;1&#xff09;成员函数实现算术运算符&#xff08;-&#xff09;重载 const 类名 operator#(const 类名 &R) const {} #include <iostream>using namespac…

三级_网络技术_56_应用题

一、 请根据下图所示网络结构回答下列问题。 1.填写RG的路由表项。 目的网络/掩码长度输出端口__________S0&#xff08;直接连接&#xff09;__________S1&#xff08;直接连接&#xff09;__________S0__________S1__________S0__________S1 (2)在不改变路由表项的前提下&…

mysql安装和使用

文章目录 下载mysqlmysql安装检验mysqlpython、vscode插件 下载mysql 进入官网&#xff0c;选择下载。mysql官网是 https://www.mysql.com/ 。 选择社区版 选择windows版 选择离线包 让你登陆&#xff0c;我们谢绝登录 ojbk。开始下载。 mysql安装 选自定义安装。 “Server…

「漏洞复现」WookTeam searchinfo SQL注入漏洞

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

用Python导入CSV和Excel表格数据到Word表格

在不同格式的文档之间进行数据传输是非常重要的操作。例如将CSV和Excel表格数据导入到Word文档中&#xff0c;不仅可以实现数据的有效整合与展示&#xff0c;还能极大地提升工作效率和文档的专业性。无论是生成报告、制作统计分析还是编制业务文档&#xff0c;熟练掌握用Python…

Java简单实现服务器客户端通信

目录 Socket 概述Socket 通信模型Socket 编程流程DEMO服务器端客户端 在Java中实现服务器和客户端的通信&#xff0c;可以使用Java Socket编程。 Socket 概述 Socket 指的是“插座”&#xff0c;是应用层与传输层之间的桥梁&#xff0c;用于在网络上进行双向通信。在 Socket …