c++进阶之------红黑树

news2025/3/26 9:31:45

一、概念

红黑树(Red-Black Tree)是一种自平衡二叉查找树,它在计算机科学的许多领域中都有广泛应用,比如Java中的TreeMap和C++中的set/map等数据结构的底层实现。红黑树通过在每个节点上增加一个颜色属性(红色或黑色),并遵循一定的规则来确保树的平衡性,从而保证了各项操作的时间复杂度为O(log n)。

二、性质

红黑树具有以下性质:

  1. 每个节点是红色或黑色。

  2. 根节点是黑色。

  3. 所有叶子节点(NIL节点)是黑色。

  4. 如果一个节点是红色,则它的两个子节点都是黑色。(注:这条规则保证了从任一节点到其每个叶子节点的所有路径上不会出现两个连续的红色节点)

  5. 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。

三、构建红黑树

3.1 树的基本结构

#pragma once

// 枚举值表示颜色
enum color
{
	RED,
	BLACK
};
// 这里我们默认按key/value结构实现
template<class K,class V>
struct RBTreeNode
{
	// 这里更新控制平衡也要加入parent指针
	pair<k, v> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	color _col;

	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};
template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
private:
	Node* _root = nullptr;
};

3.2 树的插入

在红黑树中插入一个节点后,可能会破坏红黑树的性质,因此需要通过旋转和重新着色来恢复红黑树的平衡。插入操作的具体步骤如下:

  1. 插入节点:按照普通二叉查找树的插入方式插入新节点,并将新节点着色为红色。

  2. 检查平衡性:检查插入新节点后是否破坏了红黑树的性质。如果插入的新节点是根节点,则直接将其着色为黑色;如果新节点的父节点是黑色,则红黑树的性质未被破坏,插入结束;如果新节点的父节点是红色,则需要进行修复。

  3. 修复红黑树性质:根据新节点的父节点和叔叔节点的颜色以及它们的位置关系,分为以下几种情况:

    • 情况一:叔叔节点是红色:将父节点和叔叔节点着色为黑色,祖父节点着色为红色,然后将祖父节点作为新的当前节点,继续检查。

    • 情况二:叔叔节点是黑色,且当前节点是父节点的左孩子:此时要进行旋转来解决,单纯的变色已经不能解决问题了,以爷爷结点为基准右旋之后在变色

    • 情况三:叔叔节点是黑色,且当前节点是父节点的右孩子:此时要进行左右双旋转,具体参见代码

注:情况123均需要考虑u  p 还是p  u 的问题!!! 

 代码显示:

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)
	{
		if (cur->_kv.first<kv.first)
		{
			//插入的数大
			parent = cur;
			cur = cur->_right;
		}
		else if(cur->_kv.first>kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	//找到插入的位置了
	cur=new Node(kv);
	//要是插入黑节点会导致性质5不符合,所以插红的
	cur->_col = RED;
	//考虑做左孩子还是右孩子
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;      //别忘了把_parent给到parent,否则下文的parent还是空!
	//由于我插入的结点必须为红,而红黑树又要求不能有两个连着的红结点
	//所以我们要判断一下父节点的颜色
	while (parent && parent->_col == RED)
	{
		//父节点为红色
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)                    //parent为左孩子   
		{
			//   g                      黑
            // p   u                 红     红
			Node* uncle = grandfather->_right;
			// 叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;   //g和u都为红,那么一起变色就o而k之
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				// 叔叔不存在或者存在且为黑
                //      g                  黑
                //   p     u            红    黑
                //c                  红
				if (cur == parent->_left)
				{
					RotateR(grandfather);          //单纯变色无法解决,旋转一下
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//      g
                    //   p     u
                    //      c
					RotateL(parent);
					RotateR(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;              //处理的是一个子树,这个符合了上面的也就Ok了
			}
		}     
		else                    //parent为右孩子
		{
			//   g                   黑
			// u   p             红      红
			Node* uncle = grandfather->_left;
			// 叔叔存在且为红,-》变色即可
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				// 叔叔不存在或者存在且为黑
				// // 旋转+变色
                //      g                  黑
                //   u     p            黑     红
                //             c       红
				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;
	return true;
}

3.3 遍历红黑树

 我们还是使用中序遍历遍历红黑树,代码如下:

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

3.4 检查红黑树

 检查的方法很简单,就是通过随便跑一条路,数一下黑色节点,并将其作为参考值,之后在遍历整棵树,看看是不是每条路上的黑色节点数都是等于参考值的,换句话说,如果我们的树满足红黑树的性质,那么就o而k之,否则就不o而k之~

	// 前序递归遍历
	bool Check(Node* root, int blacknum, const int refnum)
	{
		if (root == nullptr)
		{
			// 前序遍历走到空时,意味着一条路径走完了
            //cout << blackNum << endl;
			if (refnum != blacknum)
			{
				cout << "存在黑色结点的数量不相等的路径" << endl;
				return false;
			}
			return true;
		}
		// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色结点" << endl;
			return false;
		}
		if (root->_col == BLACK)
		{
			blacknum++;
		}
		return Check(root->_left, blacknum, refnum) 
			&& Check(root->_right, blacknum, refnum);
	}

	bool IsBalanceTree()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			return false;
		}

		//设置一个参考值
		int refnum = 0;
		Node* cur = _root;
		//随便选一条路看看几个黑的
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				refnum++;
			}
			cur = cur->_left;
		}
		return Check(_root, 0, refnum);
	}

