【C++高阶】深入理解红黑树:数据结构与算法之美

news2024/11/13 9:09:20

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:了解 AVL 树
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀红黑树

  • 📒1. 红黑树的概念
  • 📙2. 红黑树结构
  • 📜3. 红黑树节点的定义
  • 📚4. 红黑树的插入
    • 🧩插入新节点
    • 🌈检测红黑树是否造到破坏
      • 🌞情况一
      • 🌙情况二
      • ⭐情况三
  • 📝5. 红黑树的验证
  • 📘6. 红黑树与AVL树的比较
  • 📖7. 总结


前言: 在数据结构的浩瀚星空中,红黑树犹如一颗璀璨的明珠,以其独特的自平衡特性和高效的搜索能力,成为了计算机科学领域中不可或缺的一部分。红黑树,作为二叉搜索树的一种变体,通过引入节点颜色的概念和一系列复杂的旋转操作,巧妙地解决了传统二叉搜索树在极端情况下退化为链表的问题

红黑树的魅力并不仅仅在于其高效和实用。其背后所蕴含的算法思想和数据结构设计的智慧,更是值得我们深入学习和探索的宝贵财富。红黑树的实现过程充满了挑战与乐趣,它要求开发者不仅要掌握扎实的编程基础,还要具备敏锐的逻辑思维和严谨的数学分析能力

本篇文章,将带您一起揭开红黑树的神秘面纱。我们将从红黑树的基本概念出发,逐步深入到其内部结构、性质、操作原理以及实际应用等多个方面。通过生动的图解、详细的步骤解析和丰富的实例代码,帮助您全面理解红黑树的精髓和魅力

让我们一起踏上学习 红黑树 的旅程,探索它带来的无尽可能!


📒1. 红黑树的概念

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

红黑树由Rudolf Bayer在1972年发明,最初被称为平衡二叉B树(Symmetric Binary B-trees),后来被Guibas和Robert Sedgewick修改为如今的“红黑树”。

在这里插入图片描述


红黑树的性质:

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

📙2. 红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:

在这里插入图片描述


📜3. 红黑树节点的定义

红黑树节点的定义通常包含以下几个关键部分:

基本元素:

  • _left:指向节点的左子节点的指针
  • _right:指向节点的右子节点的指针
  • _parent:指向节点的父节点的指针
  • _kv:一个结构体或配对(pair),包含节点的键值(key)和值(value)。这取决于红黑的具体用途,可能只包含键或包含键值对。

节点颜色(Color):

  • 在上面的定义中,_col 成员变量用于表示节点的颜色,通过 Color 枚举类型来定义,可以是 RED 或 BLACK。

构造函数:

  • 初始化一个新节点时,通常需要一个构造函数,它接受一个键值对(或仅键),并设置节点的左子节点、右子节点、父节点和颜色(初始化为红色)

节点定义示例(C++):

enum Color
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left; // 该节点的左孩子
	RBTreeNode<K, V>* _right; // 该节点的右孩子
	RBTreeNode<K, V>* _parent; // 该节点的父亲
	
	pair<K, V> _kv;  // pair
	
	Color _col; // 该节点的颜色

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

📚4. 红黑树的插入

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  • 按照二叉搜索的树规则插入新节点
  • 检测新节点插入后,红黑树的性质是否造到破坏

在我们进行插入操作之前,我们先定义一个红黑树的类

红黑树定义示例(C++):

template<class K, class V>
class RBTTree
{
	typedef BSTreeNode<K, V> Node;
public:
	// 其他未实现的成员函数
private:
	Node* _root = nullptr;	
};

红黑树的插入操作类似于我们之前AVL树的插入,只不过红黑树的插入操作涉及到旋转操作以及考虑其他节点的颜色,前面的操作还是一样的


🧩插入新节点

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

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			parent = cur;
			if (cur->_kv.first < kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		// 新增节点给红色
		cur = new Node(kv);
		cur->_col = RED;
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 检测新节点插入后,红黑树的性质是否造到破坏

		return true;
	}

🌈检测红黑树是否造到破坏

(如果遭到破坏则对当前红黑树进行变色,旋转处理)
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

🌞情况一

cur为红,p为红,g为黑,u存在且为红
在这里插入图片描述
解决方式:将parent,uncle改为黑,g改为红,然后把g当成cur,继续向上调整。


🌙情况二

