【C++】红黑树及其实现

news2024/10/5 15:23:45

目录

  • 一、红黑树的定义
    • 1.为什么提出红黑树?
    • 2.红黑树的概念
    • 3.红黑树的性质
  • 二、红黑树的实现
    • 1.红黑树的结构
    • 2.红黑树的插入
      • 2.1 uncle为红色
      • 2.2 uncle为黑色,且是grandfather的右孩子
      • 2.3 uncle为黑色,且是grandfather的左孩子
    • 3.红黑树的验证
  • 4.红黑树的查找效率分析
  • 5.整体代码

一、红黑树的定义

1.为什么提出红黑树?

AVL树和红黑树的插入、删除和查找操作的时间复杂度都是 l o g ( n ) log(n) log(n),既然如此,为什么会提出红黑树这一概念呢?
AVL树在插入和删除过程中,为了保持平衡性,会非常频繁地调整全树整体的拓扑结构,代价较大,为此在AVL树的平衡标准上进一步放宽条件,引入了红黑树的结构。

2.红黑树的概念

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

3.红黑树的性质

一棵红黑树是满足如下红黑性质的二叉排序树:

  1. 每个结点或是红色,或是黑色的。
  2. 根结点是黑色的。
  3. 叶结点(这里指的是空结点)都是黑色的。
  4. 不存在两个相邻的红结点(即红结点的父结点和孩子结点均是黑色的)。
  5. 对每个结点,从该结点到任意一个叶结点的简单路径上,所含黑结点的数量相同。

对于这样的红黑树,有两个结论:

  1. 从根到叶结点的最长路径不大于最短路径的2倍。
  2. 有n个内部结点的红黑树的高度 h < = 2 l o g 2 ( n + 1 ) h<=2log_2(n+1) h<=2log2(n+1)

二、红黑树的实现

1.红黑树的结构

红黑树的结构就是在二叉排序树的基础上加上了一个表示颜色的变量,我们可以使用枚举来实现。

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	T _data;
	Colour _col;	// 颜色

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

2.红黑树的插入

红黑树的插入操作也需要先按照二叉排序树的插入方式插入结点,然后根据其是否破坏了红黑树的性质来进行调整。
当我们插入一个新结点时,其默认颜色是红色,不过当这个新结点为根时,则要把它改成黑色。
红黑树的插入调整共分为三种情况,想要知道该选用哪种方式,要看的是叔叔结点的位置和颜色,因此我们想要进行调整,必须准备好四个结点:cur(当前结点)、parent(父结点)、grandfather(爷结点)和uncle(叔叔结点)。

2.1 uncle为红色

当叔叔结点为红色时,不需要旋转操作即可完成调整,先将parent和uncle变为黑色,再将grandfather变为红色即可,如果grandfather是根结点,则调整后要将其改成黑色,如果其父结点是红色则需要继续向上调整。
在这里插入图片描述

2.2 uncle为黑色,且是grandfather的右孩子

这里的uncle可以为空,因为叶结点就是黑色的。
此时若cur在parent的左侧,则可以进行右旋,右旋后将parent和grandfather的颜色进行交换。
在这里插入图片描述
若cur载parent的右侧,需要进行两次旋转,先对parent进行左旋,此时即可看作是cur在parent的左侧了,再对grandfather进行右旋,再将cur改为黑色,parent和grandfather改为红色即可。
在这里插入图片描述

2.3 uncle为黑色,且是grandfather的左孩子

这种情况则和上一种情况相对称,cur在parent的右侧,则可以进行左旋,左旋后将parent和grandfather的颜色进行交换。
若cur载parent的左侧,需要进行两次旋转,先对parent进行右旋,此时即可看作是cur在parent的右侧了,再对grandfather进行左旋,再将cur改为黑色,parent和grandfather改为红色即可。

以下是插入的代码实现:

