带你手撕红黑树! c++实现 带源码

目录

一、概念

二、特性

三、接口实现

1、插入

情况一:p为黑,结束

情况二:p为红

1)叔叔存在且为红色

2)u不存在/u存在且为黑色

(1)p在左,u在右

(2)u在左,p在右

2、检查平衡

四、对红黑树的理解

五、原码


一、概念

红黑树:AVL树不好控制(严格平衡),所以推出了红黑树
不用高度控制平衡,用颜色
最长路径<= 最短路径*2
红黑树是近似平衡

二、特性

1、每个节点不是红色就是黑色
2、根节点是黑色的
3、如果一个节点是红色的,则他的两个孩子是黑色的(不存在连续的红色节点)
4、如果对于每个节点,从该节点到其后代节点的简单路径上,均包含相同数目的黑色节点(每条路径的黑色节点数量相等)
5、每个叶子节点都是黑色的(叶子节点指的是空节点)

最短路径:全黑
最长路径:一黑一红

三、接口实现

1、插入

说明:p为父,cur为当前节点,u为叔叔节点,g为祖父节点

情况一:p为黑,结束

情况二:p为红

1)叔叔存在且为红色

怎么办?
将p和u变黑,g变红    
    (1)如果g是根节点,再变为黑色


    (2)如果g不是根,继续往上调整 g变cur, parent = g->parent


        有可能会一路更新到根节点,即父节点不存在
        c++库内部的处理是,直接将parent作为while循环条件之一
        然后,在单次数循环结束位置将parent置为黑
        保证parent为黑

2)u不存在/u存在且为黑色

p改黑,g改红,再旋转

(1)p在左,u在右

情况1
//        g
//      p          u
//  c
//
p在左,u在右:以g进行右单旋
p变黑,g变红
 
情况2
//        g
//      p          u
//            c
//
c在右:以p进行左单旋变为情况1
//        g
//      c          u
//  p
//
需修改p和c位置
//        g
//      p          u
//  c
//
再以g右单旋转
p变黑,g变红

(2)u在左,p在右

情况1
//        g
//      u          p
//                  c
//
以g进行左单旋
p变黑,g变红
 


情况2
//        g
//      u          p
//                   c
//
以p进行右单旋变为情况1
//        g
//      u          c
//                p
//
需修改p和c位置
//        g
//      u          p
//                 c
//
再以g右单旋转
p变黑,g变红

2、检查平衡


计算每一条路径的黑色节点的个数
检查是否有连续红色节点
递归
走到空的时候,说明该路径走到头了


p为红之后,就要判断p是左边还是右边
即:u在左,p在右 和 u在右,p在左
就是在p为红色的同时话要细分为两种大情况
然后对于内部还要进行u的判断

四、对红黑树的理解

红黑树的核心,是保证最长路径的长度不超过最短路径的两倍
怎么做到整个特性呢?
通过维持其四个特性
尤其是特性三和特性四
所以,在插入的时候,就要考虑不能打破特性3和特性4
特性3是不能有连续的红色节点
特性4是所有路径的黑色节点个数相同
相比之下,维持特性4明显要比特性3更加严格
维持成本更高,同时也更加难以控制
所以,在插入的时候,为了便于控制和成本
我们选择插入红色节点
剩下的问题,就是要怎么避免出现连续两个红色节点
如果插入的时候,父节点就已经是一个黑色节点
那么,直接插入,此时不会出现连续两个红色节点
同时,这个条路径的黑色节点个数也没有发生变化
但是,如果插入的父节点是一个红色节点呢?
问题来了
怎么办?
父节点是红色,插入的也是红色
只能有一个变黑色
谁变?
新插入节点吗?
如果新插入节点是黑色,那么插入路径黑色节点个数就增大了
就要去维护其他的所有路径的
何其恐怖
所以,只能父节点变黑色
同时,如果父亲有兄弟,就是叔叔节点存在
父节节点变为黑色,父亲节点的路径黑色节点多了一个
那么作为另外一条路径的叔叔节点,也必须变为黑色,也增加一个黑色节点,才能保持
而,父节点的父节点,即祖父节点一定存在且为黑色
因为父节点和叔叔节点(如果存在)已经变为黑色
那么,对于祖父作为根节点的这课子树来说,多了一个黑色节点
因此,祖父节点必须变为红色
以保持平衡
如此,以祖父节点作为根节点的这棵子树已经保持了黑色节点数量不变
但是因为祖父节点已经变为了红色,需要继续往上更改颜色

