【红黑树】—— 我与C++的不解之缘(二十五)

news2025/3/16 11:17:29

前言

学习了avl树,现在来学习红黑树

一、什么是红黑树

红黑树是一颗平衡二叉搜索树,它每一个节点增加了一个存储位表示节点的颜色,可以是红色或者黑色。

相比较于AVL树,红黑树也是一个自平衡二叉搜索树,但是它与AVL树控制平衡的方式不同;

  • AVL是通过平衡因子来控制整个树的平衡
  • 红黑树则是通过节点的颜色红/黑来控制整个树的平衡

这里红黑树通过对任何一条从根到叶子的路径上每一个节点的颜色的约束,从而确保最长路径不会超过最短路径的2倍,因此让整个树保证平衡。

红黑树的规则

红黑树遵循以下几条规则:

  • 每一个节点的颜色不是RED/红色就是BLACE/黑色
  • 根节点的颜色为BLACK
  • 如果一个节点是红色的,那么它的孩子节点就一定是黑色的;也就是在任意一条路径中不会出现连续的红色节点。
  • 对任何一个节点,从这个节点到其所有的NULL的简单路径上,均包含相同数量的黑色节点。

这里在《算法导论》《STL源码剖析》书籍中,存在这样的一条定义每个叶子节点NIL都是黑色

注意:这里说的并不是我们认为的叶子节点,而是NULL节点;

在这里插入图片描述

有了NIL节点我们就可以十分清楚的看出来所有的路径

为什么红黑树能确保最长路径不超过最短路径的2

  • 根据规则4,从根节点到NULL节点每条路径都存在相同数量的黑色节点;所以这里假设一下极端情况:最短路径下全是黑色节点,长度为bh,那么黑色节点的个数也是bh
  • 根据规则3,任何一条路径下不会存在连续的红色节点,那极端情况下,最长路径就是由一黑一红组成的,那最长路径的长度为2*bh,黑色节点个数是bh
  • 然而极端情况并不是在每一个红黑树中都存在的;假设从根节点到NULL的一条路径长度为h,那就存在bh <= h <= 2*bh

在这里插入图片描述

红黑树的效率

对于红黑树,确实控制了平衡,但是它的查找效率如何呢?

这里假设红黑树的节点个数为nh是最短路径的长度;那我们就可以得出红黑树最坏情况是走最长路径,时间复杂度就是O(log N)

虽然说红黑树相对于AVL树的效率较差一点,但是它的效率还是O(log N)

  • AVL树通过高度来严格控制了整个树的平衡
  • 红黑树就通过了四条规则,控制树中节点的颜色来控制这个树的相对平衡。

二、红黑树的结构

说了这么多,现在来看一下红黑树的结构

红黑树首先是一个三叉链结构,还要存放pair<K,V>,和颜色Color

这里颜色使用枚举变量来表示