3.5 代码测试

#include<iostream>
#include<vector>
using namespace std;

#include"RBTree.h"

// 测试代码
void TestRBTree1()
{
	RBTree<int, int> t;
	 //常规的测试用例
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		t.Insert({ e, e });
	}
	t.InOrder();
	cout << t.IsBalanceTree() << endl;
}

void TestRBTree2()
{
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	RBTree<int, int> t;
	for (size_t i = 0; i < v.size(); ++i)
	{
		t.Insert(make_pair(v[i], v[i]));
	}
	cout << t.IsBalanceTree() << endl;
}

int main()
{
	TestRBTree1();
	TestRBTree2();
	return 0;
}

3.6 代码汇总

这里只放.h文件的!

#pragma once
#include<iostream>
#include<utility>
using namespace std;
// 枚举值表示颜色
enum color
{
	RED,
	BLACK
};
// 这里我们默认按key/value结构实现
template<class K,class V>
struct RBTreeNode
{
	// 这里更新控制平衡也要加入parent指针
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	color _col;

	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};
template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	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)
		{
			if (cur->_kv.first<kv.first)
			{
				//插入的数大
				parent = cur;
				cur = cur->_right;
			}
			else if(cur->_kv.first>kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//找到插入的位置了
		cur=new Node(kv);
		//要是插入黑节点会导致性质5不符合,所以插红的
		cur->_col = RED;
		//考虑做左孩子还是右孩子
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;      //别忘了把_parent给到parent,否则下文的parent还是空!
		//由于我插入的结点必须为红,而红黑树又要求不能有两个连着的红结点
		//所以我们要判断一下父节点的颜色
		while (parent && parent->_col == RED)
		{
			//父节点为红色
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)                    //parent为左孩子   
			{
				//   g                      黑
                // p   u                 红     红
				Node* uncle = grandfather->_right;
				// 叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;   //g和u都为红,那么一起变色就o而k之
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 叔叔不存在或者存在且为黑
                    //      g                  黑
                    //   p     u            红    黑
                    //c                  红
					if (cur == parent->_left)
					{
						RotateR(grandfather);          //单纯变色无法解决,旋转一下
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//      g
                        //   p     u
                        //      c
						RotateL(parent);
						RotateR(grandfather);
						grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break;              //处理的是一个子树,这个符合了上面的也就Ok了
				}
			}     
			else                    //parent为右孩子
			{
				//   g                   黑
				// u   p             红      红
				Node* uncle = grandfather->_left;
				// 叔叔存在且为红,-》变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 叔叔不存在或者存在且为黑
					// // 旋转+变色
                    //      g                  黑
                    //   u     p            黑     红
                    //             c       红
					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;
		return true;
	}


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

		parent->_left = subLR;
		subL->_right = parent;

		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = parentParent;
		}
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (parentParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
	}

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

	// 前序递归遍历
	bool Check(Node* root, int blacknum, const int refnum)
	{
		if (root == nullptr)
		{
			// 前序遍历走到空时,意味着一条路径走完了
            //cout << blackNum << endl;
			if (refnum != blacknum)
			{
				cout << "存在黑色结点的数量不相等的路径" << endl;
				return false;
			}
			return true;
		}
		// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色结点" << endl;
			return false;
		}
		if (root->_col == BLACK)
		{
			blacknum++;
		}
		return Check(root->_left, blacknum, refnum) 
			&& Check(root->_right, blacknum, refnum);
	}

	bool IsBalanceTree()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			return false;
		}

		//设置一个参考值
		int refnum = 0;
		Node* cur = _root;
		//随便选一条路看看几个黑的
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				refnum++;
			}
			cur = cur->_left;
		}
		return Check(_root, 0, refnum);
	}