bool Insert(const T& data)
{
	// 先按照二叉排序树的方式进行排序
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (data < cur->_data)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (data > cur->_data)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(data);
	if (data < parent->_data)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;
	// 开始调整
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		// 当叔叔在爷结点的右侧
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			// 当叔叔为红色
			if (uncle && uncle->_col == RED)
			{
				uncle->_col = BLACK;
				parent->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			// 当叔叔为黑色
			else
			{
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					parent->_col = RED;
					grandfather->_col = RED;
				}
				break;
			}
		}
		// 当叔叔在爷结点的左侧
		else
		{
			Node* uncle = grandfather->_left;
			// 当叔叔为红色
			if (uncle && uncle->_col == RED)
			{
				uncle->_col = BLACK;
				parent->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			else // 当叔叔为黑色
			{
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					parent->_col = RED;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;
	return true;
}

3.红黑树的验证

由于红黑树的删除过于复杂,这里就不再谈了。
关于红黑树的验证,我们需要从红黑树的性质入手,查看当前红黑树是否违背了性质。性质1、3不必检查,剩下的就是2、4和5了。
关于性质2,只需要简单一条if语句即可判定。
对于性质4,我们可以对红黑树进行遍历,如果父结点和当前结点同时为红色,则可以返回false。
性质5想要判断有一点麻烦了,我们可以使用一个mark来记录某一条路径上有多少黑色结点,然后递归每一条路径,来判断是否有路径上黑色结点总值与mark不同。
以下是具体实现:

bool Isbalance()
{
	if (_root && _root->_col == RED)
	{
		cout << "根结点为红色" << endl;
		return false;
	}
	int benchmark = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++benchmark;
		}
		cur = cur->_left;
	}
	return _Check(_root, 0, benchmark);
}

bool _Check(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "某条路径黑色节点数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
		{
			++blacknum;
		}
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续红色节点" << endl;
			return false;
		}
		return _Check(root->_left, blacknum, benchmark)
			&& _Check(root->_right, blacknum, benchmark);
	}

4.红黑树的查找效率分析

我们先引入一个概念,
黑高:从某结点出发(不含该结点)到达一个叶结点的任意一个简单路径上的黑结点总数称为该结点的黑高(记为bh),黑高的概念是由性质5确定的。根结点的黑高称为红黑树的黑高。
比如下图中13的bh=2,15的bh=1。
在这里插入图片描述
由红黑树的性质可知,从根到任意一个叶结点的简单路径最短时,就是这条路径上全是黑结点。某条路径最长时,这条路径必然是由黑结点和红结点相间构成的。这样也就引出了结论1:从根到叶结点的最长路径不大于最短路径的两倍。这样就能够知道根的黑高至少为h/2,于是就有 n > = 2 h / 2 − 1 n>=2^{h/2}-1 n>=2h/21可以证得结论2。
可见红黑树的“适度平衡”,由AVL树的“高度平衡”,降低到“人体一个结点左右子树的高度,相差不会超过2倍”,也降低了动态操作时调整的频率。对于一颗动态查找树,若插入和删除操作比较少,查找操作比较多,则采用AVL树比较合适,否则采用红黑树比较合适。
但由于维护这种高度平衡所付出的代价比获得的收益大得多,红黑树的实际应用更广泛,比如C++中的map和set、Java中的TreeMap和TreeSet就是用红黑树实现的。

5.整体代码

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	T _data;
	Colour _col;	// 颜色

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
	}
	Node* Find(const T& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data < key)
			{
				cur = cur->_right;
			}
			else if (cur->_data > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	bool Insert(const T& data)
	{
		// 先按照二叉排序树的方式进行排序
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(data);
		if (data < parent->_data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			// 当叔叔在爷结点的右侧
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 当叔叔为红色
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				// 当叔叔为黑色
				else
				{
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
			// 当叔叔在爷结点的左侧
			else
			{
				Node* uncle = grandfather->_left;
				// 当叔叔为红色
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 当叔叔为黑色
				{
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}
	bool Isbalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根结点为红色" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}
		return _Check(_root, 0, benchmark);
	}
	int Height()
	{
		return _Height(_root);
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
protected:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}
	bool _Check(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "某条路径黑色节点数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
		{
			++blacknum;
		}
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续红色节点" << endl;
			return false;
		}
		return _Check(root->_left, blacknum, benchmark)
			&& _Check(root->_right, blacknum, benchmark);
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);
		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_data << " ";
		_InOrder(root->_right);
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		subR->_left = parent;
		parent->_right = subRL;
		Node* ppnode = parent->_parent;		// 记录parent的父结点

		parent->_parent = subR;

		if (subRL)
		{
			subRL->_parent = parent;
		}
		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}
	void RotateR(Node* parent)
	{
		// subL:parent的左孩子
		// subLR:parent的左孩子的右孩子,注意:该点可能不存在
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		subL->_right = parent;
		parent->_left = subLR;
		Node* ppnode = parent->_parent;		// 记录parent的父结点,用于连接新的子树

		parent->_parent = subL;

		if (subLR)
		{
			subLR->_parent = parent;
		}
		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}
private:
	Node* _root = nullptr;
};

void RBTreeTest1()
{
	// int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree<int> t1;
	for (auto e : a)
	{
		t1.Insert(e);
	}
	t1.InOrder();
	cout << t1.Isbalance() << endl;
}

void RBTreeTest2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(x);
	}
	cout << t.Isbalance() << endl;
	cout << t.Height() << endl;
}

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

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