五、原码

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


	enum Colour
	{
		BLACK,
		RED
	};

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

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

	};


	template<class K, class V>
	class BRTree
	{
		typedef BRTreeNode<K, V> Node;
	public:

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

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

			while (cur)
			{

				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else//找到相等key
				{
					return false;
				}
			}

			cur = new Node(kv);
			cur->_col = RED;
			if (kv.first < parent->_kv.first)//插入左
			{
				parent->_left = cur;
			}
			else //插入右
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//插入之后,要进行颜色调整
			while (parent && parent->_col == RED)//如果为空/黑色节点,直接结束
			{

				//
				Node* grandfather = parent->_parent;

				if (parent == grandfather->_left)//p为左,u为右
				{
					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      u
							//  c
							//
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else
						{
							//		   g
							//	   p      u
							//      c
							//
							RotateL(parent);
							//		   g
							//	   c      u
							//  p
							//
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}

						break;
					}

				}
				else//p为右,u为左
				{
					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)
						{
							//		   g
							//	   u      p
							//					c
							//
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else
						{
							//		   g
							//	   u      p
							//          c
							//
							RotateR(parent);
							//		   g
							//	   u      c
							//  				p
							//
							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;
				if (subLR)//subLR可能为空
				{
					subLR->_parent = parent;
				}

				subL->_right = parent;
				Node* ppNode = parent->_parent;
				parent->_parent = subL;

				//注意修改顺序
				if (parent == _root)
				{
					_root = subL;
					_root->_parent = nullptr;
				}
				else
				{
					if (ppNode->_left == parent)
					{
						ppNode->_left = subL;
					}
					else
					{
						ppNode->_right = subL;
					}
					subL->_parent = ppNode;
				}

			}

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

				parent->_right = subRL;
				if (subRL)
				{
					subRL->_parent = parent;
				}

				subR->_left = parent;

				Node* ppNode = parent->_parent;
				parent->_parent = subR;

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

			}

			//检查平衡
			bool isBalance()
			{
				if (_root->_col == RED)
				{
					return false;
				}
				
				//找到任意一条路黑色节点个数
				Node* cur = _root;
				int refNum = 0;
				while (cur)
				{
					if (cur->_col == BLACK)
					{
						refNum++;
					}
					cur = cur->_left;
				}
				return Check(_root, 0,  refNum);
				return 1;
			}

			
			void Inoder()
			{
				_Inoder(_root);
				cout << endl;
			}

		private:

			bool Check(Node* root,int blackNum,const int refNum)
			{
				//到路径结束位置检查黑色节点
				if (root == nullptr)
				{
					if (blackNum != refNum)
					{
						cout << "黑色节点不相等" << endl;
						return false;
					}
					// << blackNum << endl;
					return true;
				}

				//检查红色节点
				if (root->_col == RED && 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);
			}

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

	private:
		Node* _root = nullptr;

	};


		void BRTreeTest1()
		{

			int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
			int b[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,8, 3, 1, 10, 6, 4, 7, 14, 13 };
			BRTree<int, int> t;
			for (auto e : b)
			{
				t.Insert({ e,e });
			}
			t.Inoder();

			int ret = t.isBalance();
			cout << ret << endl;

		}



		void BRTreeTest2()
		{

			int n = 10000000;//1000万个节点进行测试
			srand(time(0));
			vector<int> v;
			v.reserve(n);
			for (int i = 0; i < n; ++i)
			{
				v.push_back(rand() + i);
			}

			size_t T1 = clock();
			BRTree<int, int> t;
			for (auto e : v)
			{
				t.Insert(make_pair(e, e));
			}
			size_t T2 = clock();


			cout << "insert:" << T2 - T1 << endl;
			int ret = t.isBalance();
			cout << ret << endl;


		}

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

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