cur为红,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述
解决方式: p为g的左孩子,cur为p的左孩子,则进行右单旋转p为g的右孩子,cur为p的右孩子,则进行左单旋转

parent、grandfather变色变色–> p变黑,g变红


⭐情况三

cur为红,p为红,g为黑,u不存在/u存在且为黑

在这里插入图片描述
解决方式: p为g的左孩子,cur为p的右孩子,则针对p做左右双旋;p为g的右孩子,cur为p的左孩子,则针对p做右左旋转

cur、grandfather变色–> c变黑,p变红


检测红黑树是否造到破坏代码演示(C++):

while (parent && parent->_col == RED)
{
	Node* grandfather = parent->_parent;
	if (parent == grandfather->_left)
	{
		//     g
		//   p   u
		// c
		Node* uncle = grandfather->_right;
		if (uncle && uncle->_col == RED)
		{
			// 变色
			parent->_col = uncle->_col = BLACK;
			grandfather->_col = RED;

			// 继续往上更新处理
			cur = grandfather;
			parent = cur->_parent;
		}
		else
		{
			if (cur == parent->_left)
			{
				// 单旋
				//     g
				//   p
				// c
				RotateR(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				// 双旋
				//     g
				//   p
				//     c
				RotateL(parent);
				RotateR(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}
	else  // parent == grandfather->_right
	{
		//     g
		//   u   p 
		//          c
		//
		Node* uncle = grandfather->_left;
		if (uncle && uncle->_col == RED)
		{
			// 变色
			parent->_col = uncle->_col = BLACK;
			grandfather->_col = RED;

			// 继续往上处理
			cur = grandfather;
			parent = cur->_parent;
		}
		else
		{
			if (cur == parent->_right)
			{
				RotateL(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				//     g
				//   u   p 
				//     c
				//
				RotateR(parent);
				RotateL(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}
}

_root->_col = BLACK;

红黑树的旋转和AVL树差不多,我们直接上代码回顾以下:
旋转代码示例(C++):

void RotateL(Node* parent) // 左旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	subR->_left = parent;

	Node* Parentparent = parent->_parent;

	parent->_parent = subR;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	// 判断parent是不是根节点
	if (_root == parent)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == Parentparent->_left)
		{
			Parentparent->_left = subR;
		}
		else
		{
			Parentparent->_right = subR;
		}
		subR->_parent = Parentparent;
	}
}

void RotateR(Node* parent) // 右旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}

	Node* Parentparent = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

	if(_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (parent == Parentparent->_left)
		{
			Parentparent->_left = subL;
		}
		else
		{
			Parentparent->_right = subL;
		}

		subL->_parent = Parentparent;
	}
}

📝5. 红黑树的验证

红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  • 检测其是否满足红黑树的性质

中序遍历代码演示(C++):

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

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

检测其是否满足红黑树的性质(C++):

bool Check(Node* root, int blacknum, const int refVal)
{
	if (root == nullptr)
	{
		// 走到null之后,判断refVal和blacknum是否相等
		if (blacknum != refVal)
		{
			cout << "存在黑色节点数量不相等的路径" << endl;
			return false;
		}
		return true;
	}
	
	// 检测当前节点与其双亲是否都为红色
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "有连续的红色节点" << endl;
		return false;
	}
	
	// 统计黑色节点的个数
	if (root->_col == BLACK)
	{
		++blacknum;
	}

	return Check(root->_left, blacknum, refVal)
		&& Check(root->_right, blacknum, refVal);
}

bool IsBalance()
{
	if (_root == nullptr)
	{
		// 空树也是红黑树
		return true;
	}

	if(_root->_col == RED)
	{
		return false;
	}

	// 参考值
	// 检测是否满足红黑树的性质,refVal用来记录路径中黑色节点的个数
	int refVal = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refVal;
		}
		cur = cur->_left;
	}
	// 获取任意一条路径中黑色节点的个数
	int blacknum = 0;
	
	return Check(_root, blacknum, refVal);
}

测试:

int main()
{
	RBTree<int, int> t;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for(auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
	return 0;
}

在这里插入图片描述
没有出现问题,暂时认为此红黑树创建成功!


📘6. 红黑树与AVL树的比较

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

红黑树与AVL树在平衡策略、性能特性和实现复杂度等方面存在显著差异。在选择使用哪种数据结构时,需要根据具体的应用场景和需求进行权衡和选择。


📖7. 总结

关于红黑树的删除由于比较复杂,这里就不再讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》
红黑树的删除

随着我们对红黑树深入而细致的探讨,这段关于自平衡二叉搜索树的探索之旅也即将画上圆满的句号。红黑树,以其独特的魅力和卓越的性能,不仅在理论上为我们揭示了数据结构的精妙与复杂,更在实际应用中展现了其无可替代的价值与力量

我们从红黑树的基本概念出发,逐步深入了解了其性质、操作原理以及实际应用。我们见证了红黑树如何通过引入节点颜色和复杂的旋转操作,巧妙地维护了树的平衡,保证了在最坏情况下也能拥有高效的搜索、插入性能。深刻感受到了它在现代计算机科学中的重要性

红黑树的故事并未结束。随着技术的不断发展和应用场景的不断拓展,红黑树也将继续发挥其独特的作用,为我们解决更多复杂的问题和挑战。同时,红黑树所蕴含的算法思想和数据结构设计的智慧也将激励着我们不断学习和探索,追求更加高效、优雅和简洁的编程之道

让我们充分理解红黑树,继续在数据结构和算法的海洋中遨游,不断挖掘计算机科学的奥秘,为未来的技术创新和进步贡献自己的力量。愿每一位学习者都能在求知的道路上不断前行,收获满满的智慧与快乐

在这里插入图片描述
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

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

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

相关文章

加油站税控云平台:税务合规新标杆,引领油站高效运营

在当今数字化快速发展的时代&#xff0c;加油站行业也面临着前所未有的变革。税务管理的合规性与运营的高效性成为了加油站发展的两大核心要素。 而加油站税控云平台的出现&#xff0c;无疑为这一传统行业注入了新的活力&#xff0c;它不仅是税务合规的新标杆&#xff0c;更是…

图形编辑器基于Paper.js教程07:鼠标画直线或移动路径

探索Paper.js: 使用鼠标绘制直线和轨迹 在数字图形设计和Web应用开发中&#xff0c;提供一个直观和互动的界面供用户绘制图形是极为重要的。Paper.js是一款功能强大的JavaScript库&#xff0c;它使得在HTML5 Canvas上绘制矢量图形变得简单快捷。本文将介绍如何使用Paper.js实现…

昇思MindSpore25天学习Day19:CycleGAN图像风格迁移互换

(TOC)[CycleGAN图像风格迁移呼唤] 模型介绍 模型简介 CycleGAN(Cycle Generative Adversaial Network)即循环对抗生成网络&#xff0c;来自论文Link:Unpaired lmage-to-mage Translation using Cycle-Consistent AdvesairalNetworks该模型实现了—种在没有配对示例的情况下学…

【单片机毕业设计选题24049】-基于STM32单片机的智能手表设计

系统功能: 显示时间&#xff0c;温湿度&#xff0c;体温信息&#xff0c;播放音乐及控制红外小夜灯&#xff0c;通过蓝牙模块连接手机APP。 系统上电后OLED显示“欢迎使用智能手表系统请稍后”&#xff0c;两秒后进入正常页面显示 第一行显示获取到的当前时间 第二行显示获…

汽车数据应用构想(六)

今天接着说车辆独有的数据信息&#xff0c;对于车辆本身的故障、损耗&#xff0c;原理上都会有相应的数据特征&#xff0c;举个例子&#xff1a; 刹车对于安全无比重要&#xff0c;但刹车性能的下降却并不会引发仪表告警。一般都是保养的时候&#xff0c;工人肉眼观察一下刹车…

文章解读与仿真程序复现思路——太阳能学报EI\CSCD\北大核心《考虑碳效益和运行策略的风电场储能优化配置》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

【scau统计学】期末考试——Excel数据分析加载项

首先机房只有MicrosoftExcel没有WPS&#xff0c;所以需要熟悉Microsoft Excel的使用。 如果没有数据分析这块选项 如果没有数据分析这个加载项&#xff0c;请先下载。 左上角点击文件&#xff1a; 点击左下角&#xff08;选项&#xff09;&#xff0c;选择加载项 点击下方&a…

【C语言】C语言编译链接和Win32API简单介绍

目录 翻译环境和运行环境翻译环境编译器预处理&#xff08;预编译&#xff09;编译链接 执行环境 Win32API是什么控制台程序控制台获取坐标COORDGetStdHandle函数GetConsoleCursorinfo函数CONSOLE_CURSOR_INFOSetConsoleCursorInfo函数SetConsoleCursorPostion函数GetAsyncKeyS…

完美解决ValueError: not enough values to unpack (expected 2, got 1)的正确解决方法,亲测有效!!!

完美解决ValueError: not enough values to unpack (expected 2, got 1)的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 亲测有效 完美解决ValueError: not enough values to unpack (expected 2, got 1)的正确解决方法&#xff0c;亲测有效&#xf…

算法设计与分析 实验5 并查集法求图论桥问题

目录 一、实验目的 二、问题描述 三、实验要求 四、实验内容 &#xff08;一&#xff09;基准算法 &#xff08;二&#xff09;高效算法 五、实验结论 一、实验目的 1. 掌握图的连通性。 2. 掌握并查集的基本原理和应用。 二、问题描述 在图论中&#xff0c;一条边被称…

Three.js机器人与星系动态场景(四):封装Threejs业务组件

实际在写业务的时候不会在每个组件里都写几十行的threejs的初始化工作。我们可以 将通用的threejs的场景、相机、render、轨道控制器等进行统一初始化。同时将非主体的函数提到组件外部&#xff0c;通过import导入进组件。将业务逻辑主体更清晰一些。下面的代码是基于reactthre…

(附源码)springboot共享单车管理系统-计算机毕设 65154

springboot共享单车管理系统 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于共享单车管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了共享单车管理系…

Leetcode3194. 最小元素和最大元素的最小平均值

Every day a Leetcode 题目来源&#xff1a;3194. 最小元素和最大元素的最小平均值 解法1&#xff1a;排序遍历 将数组 nums 排序后&#xff0c;利用双指针计算每一对 (minElement maxElement) / 2&#xff0c;最小值即为答案。 代码&#xff1a; /** lc appleetcode.cn …

多线程网络实战之仿qq群聊的服务器和客户端

目录 一、前言 二、设计需求 1.服务器需求 2.客户端需求 三、服务端设计 1.项目准备 2.初始化网络库 3.SOCKET创建服务器套接字 4. bind 绑定套接字 5. listen监听套接字 6. accept接受客户端连接 7.建立套接字数组 8. 建立多线程与客户端通信 9. 处理线程函数&…

iptables实现端口转发ssh

iptables实现端口转发 实现使用防火墙9898端口访问内网front主机的22端口&#xff08;ssh连接&#xff09; 1. 防火墙配置(lb01) # 配置iptables # 这条命令的作用是将所有目的地为192.168.100.155且目标端口为19898的TCP数据包的目标IP地址改为10.0.0.148&#xff0c;并将目标…

【Java】垃圾回收学习笔记(一):Root Search 根可达算法+垃圾回收的起点

文章目录 1. 引用计数法优点缺点 2. 可达性分析 Root Search2.1 那些对象是GC Roots2.2 引用的分类2.3 回收方法区 3. 实现细节3.1 GC的起点&#xff1a;节点枚举OopMap&#xff1a;帮助高效的根节点枚举 3.2 何时开始GC&#xff1a;安全点与安全区域如何选取安全点如何让程序进…

数据驱动的内容优化:Kompas.ai如何提升内容表现

在数字化营销时代&#xff0c;内容是企业与用户沟通的重要桥梁。然而&#xff0c;随着信息量的爆炸性增长&#xff0c;如何让内容在激烈的竞争中脱颖而出&#xff0c;成为每个营销人员面临的问题。数据驱动的内容优化策略&#xff0c;通过精准分析和科学决策&#xff0c;帮助品…

华为OD机试 - 员工派遣(Java 2024 D卷 200分)

华为OD机试 2024D卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;D卷C卷A卷B卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测…

基于字典学习的地震数据降噪(MATLAB R2021B)

稀疏表示基于研究者们提出了许多变换基函数的方法逐渐成型&#xff0c;比如小波域&#xff0c;曲波域&#xff0c;dreamlet 域等&#xff0c;其原理是利用地震信号在变换域内的稀疏性和可分离性以去除噪声。继 Donoho发表非线性去噪方法-小波阈值萎缩方法&#xff0c;在后续的研…

Swift 中 map 和 flatMap 的区别 (入门版)

在 Swift 中&#xff0c;map 和 flatMap 是用于处理集合类型&#xff08;如数组、字典、集合等&#xff09;的两个重要方法。尽管它们看起来相似&#xff0c;但它们在处理集合中的元素和结果时有着不同的行为。 map 方法 map 方法会对集合中的每一个元素应用一个变换&#xff…