相关文章

PTL系统电子标签让工厂仓储出入库管理更高效

在现代工厂的运营中&#xff0c;仓储管理的效率直接影响着整个生产流程的顺畅与企业的竞争力。PTL 系统&#xff08;Pick-to-Light&#xff09;的应用&#xff0c;为工厂仓储的出入库管理带来了革命性的变化&#xff0c;使其更加高效、精准和智能化。 一、仓库安装使用PTL系统电…

TF-IDF、BM25传统算法总结

1. TF-IDF算法 F-IDF&#xff08;词频-逆文档频率&#xff09;是一种用于衡量文本中词语重要性的方法&#xff0c;特别适用于信息检索和文本挖掘任务。下面会拆分为两部分深入讲解TF-IDF的计算过程&#xff0c;以便更好地理解。 TF-IDF的计算过程可以分为两个主要部分&#xf…

2.移植freertos到stm32f103c8t6

目录 1.步骤 2.freertos配置时常见的选项卡意思 1.步骤 2.freertos配置时常见的选项卡意思

typescript学习回顾(三)

今天继续来分享ts的相关概念&#xff0c;枚举&#xff0c;ts模块化&#xff0c;接口和类型兼容性 ts的扩展类型&#xff1a;类型别名&#xff0c;枚举&#xff0c;接口和类 枚举 基础概念 枚举通常用于约束某个变量的取值范围。当然字面量和联合类型配合使用&#xff0c;也可…

动态规划——123. 买卖股票的最佳时机 III

目录 1、题目链接 2、题目分析 1.状态表示 2.状态转移方程 3.初始化 4.填表 5.返回值 3、代码解析 1、题目链接 123. 买卖股票的最佳时机 III 2、题目分析 1.状态表示 由题目可知&#xff0c;我们分为两种状态&#xff0c;买入和卖出&#xff0c;又因为只能完成两次交易…

如何从iPhone恢复错误删除的照片

嘿&#xff0c;iPhone 用户&#xff01;作为一名苹果专业人士&#xff0c;我见过相当多的“哎呀&#xff0c;我删除了它&#xff01;”的时刻。今天&#xff0c;我在这里指导您完成从iPhone中恢复那些珍贵的&#xff0c;错误删除的照片的迷宫。坐下来&#xff0c;拿起你的设备&…

【2020-2023】Transformer在小目标检测领域的应用与发展综述

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

std::enable_if和std::is_base_of

std::enable_if,其主要为了完成模板特偏化&#xff0c;有两个参数&#xff0c;第一个为布尔值类型&#xff0c;第二个如果布尔值为true&#xff0c;其为默认空值&#xff0c;如果已经赋值&#xff0c;则为对应的类型。 std::is_base_of&#xff0c;其一共存在两个参数&#xff…

windows10/win11截图快捷键 和 剪贴板历史记录 快捷键

后知后觉的我今天又学了两招&#xff1a; windows10/win11截图快捷键 按 Windows 徽标键‌ Shift S。 选择屏幕截图的区域时&#xff0c;桌面将变暗。 默认情况下&#xff0c;选择“矩形模式”。 可以通过在工具栏中选择以下选项之一来更改截图的形状&#xff1a;“矩形模式”…