enum Color
{
	RED,
	BLACK,
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	pair<K, V> _kv;
	Color _col;

	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

三、红黑树的插入

了解了红黑树节点的结果,现在来看红黑树的插入节点

插入一个值,我们需要按照`二叉搜索树的结构来进行插入,插入之后来判断是否满足红黑树的规则

这里AVL在找到插入位置并插入节点后做的是更新平衡因子;

而红黑树则是进行循环操作(变色旋转+变色1等),直到其父节点不存在(遍历到_root)或者父节点颜色为BLACK

首先就是插入节点的颜色

  • 如果是空树插入,新增节点为黑色;
  • 那如果是非空树的插入节点,新增节点就必须是红色
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree() {};
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)//空树插入
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		//非空树插入
		Node* tail = _root;
		Node* parent = nullptr;
		while (tail)
		{
			if (kv.first > tail->_kv.first)
			{
				parent = tail;
				tail = tail->_right;
			}
			else if (kv.first < tail->_kv.first)
			{
				parent = tail;
				tail = tail->_left;
			}
			else
			{
				return false;
			}
		}
		Node* cur = new Node(kv);
		cur->_col = RED;//新插入节点一定是红色
		cur->_parent = parent;
		if (cur->_kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else if (cur->_kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		//进行不满足规则的一系列处理
	}
private:
	Node* _root;
};

这里如果新增节点是黑色,那就一定破坏了规则四(因为插入之前是符合条件的红黑树)。

如果我们插入红色节点,其父节点为黑色就不需要做任何调整;

如果父节点是红色,这时违反了规则三,我们需要进一步分析

  • 此时其父节点p为红色,那祖父节点g就一定为黑色;而叔节点uncle颜色并不确定

    这时c插入节点,p父节点,g祖父节点颜色都是固定的,这种情况下我们来看u叔节点

我们根据u节点的颜色可以分为三种情况

1. 情况一: 变色

在上述中,我们已经确定了cpg的颜色,现在我们现在唯一不能确定的就是u节点;

如果u节点存在,且u节点的颜色为红色,如下图所示

在这里插入图片描述

对于这种情况,u存在且为红色;通过观察我们可以发现:

g节点的黑色节点影响的路径是其左右子树pu的路径,那我们将pu变成黑色、g变成红色

变化之后可以发现,从g节点到NULL节点的路径上的黑色几点并没有发生变化。

这里有些问题:

  1. 在上述图中,g的父节点为黑色,那如果g的父节点为红色呢?

这就好说了,将g赋值给c,继续执行循环即可。

  1. 那现在存在一个问题,如果在向上变色的过程中,g为根节点怎么办?

这里在执行完循环过后,直接把_root的颜色修改为黑即可。

  1. 如果在执行向上变色的过程中遇到u不存在或者u存在但其颜色为怎么办?

接着往下看情况二:

2. 情况二:单选 + 变色

如果我们在向上执行变色的过程中,遇到了u不存在或者u存在但它的颜色为

这时我们就不能只变色来解决问题了。

在这里插入图片描述

这种情况下,我们就需要进行一次单旋,再进行变色才能解决问题

在这里插入图片描述

在这里插入图片描述

根据上图可以看到,右单旋的条件是c = p->_left && u==g->_right

这里都是右单旋的问题,左单旋是以下情况,就不做分析了。

在这里插入图片描述

进行右单旋加变色的条件是c = p->_right && u==g->_left

3. 情况三: 双旋 + 变色

在上述过程中,只有单旋,那如果单旋解决不了问题呢?

如下图所示:

在这里插入图片描述

如果我们在p的这个位置插入(或者有g向上更新变化过来的c

这时就不能就进行单旋了,就像AVL中平衡因中一样,如果进行了单旋就会发现把问题变得更加复杂了。

这是要进行左右双旋转

先以p节点进行一次左单旋,再以g节点进行一下右单旋。

在这里插入图片描述

在这里插入图片描述

当然,存在左右双旋的情况,也存在右左双旋的情况,如下图所示(这里就不推理了)

在这里插入图片描述

插入代码实现:

有了上述情况分析,现在来实现红黑树插入的代码:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree() {};
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)//空树插入
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		//非空树插入
		Node* tail = _root;
		Node* parent = nullptr;
		while (tail)
		{
			if (kv.first > tail->_kv.first)
			{
				parent = tail;
				tail = tail->_right;
			}
			else if (kv.first < tail->_kv.first)
			{
				parent = tail;
				tail = tail->_left;
			}
			else
			{
				return false;
			}
		}
		Node* cur = new Node(kv);
		cur->_col = RED;//新插入节点一定是红色
		cur->_parent = parent;
		if (cur->_kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else if (cur->_kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		//这里当父节点存在且为红色时就一直循环
		//直到父节点不存在或者父节点的颜色为黑色
		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 if (uncle == nullptr || uncle->_col == BLACK)
				{
					if (cur == parent->_left)
					{
						//    g
						//  p   u
						//c
						//右单旋+变色
						RevoleR(parent);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else if (cur == parent->_right)
					{
						//    g
						//  p   u
						//   c
						//先左单旋,再右单旋,再变色
						RevoleL(parent);
						RevoleR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
				else if (uncle == grandfather->_left)
				{
					//   g
					// u  p
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;
						cur = grandfather;
						parent = cur->_parent;
					}
					else if (uncle == nullptr || uncle->_col == BLACK)
					{
						if (cur == parent->_right)
						{
							//   g
							// u   p
							//       c
							//左单旋+变色
							RevoleL(parent);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else if (cur == parent->_left)
						{
							//   g
							// u   p
							//    c
							//先右单旋,再左单旋,再变色
							RevoleR(parent);
							RevoleL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
					}
				}
			}
		}
		_root->_col = BLACK;
	}
private:
	void RevoleR(Node* parent) //右单旋
	{
		Node* subl = parent->_left;
		Node* sublr = parent->_left->_right;

		parent->_left = sublr;
		if (sublr)
			sublr->_parent = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subl;
		subl->_parent = ppNode;
		if (ppNode == nullptr)
		{
			_root = subl;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subl;
			}
			else if (parent->_right)
			{
				ppNode->_right = subl;
			}
		}
	}
	void RevoleL(Node* parent)//左单旋
	{
		Node* subr = parent->_right;
		Node* subrl = parent->_right->_left;
		
		parent->_right = subrl;
		if (subrl)
			subrl->_parent = parent;

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

四、红黑树的查找

红黑树依旧是一个搜索二叉树,它的查找效率仍旧是log N;比AVL略微差一点。

	bool find(const pair<K, V>& kx)
	{
		Node* tail = _root;
		while (tail)
		{
			if (kv.first > tail->_kv.first)
			{
				tail = tail->_right;
			}
			else if (kv.first < tail->_kv.first)
			{
				tail = tail->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

四、红黑树的查找

红黑树依旧是一个搜索二叉树,它的查找效率仍旧是log N;比AVL略微差一点。

	bool find(const pair<K, V>& kx)
	{
		Node* tail = _root;
		while (tail)
		{
			if (kv.first > tail->_kv.first)
			{
				tail = tail->_right;
			}
			else if (kv.first < tail->_kv.first)
			{
				tail = tail->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

五、红黑树验证

说了这么多,现在来看一下如何验证一个树是不是红黑树。

首先去检查最长路经和最短路径是不可行的,因为如果满足最长路径不超过最短路径的2倍,但是颜色也可能不满足规则。

也也能存在问题;

所以我们需要去检查红黑树的四条规则

  • 对于规则一,我们使用枚举常量,就保证了颜色不是黑色就是红色。
  • 规则二,直接检查跟节点颜色即可。
  • 规则三,使用前序遍历检查,遇到红色节点(可能不存在孩子节点,有可能存在一个/两个),非常不方便;这里可以反过来检查,遇到红色节点检查其父节点即可。
  • 规则四,在遍历的过程中,用形参来记录根节点到当前节点的BLACK_num(黑色节点个数),前序遍历遇到黑色节点就++BLACK_num,遍历到空就计算出了一条路径的黑色节点个数,再将任一条路径黑色节点个数作为参考值,依次比较即可。

六、红黑树的删除

对于红黑树的删除,这里暂时不做讨论,等以后再深入研究。

感兴趣的可以参考书籍《算法导论》《STL源码剖析》

到这里本篇内容就结束了,希望对你有所帮助。

制作不易,感谢大佬的支持。

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

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

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

相关文章

驾驭 DeepSeek 科技之翼,翱翔现代学习新天际

在当今这个信息爆炸的时代&#xff0c;学习的方式和途径正在经历着前所未有的变革。人工智能技术的飞速发展&#xff0c;为我们的学习带来了全新的机遇和挑战。DeepSeek 作为一款强大的大语言模型&#xff0c;凭借其卓越的性能和丰富的功能&#xff0c;为现代学习注入了新的活力…

DeepSeek本地部署 (Windows+Ollama+Docker Desktop+ RAGFlow)

适用场景&#xff1a; 1、商城的小机器人自动根据实际情况回复 2、需要7*24小时运行在线回复&#xff0c;如&#xff1a;在线购物、在线咨询、在线招生等 3、无人值守环境 2025年1月&#xff0c;DeepSeek 正式发布 DeepSeek-R1 推理大模型&#xff0c;DeepSeek-R1 成本价格低…

SPI驱动(八) -- SPI_DAC设备驱动程序

文章目录 参考资料&#xff1a;一、编写设备树二、 编写驱动程序三、编写测试APP四、Makefile五、上机实验 参考资料&#xff1a; 参考资料&#xff1a; 内核头文件&#xff1a;include\linux\spi\spi.h内核文档&#xff1a;Documentation\spi\spidevDAC芯片手册&#xff1a;…

MySQL 衍生表(Derived Tables)

在SQL的查询语句select …. from …中&#xff0c;跟在from子句后面的通常是一张拥有定义的实体表&#xff0c;而有的时候我们会用子查询来扮演实体表的角色&#xff0c;这个在from子句中的子查询会返回一个结果集&#xff0c;这个结果集可以像普通的实体表一样查询、连接&…

HarmonyOS NEXT开发进阶(十二):build-profile.json5 文件解析

文章目录 一、前言二、Hvigor脚本文件三、任务与任务依赖图四、多模块管理4.1 静态配置模块 五、分模块编译六、配置多目标产物七、配置APP多目标构建产物八、定义 product 中包含的 target九、拓展阅读 一、前言 编译构建工具DevEco Hvigor&#xff08;以下简称Hvigor&#x…

深度学习笔记(37周)

目录 摘要 Abstracts 1. 介绍 2. 相关工作 3. 模型 3.1 时序段网络TSN 3.2 学习时序段网络 4. 训练结果 5. 结论 摘要 本周阅读的论文是《Temporal Segment Networks: Towards Good Practices for Deep Action Recognition》。作者主要想通过较少的训练样本&#xff…

ELK+Filebeat+Kafka+Zookeeper安装部署

1.安装zookeeper zookpeer下载地址:apache-zookeeper-3.7.1-bin.tar.gzhttps://link.csdn.net/?targethttps%3A%2F%2Fwww.apache.org%2Fdyn%2Fcloser.lua%2Fzookeeper%2Fzookeeper-3.7.1%2Fapache-zookeeper-3.7.1-bin.tar.gz%3Flogin%3Dfrom_csdn 1.1解压安装zookeeper软件…

【软考-架构】3.3、模式分解-事务并发-封锁协议

✨资料&文章更新✨ GitHub地址&#xff1a;https://github.com/tyronczt/system_architect 文章目录 模式分解&#xff08;难点&#xff09;无损分解&#x1f4af;考试真题并发控制封锁协议&#x1f4af;考试真题第一题第二题 模式分解&#xff08;难点&#xff09; 保持函…

审批工作流系统xFlow

WorkFlow-审批流程系统 该项目为完全开源免费项目 可用于学习或搭建初始化审批流程系统 希望有用的小伙伴记得点个免费的star gitee仓库地址 仿钉钉飞书工作审批流系统 介绍 前端技术栈: vue3 ts vite arcodesign eslint 后端技术栈:springbootspring mvc mybatis mavenmysq…

【数据结构初阶第十九节】八大排序系列(下篇)—[详细动态图解+代码解析]

hello&#xff0c;好久不见&#xff01; 云边有个稻草人-CSDN博客 上篇内容&#xff0c;回顾一下吧【数据结构初阶第十八节】八大排序系列(上篇)—[详细动态图解代码解析]-CSDN博客 今天我们来学习下篇 目录 &#xff08;2&#xff09;快速排序 【挖坑法】 —思路 —思路…

定制开发开源 AI 智能名片 S2B2C 商城小程序源码在小程序直播营销中的应用与价值

摘要&#xff1a; 本文主要探讨了定制开发开源 AI 智能名片 S2B2C 商城小程序源码在小程序直播营销中的应用与价值。首先详细阐述了小程序直播的基本概念、特点、发展历程及营销意义&#xff0c;包括其便捷性、广泛的受众连接能力以及对企业推广的重要作用。接着深入剖析了定制…

蓝桥杯Python赛道备赛——Day3:排序算法(二)(归并排序、堆排序、桶排序)

本博客是蓝桥杯备赛系列中排序算法的第二期&#xff0c;包括&#xff1a;归并排序、堆排序和桶排序。每一个算法都在给出概念解释的同时&#xff0c;给出了示例代码&#xff0c;以供低年级师弟师妹们学习和练习。 由于本期的三个算法的复杂度相对来说要高于上一期的三个算法&am…

Type-C:智能家居的电力革命与空间美学重构

在万物互联的时代浪潮中&#xff0c;家居空间正经历着从功能容器到智慧终端的蜕变。当意大利设计师安东尼奥奇特里奥提出"消失的设计"理念二十年后&#xff0c;Type-C充电技术正以润物无声的方式重塑着现代家居的形态与内核&#xff0c;开启了一场静默的居住革命。 【…

第十五届蓝桥杯C/C++组:宝石组合题目(从小学奥数到编程题详解)

这道题目真的一看就不好做&#xff0c;如果直接暴力去做百分之90必挂掉&#xff0c;那么这道题目到底应该怎么去做呢&#xff1f;这我们就得从小学奥数开始聊了。&#xff08;闲话&#xff1a;自从开始蓝桥杯备赛后&#xff0c;每天都在被小学奥数震惊&#xff0c;为什么我小的…

ECharts中Map(地图)样式配置、渐变色生成

前言 ECharts是我们常用的图表控件&#xff0c;功能特别强大&#xff0c;每次使用都要查API比较繁琐&#xff0c;这里就记录开发中常用的配置。 官网&#xff1a;https://echarts.apache.org/handbook/zh/get-started 配置项&#xff1a;https://echarts.apache.org/zh/opti…

MySQL | MySQL表的增删改查(CRUD)

目录 前言&#xff1a;什么是 CRUD ?一、Creat 新增1.1 语法1.2 示例1.2.1 单行数据全列插入1.2.2 单行数据指定列插入1.2.3 多行数据指定列插入 二、Retrieve 检索2.1 语法2.2 示例2.2.1 全列查询2.2.2 指定列查询2.2.3 查询字段为表达式2.2.4 结果去重查询2.2.5 where条件查…

电子电气架构 --- 分布到集中的动カ系统及基于域控制器的架构

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所有人的看法和评价都是暂时的,只有自己的经历是伴随一生的,几乎所有的担忧和畏惧,都是来源于自己的想象,只有你真的去做了,才会发现有多快乐。…

基于SpringBoot的“考研互助平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“考研互助平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…

基于javaweb的SpringBoot足球俱乐部管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

DQN 玩 2048 实战|第一期!搭建游戏环境(附 PyGame 可视化源码)

视频讲解&#xff1a; DQN 玩 2048 实战&#xff5c;第一期&#xff01;搭建游戏环境&#xff08;附 PyGame 可视化源码&#xff09; 代码仓库&#xff1a;GitHub - LitchiCheng/DRL-learning: 深度强化学习 2048游戏介绍&#xff0c;引用维基百科 《2048》在44的网格上进行。…