相关文章

爱分析基于杭州云器Lakehouse实现成本最优的一体化管理,新一代数据平台的建设方式

导读 1.当前&#xff0c;企业在大数据和数据中台建设上取得成果&#xff0c;但数据开发管理仍具挑战性&#xff08;成本、效率、复杂度&#xff09;。 2.随数据平台领域成熟&#xff0c;厂商应结合自身需求&#xff0c;重新思考“基于开源自建数据平台”的重资产模式与“购买…

Windows单机部署RocketMQ5.X并与SpringBoot集成

RocketMQ 5.X下载 《RocketMQ 5.X下载》 下载后&#xff0c;解压缩 修改初始内存 修改runserver.cmd&#xff08;Linux则为runserver.sh&#xff09; 将下面的-Xms2g -Xmx2g -Xmn1g调小 if %JAVA_MAJOR_VERSION% lss 17 (set "JAVA_OPT%JAVA_OPT% -server -Xms2g -X…

传感网应用开发教程--AT指令访问新大陆云平台(ESP8266模块+物联网云+TCP)

实现目标 1、熟悉AT指令 2、熟悉新大陆云平台新建项目 3、具体目标&#xff1a;&#xff08;1&#xff09;注册新大陆云平台&#xff1b;&#xff08;2&#xff09;新建一个联网方案为WIFI的项目&#xff1b;&#xff08;3&#xff09;ESP8266模块&#xff0c;通过AT指令访问…

计算机毕业设计python校园二手交易系统aqj3i-

什么叫三层架构呢&#xff1f;指的是表示层、组件层、数据访问层。组件层是双层架构没有的&#xff0c;它的加入&#xff0c;把复杂的问题分解得更简单、明了&#xff0c;通过组件层&#xff0c;实现控制数据访问层&#xff0c;这样达到功能模块易于管理、易于访问等目的&#…

【python量化交易】qteasy使用教程06——创建自定义因子选股交易策略

创建自定义因子选股策略 使用qteasy创建自定义因子选股交易策略开始前的准备工作本节的目标Alpha选股策略的选股思想计算选股指标用FactorSorter定义Alpha选股策略交易策略的回测结果用GeneralStg定义一个Alpha选股策略回测结果&#xff1a;本节回顾 使用qteasy创建自定义因子选…

【UnityRPG游戏制作】Unity_RPG项目_PureMVC框架应用

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

Redis系列-3 Redis缓存问题

1.缓存的作用 数据库(如Mysql)的持久化特点带来了较低的性能&#xff0c;高并发的场景下&#xff0c;连接池很快被耗尽而出现宕机或DOS&#xff0c;无法继续对外提供服务。相对于数据库的硬盘IO&#xff0c;缓存中间件基于内存进行读写&#xff0c;从而具备较大的吞吐量和高并…

5.10.6 用于乳腺癌超声图像分类的Vision Transformer

医学超声&#xff08;US&#xff09;成像由于其易用性、低成本和安全性已成为乳腺癌成像的主要方式。卷积神经网络&#xff08;CNN&#xff09;有限的局部感受野限制了他们学习全局上下文信息的能力。利用 ViT 对使用不同增强策略的乳房 US 图像进行分类。 卷积神经网络&#…

你知道C++多少——默认成员函数

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

Linux中gitlab-runner部署使用备忘

环境&#xff1a; 操作系统:&#xff1a;CentOS8 gitlab版本&#xff1a;13.11.4 查看gitlab-runner版本 可以从https://packages.gitlab.com/app/runner/gitlab-runner/search找到与安装的gitlab版本相近的gitlab-runner版本以及安装命令等信息&#xff0c;我找到与13.11.4相…

网络 | 应用层-websocket协议概述与握手过程解析

