数据结构——红黑树

2023-11-09

1.什么是红黑树?

红黑树是一种特定类型的二叉树,用于组织数据。它是一种平衡二叉查找树(AVL树)的变体,每个结点都带有颜色属性(红色或黑色)。在红黑树中,从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。具体来说,红黑树满足以下性质:

  1. 每个结点要么是红色,要么是黑色。
  2. 根结点是黑色。
  3. 每个叶结点(NIL或空结点)是黑色的。
  4. 如果一个结点是红色的,则它的两个子结点都是黑色的。
  5. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

举个例子

2.红黑树插入的模拟实现

①节点的定义

// 在这里为了方便表示我们先将颜色枚举
// 在红黑树中只有黑与红,即BLACK与RED
enum Color
{
	BLACK,
	RED
};

// 因为节点是公用的,因此设定为struct
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		// 根据红黑树的性质可以知道
		// 要插入的新节点不应该是黑色的,而应该是红色的
		// 如果是黑色的那么就会影响它当前路径的黑色节点总数
		, _color(RED) 
	{}

	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
};

②插入

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 寻找要插入的位置
		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已经指向了应该插入的位置,
		// 然后判断应该插入到parent的哪边
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 插入完成后需要对红黑树进行调整
		// 若父节点是黑就无需调整
		// 而当父节点是红就需要进行调整
        // ...
	}

private:
	Node* _root = nullptr;
};

以上面的红黑树为例,在parent为红的情况下,插入之后的情况可以将它们大致分为两种:1. uncle存在且为红;2.uncle不存在或uncle存在且为黑。让我们来分析一下,在这里我们依旧采取先具体后推广到一般的方法

1.uncle存在且为红

先举一个具体例子

 抽象图如下

2.uncle不存在或uncle存在且为黑

先举一个uncle不存在的具体例子

再举一个uncle存在且为黑的具体例子 

观察上述例子之后可以发现,这两种情况本质上可以当做同一操作,那就是先判断grandpa、parent与cur的关系,然后根据具体情况进行旋转,旋转之后进行变色,因此抽象图如下

 