private:
	Node* _root = nullptr;
};

四、总结及应用

在红黑树中,我们省去了ALV树的平衡因子,而换用颜色来判断树的平衡,这样使得代码的逻辑变得简单明了 ,后续我们对map和set的封装(实现mymap和myset)便会用到红黑树的思想与结构!

红黑树通过其自平衡的特性,确保了在插入、删除和查找操作中的高效性,使其成为许多实际应用中不可或缺的数据结构。

 

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

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

相关文章

ubuntu22.04安装搜狗输入法保姆教程~

一、添加中文语言支持 1.首先打开设置,找到Language and Region 2.点击Manage Installed Languages 3.点击 Install/Remove Languages... 4.选中Chinese (simplified),点击Apply

《数据库原理》SQLServer期末复习_题型+考点

目录 题型&#xff1a; 一. 概况分析题&#xff08;5小题&#xff0c;每小题2分&#xff0c;共10分&#xff09; 二. 计算题&#xff08;3小题&#xff0c;每小题5分&#xff0c;共15分&#xff09; 三. 数据库设计&#xff08;2小题&#xff0c;每小题10分&#xff0c;共2…

Zstd(Zstandard)压缩算法

要压缩的数据量越小&#xff0c;压缩的难度就越大。这个问题对所有压缩算法都是通用的&#xff0c;原因是压缩算法从过去的数据中学习如何压缩未来的数据。但是&#xff0c;在新数据集开始时&#xff0c;没有“过去”可以构建。 官网 为了解决这种情况&#xff0c;Zstd 提供了一…

本地部署 browser-use

本地部署 browser-use 0. 引言1. 核心功能与优势2. 快速上手3. 部署 Gradio UI4. 更多示例0. 引言 Browser-Use 是一个强大的工具,旨在让 AI Agent 能够控制浏览器,从而实现各种自动化任务。它简化了 AI 与浏览器的交互,让开发者能够轻松构建能够执行网页操作的智能应用。本…

Vite管理的Vue3项目中monaco editer的使用以及组件封装

文章目录 背景环境说明安装流程以及组件封装引入依赖封装组件 外部使用实现效果 v-model实现原理 背景 做oj系统的时候,需要使用代码编辑器,决定使用Monaco Editor&#xff0c;但是因为自身能力问题&#xff0c;读不懂官网文档&#xff0c;最终结合ai和网友的帖子成功引入&…

[笔记] TinyWebServer编译及demo运行过程

文章目录 前言环境搭建ubuntumysql 8.0c/c开启root用户TinyWebServer 搭建及编译过程运行结果常见问题./threadpool/../CGImysql/sql_connection_pool.h:6:10: fatal error: mysql/mysql.h: No such file or directory./server运行后直接退出了 前言 哎 也就帮帮新手看看问题 …

基于springboot的电影院管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 互联网技术的成熟和普及&#xff0c;势必会给人们的生活方式带来不同程度的改变。越来越多的经营模式中都少不了线上运营&#xff0c;互联网正强力推动着社会和经济发展。国人对民族文化的自信和不同文化的包容&#xff0c;再加上电影行业的发展&#xff0c;如此繁荣吸引…

虚拟电商-延迟任务系统的微服务改造(二)注册中心和Feign调用

一、微服务注册中心Consul 编写完延迟任务系统的web层接口&#xff0c;也就是说可以基于http协议来访问延迟系统&#xff0c;接下来要将延迟任务改造成一个服务。首要考虑的问题就是服务的注册与发现&#xff0c;服务的注册与发现都离不开服务的注册中心&#xff0c;本项目选取…

数智读书笔记系列022《算力网络-云网融合2.0时代的网络架构与关键技术》读书笔记