背景&#xff1a;这里为了实现消息实时传输决定引入websocket协议。 不管是发送消息还是接收消息&#xff0c;都需要实时传输&#xff0c;张三发给李四&#xff0c;李四立马就能收到&#xff0c;基于HTTP实现是有些困难的。 但轮询方式也带来了一些问题 1、消耗更多系统资源&…

设计模式——模板设计模式(Template Method)

模板设计-base 什么是模板&#xff1f; 举个简单的例子&#xff0c;以AABB的格式&#xff0c;写出一个词语&#xff0c;你可能会想到&#xff0c;明明白白&#xff0c;干干净净等&#xff0c; 这个AABB就是一个模板&#xff0c;对模板心中有了一个清晰的概念之后&#xff0c;…

draw.text((left, top - 15), text,font=font, fill=“green”)

这是一个Python PIL库中的方法&#xff0c;用于在图片上绘制文本。具体来说&#xff0c;它可以在指定的位置绘制指定的文本&#xff0c;并使用指定的字体、颜色等参数进行渲染。其中&#xff0c;left和top是文本绘制的左上角坐标&#xff0c;text是要绘制的文本内容&#xff0c…

揭秘高效引流获客的艺术:转化技巧大公开

在数字化营销的海洋中&#xff0c;每个企业都如同一艘努力航行的船&#xff0c;而流量便是推动船只前行的风帆。如何有效吸引并获取潜在客户&#xff0c;即所谓的“引流获客”&#xff0c;已成为企业市场营销策略中不可或缺的一环。本文将详细探讨几种实用且高效的引流获客技巧…

Vue从入门到实战Day04

一、组件的三大组成部分&#xff08;结构/样式/逻辑&#xff09; 1. scoped样式冲突 默认情况&#xff1a;写在组件中的样式会全局生效 -> 因此很容易造成多个组件之间的样式冲突问题。 1. 全局样式&#xff1a;默认组件中的样式会作用到全局 2. 局部样式&#xff1a;可以…

0X JavaSE-- UML、

# Unified Modeling Language UML 统一建模语言 UML 是一种图形化的语言。 UML 不是专门为 Java 准备的。 只要是面向对象的编程语言&#xff0c;开发前的设计&#xff0c;都需要画 UML 图进行系统设计。 最常用的四个 UML 图是 类图&#xff08;Class Diagram&#xff09;&…

微信小程序踩坑,skyline模式下,简易双向绑定无效

工具版本 基础库版本 Skline模式 页面json设置 问题描述 skyline模式下,textarea,input标签设置简易双向绑定 model:value是无效的,关闭skyline模式就正常使用了 截图展示 这里只展示了textarea标签,input标签的简易双向绑定也是无效的 总结 我在文档里面是没找到skyline里面不…

考研踩坑经验分享

文章目录 写在前面自身情况简介自身学习路线优点坑点 学习路线建议1、2和3月份3、4和5月份6、7和8月份9、10月份11、12月份 一些私货建议结尾 写在前面 考研是一件非常有盼头的事&#xff0c;但绝对不是一件容易的事。 如果你不能做好来年三月份出成绩时&#xff0c;坦然接受…

英语复习之英语形近词总结(四)

英语形近词总结复习第四部分&#xff1a; 单词 释义例句 genuine 英 /ˈdʒenjuɪn/ 美 /ˈdʒenjuɪn/ adj.真实的&#xff0c;真正的&#xff1b;诚恳的 1.Only genuine refugees can apply for asylum. 只有真正的难民才能申请政治避难。 《牛津词典》 2.This isnt a genui…

Leaflet.canvaslabel在Ajax异步请求时bindPopup无效的解决办法

目录 前言 一、场景重现 1、遇到问题的代码 2、问题排查 二、通过实验验证猜想 1、排查LayerGroup和FeatureGroup 2、排查Leaflet.canvaslabel.js 三、柳暗花明又一村 1、点聚类的办法 2、歪打正着 总结 前言 在上一篇博客中介绍了基于SpringBoot的全国风景区WebGIS按…