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

目录

  • 一、红黑树的定义
    • 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.mfbz.cn/a/762490.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQLAlchemy(alembic)和Flask-SQLAlchemy入门教程

SQLAlchemy 是 Python 生态中最流行的 ORM 类库&#xff0c;alembic 用来做 OMR 模型与数据库的迁移与映射&#xff0c;Flask-SQLAlchemy 是 Flask 的扩展&#xff0c;可为应用程序添加对 SQLAlchemy 的支持&#xff0c;简化 SQLAlchemy 与 Flask 的使用。 一.SQLAlchemy 和 a…

C++——vector类用法指南

一、vector的介绍 1、vector是表示可变大小数组的序列容器 2、就像数组一样&#xff0c;vector也采用连续存储空间来存储元素。也就意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它…

Linux C 程序 【01】最小程序

1.开发背景 基于 RK3568 平台的基础上&#xff0c;编译一个在系统上运行的最小程序。 2.开发需求 由于 RK3568 作为宿主机&#xff0c;在上面编译程序比较慢&#xff0c;所以还是采用在 Ubuntu 下交叉编译后再拷贝到宿主机上运行。 设计实验&#xff1a; 1&#xff09;搭建 M…

嵌入式学习——硬件(IIC、ADC)——day56

1. IIC 1.1 定义&#xff08;同步串行半双工通信总线&#xff09; IIC&#xff08;Inter-Integrated Circuit&#xff09;又称I2C&#xff0c;是是IICBus简称&#xff0c;所以中文应该叫集成电路总线。是飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备…

mybatis实现多表查询

mybatis高级查询【掌握】 1、准备工作 【1】包结构 创建java项目&#xff0c;导入jar包和log4j日志配置文件以及连接数据库的配置文件&#xff1b; 【2】导入SQL脚本 运行资料中的sql脚本&#xff1a;mybatis.sql 【3】创建实体来包&#xff0c;导入资料中的pojo 【4】User…

使用Colly库进行高效的网络爬虫开发

引言 随着互联网技术的飞速发展&#xff0c;网络数据已成为信息获取的重要来源。网络爬虫作为自动获取网页内容的工具&#xff0c;在数据分析、市场研究、信息聚合等领域发挥着重要作用。本文将介绍如何使用Go语言中的Colly库来开发高效的网络爬虫。 什么是Colly库&#xff1…

志愿者管理系统带讲解,保运行

技术栈 后端: SpringBoot Mysql MybatisPlus 前端: Vue Element 分为 管理员端 用户端 功能描述 用户端 管理员端 观看地址&#xff1a; B站 &#xff1a; 【毕设者】志愿者管理系统(安装讲解源码)

MQTT QoS 0, 1, 2

目录 # 开篇 1. 精细MQS TT QoS的行为 1.1 QoS 0: 最多交付一次&#xff08;At Most Once&#xff09; 1.2 QoS 1: 至少交付一次&#xff08;At Least Once&#xff09; 1.3 QoS 2: 只交付一次&#xff08;Exactly Once&#xff09; 1.4 传输过程图示 1.5 总结 2. MQTT…

如何避免爬取网站时IP被封?

互联网协议 (IP) 地址是识别网络抓取工具的最常见方式。IP 是每个互联网交换的核心&#xff0c;对其进行跟踪和分析可以了解很多有关连接客户端的信息。 在网络抓取中&#xff0c;IP 跟踪和分析&#xff08;又名指纹&#xff09;通常用于限制和阻止网络抓取程序或其他不需要的访…

面向阿克曼移动机器人(自行车模型)的LQR(最优二次型调节器)路径跟踪方法

线性二次调节器&#xff08;Linear Quadratic Regulator&#xff0c;LQR&#xff09;是针对线性系统的最优控制方法。LQR 方法标准的求解体系是在考虑到损耗尽可能小的情况下, 以尽量小的代价平衡其他状态分量。一般情况下&#xff0c;线性系统在LQR 控制方法中用状态空间方程描…

汇聚荣拼多多电商好不好?

拼多多电商好不好?这是一个值得探讨的问题。拼多多作为中国领先的电商平台之一&#xff0c;以其独特的商业模式和创新的营销策略吸引了大量用户。然而&#xff0c;对于这个问题的回答并不是简单的好或不好&#xff0c;而是需要从多个方面进行综合分析。 一、商品质量 来看拼多…

【源码+文档+调试讲解】居家养老系统

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了居家养老系统的开发全过程。通过分析高校学生综合素质评价管理方面的不足&#xff0c;创建了一个计算机管理居家养老系统的方案。文章介绍了居家养老系统的系统分…

jvm性能监控常用工具

在java的/bin目录下有许多java自带的工具。 我们常用的有 基础工具 jar:创建和管理jar文件 java&#xff1a;java运行工具&#xff0c;用于运行class文件或jar文件 javac&#xff1a;java的编译器 javadoc&#xff1a;java的API文档生成工具 性能监控和故障处理 jps jstat…

Sourcecodester Fantastic Blog CMS v1.0 SQL 注入漏洞(CVE-2022-28512)

前言 CVE-2022-28512 是一个存在于 Sourcecodester Fantastic Blog CMS v1.0 中的 SQL 注入漏洞。攻击者可以通过 "/fantasticblog/single.php" 中的 id 参数注入恶意 SQL 查询&#xff0c;从而获得对数据库的未经授权的访问和控制。 漏洞详细信息 漏洞描述: 该漏…

JavaScript将参数传递给事件处理程序

本篇文件我们将实现导航栏中&#xff0c;选中时候&#xff0c;会将您选中的进行高亮显示&#xff1b; ● 首先我们来获取我们想要的HTML元素 const nav document.querySelector(.nav);● 接着我们来写选中的高亮显示 nav.addEventListener(mouseover, function (e) { //鼠…

内网穿透小工具

内网穿透小工具 前言 当在本地或者虚拟机&#xff0c;内网搭建了项目&#xff0c;数据库。可是在外网无法访问。下面的两款小工具可以暂时实现内网穿透能力。&#xff08;不支持自定义域名&#xff0c;但是不限制隧道数量&#xff01;且免费&#xff01;免费&#xff01;免费…

【小贪】项目实战——Zero-shot根据文字提示分割出图片目标掩码

目标描述 给定RGB视频或图片&#xff0c;目标是分割出图像中的指定目标掩码。我们需要复现两个Zero-shot的开源项目&#xff0c;分别为IDEA研究院的GroundingDINO和Facebook的SAM。首先使用目标检测方法GroundingDINO&#xff0c;输入想检测目标的文字提示&#xff0c;可以获得…

互联网框架五层模型详解

注&#xff1a;机翻&#xff0c;未校对。 What is the Five Layers Model? The Framework of the Internet Explained 五层模型互联网框架解释 Computer Networks are a beautiful, amazing topic. Networks involve so much knowledge from different fields, from physics…

[OHOS_ERROR]: Please call hb utilities inside ohos source directory

当执行hb set报如下错误时&#xff1a;原因时重新拉取了源码&#xff0c;且源码路径被改了 [OHOS_ERROR]: Please call hb utilities inside ohos source directory 【解决办法】 卸载hb并在源码路径下重新安装 python3 -m pip uninstall ohos-build 安装hb python3 -m pi…

python-逻辑语句

if else语句 不同于C&#xff1a;else if range语句&#xff1a; continue continue的作用是&#xff1a; 中断所在循环的当次执行&#xff0c;直接进入下一次 continue在嵌套循环中的应用 break 直接结束所在的循环 break在嵌套循环中的应用 continue和break&#xff0c;在…