一、书籍核心价值与定位 1.1 书籍概述:中国联通研究院的权威之作 《算力网络 —— 云网融合 2.0 时代的网络架构与关键技术》由中国联通研究院算力网络攻关团队精心撰写,是业界首部系统性探讨云网融合 2.0 与算力网络的专著。在云网融合从 1.0 迈向 2.0 的关键节点,本书的…

第十六届蓝桥杯康复训练--6

题目链接&#xff1a;790. 数的三次方根 - AcWing题库 思路&#xff1a;二分&#xff0c;注意正负号和小数判断退出的方法&#xff08;虽然正负无所谓&#xff09; 代码&#xff1a; #include<bits/stdc.h> using namespace std;#define exs 0.00000018812716007232667…

logisim安装以及可能出现的问题

阅读提示&#xff1a;我这篇文章更偏向于安装出现问题的解决方案 目录 一、安装步骤 二、安装问题 1、出错的问题 2、出错的原因与解决方法 一、安装步骤 1、下载logisim 官方网站&#xff1a;https://sourceforge.net/projects/circuit/ 下载适用于你操作系统的版本&…

Servlet、HttpServletRequest、HttpServletResponse、静态与动态网页、jsp、重定向与转发

DAY15.2 Java核心基础 JavaWeb 要想通过浏览器或者客户端来访问java程序&#xff0c;必须通过Servlet来处理 没有Servlet&#xff0c;java是无法处理web请求的 Web交互&#xff1a; 接收请求HttpServletRequest&#xff1a;可以获取到请求的信息&#xff0c;比如uri&#…

hackmyvm-Icecream

arp-scan -l nmap -sS -v 192.168.222.106 enum4linux 192.168.222.106 445端口 smbmap -H 192.168.222.106 icecream为只读模式 smbclient \\192.168.222.106\icecream 反弹shell(上传put php-reverse-shell.php) 开启监听 nc -lnvp 1234 拿到webshell cat /etc/passwd 9000端…

告别低效人工统计!自动计算计划进度

实时监控任务进度一直是项目管理中的一项巨大挑战。 人工统计方式不仅耗时耗力&#xff0c;而且往往由于信息传递的延迟和人为误差&#xff0c;导致无法实时获得准确的项目进展信息。 这种不准确性可能掩盖潜在的风险点&#xff0c;从而影响项目的整体进度和成果。 Ganttable …

AI比人脑更强,因为被植入思维模型【16】反脆弱

毛选中就有言&#xff0c;不经历困难&#xff0c;我们就不会掌握战胜困难的方法。 这个世界纷繁复杂&#xff0c;不是强者总是运气好&#xff0c;而是他们能够失败后快速复原&#xff0c;不断找到战胜困难的方法。 定义 马斯洛需求层次模型是一种将人类需求从低到高按层次进…

L2TP实验

放开安全策略机制&#xff0c;FW1不配IP [FW1]firewall zone trust [FW1-zone-trust]add interface GigabitEthernet 1/0/0 [FW1]security-policy [FW1-policy-security]default action permit FW2 和FW3 [FW2]interface g1/0/1 [FW2-GigabitEthernet1/0/1]ip address 2…

【数据预测】基于遗传算法GA的LSTM光伏功率预测 GA-LSTM光伏功率预测【Matlab代码#91】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 遗传算法GA2. 长短期记忆网络LSTM3. 基于GA-LSTM的光伏功率预测4. 部分代码展示5. 运行结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】 …

【记录一下】LMDeploy学习笔记及遇到的问题

LMDeploy 是一个用于大型语言模型&#xff08;LLMs&#xff09;和视觉-语言模型&#xff08;VLMs&#xff09;压缩、部署和服务的 Python 库。 其核心推理引擎包括 TurboMind 引擎和 PyTorch 引擎。前者由 C 和 CUDA 开发&#xff0c;致力于推理性能的优化&#xff0c;而后者纯…

HC-05与HC-06蓝牙配对零基础教程 以及openmv识别及远程传输项目的概述

这个是上一年的项目&#xff0c;之前弄得不怎么完整&#xff0c;只有一个openmv的&#xff0c;所以openmv自己去我主页找&#xff0c;这篇主要讲蓝牙 这个是我在使用openmv连接单片机1然后单片机1与单片机2通过蓝牙进行通信 最终实现的效果是&#xff1a;openmv识别到图形和数…

Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测

Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测 目录 Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预…