综合上面的所有情况,再加上一些细节我们可以得到如下插入代码

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 寻找要插入的位置
		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已经指向了应该插入的位置,
		// 然后判断应该插入到parent的哪边
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 插入完成后判断一下
		// 若父节点是黑就无需调整
		// 而当父节点是红就需要进行调整
		while (parent && parent->_color == RED)
		{
			Node* grandpa = parent->_parent;
			if (parent == grandpa->_left)
			{
				Node* uncle = parent->_right;
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = parent->_color = BLACK;
					grandpa->_color = RED;

					cur = grandpa;
					parent = cur->_parent;
				}
				else
				{
					if (uncle == nullptr)
					{
						if (parent->_left == cur)
						{
							//              grandpa
							//      parent
							//cur
							RotateR(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							//              grandpa
							//      parent
							//              cur
							RotateL(parent);
							RotateR(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
					}
					else // uncle存在且为黑
					{
						if (parent->_left == cur)
						{
							//              grandpa
							//      parent
							//cur
							RotateR(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							//              grandpa
							//      parent
							//              cur
							RotateL(parent);
							RotateR(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
					}

					break;
				}
			}
			else // parent == grandpa->_right
			{
				Node* uncle = parent->_left;
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = parent->_color = BLACK;
					grandpa->_color = RED;

					cur = grandpa;
					parent = cur->_parent;
				}
				else
				{
					if (uncle == nullptr)
					{
						if (parent->_left == cur)
						{
							//grandpa
							//         parent
							//cur
							RotateR(parent);
							RotateL(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
						else
						{
							//grandpa
							//        parent
							//               cur
							RotateL(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
					}
					else // uncle存在且为黑
					{
						if (parent->_left == cur)
						{
							//grandpa
							//         parent
							//cur
							RotateR(parent);
							RotateL(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
						else
						{
							//grandpa
							//        parent
							//               cur
							RotateL(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
					}

					break;
				}
			}
		}

		return true;
	}

(注:左右旋参见数据结构——AVL树) 

3.红黑树的性能分析

红黑树可以在O(log n)时间内做查找、插入和删除,这里的n 是树中元素的数目。红黑树在最坏情况下的运行时间也是非常良好的,并且在实践中是高效的。红黑树的性能受限于它的平衡性,即每个叶子节点到根节点的路径上所包含的黑色节点的数量是相同的,这也被称为黑色完美平衡。保持这种平衡可以确保红黑树的性能稳定。总的来说,红黑树是一种高效的数据结构,适用于许多不同的情况。

4.红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构——红黑树 的相关文章

  • [MySQL]并发执行事物的时候, 都发生了什么?

    文章目录 1 目标 2 MySQL中事物的四大基本特性 2 1 原子性 2 2 一致性 2 3 持久性 2 4 隔离性 3 并发执行事物出现的问题及解决方案 3 1 什么是并发行为 3 2 并发执行事物出现的问题 3 2 1 问题一 脏读问

随机推荐

  • 使用 Python 进行深度学习以进行裂纹检测

    使用 Python 进行深度学习以进行裂纹检测 问题陈述 数据集准备 训练模型 结论 参考 问题陈述 虽然新技术已经改变了我们生活的方方面面 在建筑领域似乎牛逼 正在努力追赶 目前 建筑物的结构状况仍然主要是人工检查 简单来说 即使现在需要
  • 计数dp

    给一个数n n lt 1000 问将这个n分解成n1 n2 n3 nk的解法有多少 k gt 1 且 n1 gt n2 gt n3 gt nk gt 1 由于答案可能过大 因此答案对1e9 7取模 1 背包问题解决 这里可看作 结果恰好为n
  • 著名人物的博客

    经济学界 Gary Becker Richard Posner 世界著名经济学家 Gary Becker为诺贝尔经济学奖得主 http becker posner blog com Gregory Mankiw 哈佛大学经济学教授 http
  • 基于C++的QT实现贪吃蛇小游戏

    文章目录 一 效果演示 二 实现思路 三 代码实现 widget h widget cpp main cpp 一 效果演示 效果图 代码下载 二 实现思路 通过按键控制蛇的移动 每吃一个商品蛇身就会加长 如果蛇身头尾相碰就结束游戏 声明渲染
  • TSI系统测量参数之:转速和零转速

    一 TSI系统测量参数 1 轴向位移 2 盖振或瓦振 3 偏心 4 键相 5 零转速 6 轴向振动 7 相对热膨胀 胀差 8 绝对热膨胀 缸胀 二 各参数作用 1 零转速与转速 1 零转速 主要用在汽机转速到零时投盘车的连锁以及对大机转速的
  • mac下的各种sed、grep、ag命令查看日志好用

    sed命令 删除文件的前100行 注意mac上要加个空字符串 sed i 1 100d 404 log 查看文件若干行 输出文件的5 8行 sed n 5 8p 1156 success txt 输出文件的5 8行至11 txt sed n
  • 动态DPC算法学习

    造成坏点的原因 感光元件芯片自身工艺技术瑕疵造成 光线采集存在缺陷 制造商产品差异 坏点分类 hot pixel 固定保持较高的像素值 一般呈现为画面高亮的点 dead pixel 固定保持较低的像素值 一般在画面中呈现为暗点 noise
  • 华为OD机试-磁盘容量排序

    题目描述 磁盘的容量单位常用的有M G T 他们之间的换算关系为 1T 1024G 1G 1024M 现在给定n块磁盘的容量 请对他们按从小到大的顺序进行稳定排序 例如给定5块盘的容量 5 1T 20M 3G 10G6T 3M12G9M 排
  • CSS基础学习——动画

    一 CSS3 2D变形 利用Transfrom方法 1 rotate angle 元素顺时针旋转给定的角度 允许负值 元素将逆时针旋转
  • Winner Winner【模拟、位运算】

    Winner Winner 题目链接 点击 题目描述 The FZU Code Carnival is a programming competetion hosted by the ACM ICPC Training Center of
  • python爬虫是干嘛的?python爬虫能做什么?

    Python爬虫是什么 Python爬虫是由Python程序开发的网络爬虫 webspider webrobot 是按照一定规则自动抓取万维网信息的程序或脚本 其实一般是通过程序在网页上获取你想要的数据 也就是自动抓取数据 爬虫又被称为网络
  • golang 详解协程——errgroup

    为什么要有sync errgroup go支持并发 一般采用的是 channel sync WaitGroup context 来实现各个协程之间的流程控制和消息传递 但是对于开启的成千上万的协程 如果在每个协程内都自行去打印 错误日志的话
  • 关于K-means的通俗理解

    机器学习通俗理解系列 关于knn的通俗理解 文章目录 前言 一 什么是K means 二 什么原理 三 重点 1 K值的选定 2 样本之间的距离 四 优缺点 五 优化进阶 总结 前言 刚学习机器学习的时候免不了百度 问什么是K means
  • vue3运行npm run serve报错ERROR Error: Cannot find module ‘babel-plugin-import‘ Require stack:

    1 完整报错 gt ims support demo 0 1 0 serve Users yizhikaixinya Desktop charmplus ims gt vue cli service serve ERROR Error Ca
  • 实验SparkSQL编程初级实践

    实验SparkSQL编程初级实践 实践环境 Oracle VM VirtualBox 6 1 12 Ubuntu 16 04 Hadoop3 1 3 JDK1 8 0 162 spark2 4 0 python3 5 Windows11系统
  • 领域建模

    忙碌的过着周末 一边思考如何建设自己知识体系 另外一遍白板的各种算法在脑袋互相争抢时间 低音炮单曲循环的的Ava Max Salt 心 静下来 环境燥起来 思绪继续飞行 前期读了一半的书 重新拿起 在建模方式上理解场景方法的研究 之前分享的
  • asp.net zero 8.2 学习-3-添加实体,并迁移到数据库

    系列目录 asp net zero 8 2 学习 1 安装 asp net zero 8 2 学习 2 创建一个页面 asp net zero 8 2 学习 3 添加实体 并迁移到数据库 asp net zero 8 2 学习 4 创建接口
  • 压缩伪影的探讨

    1 压缩伪影的由来 常用的视频编码器中 在一个框架中使用了多种编码方法 01 预测编码 不编码预测值 而是编码预测值与实际值的差值 02 变换编码 对信号的样本值进行某种形式的函数变换 从一种空间变换到另一种空间 然后再根据信号在另一个空间
  • SOA中国路线图活动感受

    下午参加了SOA中国路线图活动 主要由普元公司和相关的媒体以及电信客户进行演讲 对于SOA我之前一直认为是个很虚的东西 概念大于实践 但听了普元公司黄柳青博士的介绍以及在电信领域中的应用 感觉还是有收获的 很多思想可以应用到系统的设计和开发
  • 数据结构——红黑树

    1 什么是红黑树 红黑树是一种特定类型的二叉树 用于组织数据 它是一种平衡二叉查找树 AVL树 的变体 每个结点都带有颜色属性 红色或黑色 在红黑树中 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长 具体来说 红黑树满足以下性质 每