【每日刷题】Day76

【每日刷题】Day76 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 561. 数组拆分 - 力扣&#xff08;LeetCode&#xff09; 2. 删除有序链表中重复的元素-II_牛客题霸…

多接口分线盒在工业自动化中的重要性与应用

简介 多接口分线盒是现代工业自动化中不可或缺的一个组成部分&#xff0c;它主要用于简化复杂的接线系统&#xff0c;提高效率和可靠性。本文将详细探讨多接口分线盒的定义、功能、以及在工业自动化中的应用情况。 无源多接口分线盒 多接口分线盒的定义与功能 多接口分线盒是…

vue draggable

一、安装&#xff1a; npm i -S vuedraggablenext 二、代码 <draggable :list"projectOptions" item-key"name" class"w-25" ghost-class"ghost"chosen-class"chosen" update"updateSort" animation"3…

Ubuntu系统安装软件---以安装QQ为例

以安装QQ为例&#xff0c;首先你的Ubuntu系统需要连上网&#xff0c;连上网的网络状态如下图所示。 在ubuntu系统的网页中搜索QQ&#xff0c;如下图所示。 进入QQ官网&#xff0c;点击Linux&#xff0c;如下图所示。 随后会让你选择什么架构的版本&#xff0c;如何查看自己的是…

金融企业数据跨境流动的核心需求是什么?如何才能落地?

在金融行业&#xff0c;涉及到的数据跨境流动的场景多种多样&#xff0c;主要涉及到金融机构的跨国经营、全球贸易以及服务贸易等多个方面&#xff1a; 企业跨国经营&#xff1a;当金融机构进行跨国经营时&#xff0c;如银行在海外设立分支机构或进行跨境投资&#xff0c;会涉及…

神经网络学习8-反向传播

back propagation 拿到前面传回来的L对z的偏导&#xff0c;再分别算损失值对x和w的偏导 反向传播 前馈过程求局部梯度 反向传播 这里的loss&#xff08;wxb-y)^2,第一个关于b的偏导为2(wxb-y),第二个关于w的为2w(wxb-y)

记录待办事项的便签软件哪个好用?

在快节奏的现代生活中&#xff0c;我们经常需要处理各种各样的待办事项&#xff0c;为了更好地管理时间&#xff0c;许多人选择使用便签软件来记录自己的待办事项。那么&#xff0c;记录待办事项的便签软件哪个好用&#xff1f;市面上众多的便签软件中&#xff0c;哪一个才是最…

【新闻】金融专业“免进”!私募巨头招聘涌现“新剧情”

A股市场在2024年逐渐出现新的运行特征&#xff0c;这不禁让部分主动投资的私募巨头公司重新登上招聘舞台。 但这一次&#xff0c;他们的招聘方向出现了新的变动。 有些机构有意识的为公司投研团队招聘“衔接”岗&#xff0c;有些则把重点放在了投研动作的交易层。 但这都不如…

社区团购小程序开发

在快节奏的现代生活中&#xff0c;人们越来越追求便利与效率。社区团购小程序应运而生&#xff0c;以其独特的优势成为连接社区居民与优质商品的重要桥梁。本文将探讨社区团购小程序的特点、优势以及未来发展趋势&#xff0c;为大家揭示这一新型购物模式的魅力。 社区团购小程序…

Superset二次开发之导入导出功能源码解读

可导出的类型 支持 看板(Dashboard)、图表(Charts)、数据集(Datasets)、SQL(saved_query)、数据库(Database connection) 单次或批量的导出,和单次导入操作 看板(Dashboard) 图表(Charts) 数据集(Datasets) SQL (saved_query) 数据库(database connections)…

为什么需要对数据质量问题进行根因分析?根因分析该怎么做?

在当今的商业环境中&#xff0c;数据已成为企业决策的核心。然而&#xff0c;数据的价值高度依赖于其质量。低质量的数据不仅会降低分析的准确性&#xff0c;还可能导致错误的决策&#xff0c;从而影响企业的竞争力和市场表现。因此&#xff0c;识别和解决数据质量问题是数据管…