C++模拟实现红黑树

news2024/11/29 10:41:19

目录

介绍----什么是红黑树

 甲鱼的臀部----规定

分析思考

绘图解析+代码实现

节点部分

插入部分+分步解析

●父亲在祖父的左,叔叔在祖父的右:

●父亲在祖父的右,叔叔在祖父的左:

测试部分

整体代码


介绍----什么是红黑树

红黑树基于二叉搜索树,它和AVL树一样避免了二叉搜索树中极端场景单边树的情况,保证了检索的效率。红黑树允许最长路径是最短路径的二倍,相较于AVL树而言是近似于平衡的状态,但是减少了旋转的次数。

 甲鱼的臀部----规定

●每个节点都有颜色,不是红色就是黑色。

●根节点一定是黑色的。

●不能连续出现两个红色的节点。

●每条路径上的黑色节点数量相同。

●每个空节点都是黑色的。

●最多允许一条路径上的节点是另一条路径上节点的二倍。

分析思考

节点默认颜色应该选红色还是黑色,为什么?

答:插入红色可能会出现连续的红色节点,插入黑色会改变当前路径上的黑色节点数。选择默认插入节点为红色的原因是插入后的影响会小一些,当父节点是黑色时不用调整。相反的,黑色节点的插入一定会违反红黑树的规则。

满足上述特性为什么能保证最长路径不会超过最短路径的2倍?

答:这里要关注红黑树的两个特性,红色节点的孩子节点一定是黑色,每条路径的黑色节点树相同。也就说最长路径是红色节点和黑色交替,最短路径是全黑节点。这样一来,最长路径和最短路径间的差距就控制在了规定范围内。

绘图解析+代码实现

节点部分

三叉链结构分别指向左右孩子和父亲节点,数据方面存储键值对,除此之外还需要一个记录节点颜色的变量。

enum Color
{
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;

	pair<K, V> _kv;
	Color _color;

	RBTreeNode(pair<K, V> kv, Color color = RED)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_color(color)
	{

	}
};

插入部分+分步解析

红黑树是二叉搜索树,插入节点的规则和二叉搜索的特点一样:

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

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//插入节点的位置

		cur = new Node(kv);
		cur->_color = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
        //节点插入后,需要检查红黑树的特性有没有被破坏掉
        //......
    }

上述我们谈论过新节点的颜色默认为红色的原因,那么接下来的插入如果新节点插入后其父节点的颜色是黑色,那么直接插入即可。如果其父节点的颜色为红色,那么就出现了两个红色节点出现的情况,此时就需要进行调整:

 

关注:红黑树的处理方法重点关注这样的几个节点:cur(新增节点)、parent(父亲节点)、uncle(叔叔节点)、grandfather(祖父节点)。

当节点的插入违反了红黑树“龟腚”时,对应不同的情况分别处理:

●父亲在祖父的左,叔叔在祖父的右:

情况1:cur为红,parent红色,叔叔节点存在且是红色,祖父是黑色;

 

如上图所示,情况1的处理方法就是将父亲和叔叔的节点变为黑色,祖父的节点变为红色。对于祖父为什么要变红是基于这样的考虑,我们当前的处理很可能只是在子树当中,如果祖父节点保持黑色不变,对于子树这两条路径而言,增加了黑色节点,这样一来就“拆东墙补了西墙”,又违反了另一条规则。如果祖父节点是根节点,改为红色当然是不合理的,我们在最后做下强制处理即可。

注意:这种情况可能是局部的处理,当祖父节点变红且不是根节点时,可能会破坏规则,所以针对情况1需要继续向上查找,也就是将祖父节点看成新增节点,祖父的父亲看做新增节点的父节点,继续向上更新检查树结构。

                if (uncle && uncle->_color == RED)
				{
					//情况1,叔叔节点存在,且是红色的
					parent->_color = uncle->_color = BLACK;
					grandfater->_color = RED;
					
					//更新cur和parent的位置
					cur = grandfater;
					parent = cur->_parent;
				}

情况2:cur为红(插入或者调整后和父亲祖父为一条直线),parent红色,叔叔不存在或者叔叔是黑色,祖父是红色。

当叔叔不存在时:这种情况较好分析,新节点插入后左边高,左边高度超过右边的2倍,进行一次右单旋,祖父变成红色,父亲变为黑色,这颗树调整完毕,不管它是不是子树都不会向上影响。

叔叔存在且为黑色,观察下图,每条路径上的黑色节点数量不相等,所以cur的位置应该是从黑色调整为的红色。

注意:这里不要受图示的影响,感觉uncle路径上的黑节点好像是多一个。调整前后的每条路径数量都是相等的,想象小三角中有着不同的结构就可以了。

叔叔的存在与否只是分析的过程不同,它们的代码处理方式是一样的: 

                    //在一条线上
					if (parent->_left == cur)
					{
						//情况2,叔叔节点不存在或者存在是黑色的
						//右单旋
						RotaR(grandfater);

						parent->_color = BLACK;
						grandfater->_color = RED;
					}

 

情况3:cur为红(插入或者调整后和父亲祖父为线一条折),parent红色,叔叔不存在或者叔叔是黑色,祖父是红色。

uncle不存在:

 uncle存在:

                      
                       if(cur == parent->_left)
                        {
                           //情况3,叔叔节点存在是黑色的,在一条折线上
						   RotaL(parent);
						   RotaR(grandfater);

						   cur->_color = BLACK;
						   grandfater->_color = RED;
                        }
                        

●父亲在祖父的右,叔叔在祖父的左:

处理大思路和上述一致,只是叔叔和父亲交换了位置,不在画图分析,列举处理要点:
情况1:叔叔存在且为红色,叔叔和父亲变黑,祖父变红。向上继续查找。

                if (uncle && uncle->_color == RED)
				{
					//情况1,叔叔存在且是红色
					parent->_color = uncle->_color = BLACK;
					grandfater->_color = RED;

					//继续向上更新
					cur = grandfater;
					parent = cur->_parent;
				}

情况2:叔叔不存在或者存在为黑色,cur的位置和父亲祖父是一条直线,右边高,以祖父为轴点左单旋。祖父变红,父亲变黑。

                   //在右边插入
					if (parent->_right == cur)
					{
						//进行一个左单旋
						RotaL(grandfater);
						//父亲的颜色变成黑色
						parent->_color = BLACK;
						//祖父的的颜色变成红色
						grandfater->_color = RED;
					}

情况3:叔叔不存在或者存在为黑色,cur的位置和父亲祖父是一条折线,先以父亲为轴右单旋,以祖父为轴点左单旋祖父变红,cur变黑。

                    if(cur == parent->_left)//左边插入
					{
						//右单旋
						RotaR(parent);
						//左单旋
						RotaL(grandfater);

						//改变颜色
						grandfater->_color = RED;
						cur->_color = BLACK;

					}

测试部分

因为红黑树是二叉搜索树,在测试时很可能会有隐式的错误,比较难发现,所以需要下面的测试接口对红黑树的特性进行检查,检查是否有连续的红色节点出现,每条路径上的黑色节点是否相等

bool check(Node* root,int num,int BLACKNUM)
	{
		if (root == nullptr)
		{
			if (num != BLACKNUM)
			{
				cout << "某条路径上的黑色节点和其他路径不相等" << endl;
				return false;
			}
			return true;
		}

		if (root->_color == RED && root->_parent->_color == RED)
		{
			cout << "连续出现两个红色节点" << endl;
			return false;
		}
		if (root->_color == BLACK)
		{
			num++;
		}
		return check(root->_left, num, BLACKNUM)
		     &&check(root->_right, num, BLACKNUM);
	}
	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return false;
		}
		//根节点的颜色一定要是黑色
		if (_root->_color != BLACK)
		{
			return false;
		}
		Node* ret = _root;
		int _blacknum = 0;
		while (ret)
		{
			if (ret->_color == BLACK)
			{
				_blacknum++;
			}
			ret = ret->_left;
		}
		return check(_root,0,_blacknum);
	}

整体代码

#include <time.h>
enum Color
{
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;

	pair<K, V> _kv;
	Color _color;

	RBTreeNode(pair<K, V> kv, Color color = RED)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_color(color)
	{

	}
};

template <class K,class V>
class RBTee
{
public:
	typedef RBTreeNode<K,V> Node;
	bool inster(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_color = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//插入节点的位置

		cur = new Node(kv);
		cur->_color = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}


		//插入的节点默认是红色的
		while (parent && parent->_color == RED)
		{
			Node* grandfater = parent->_parent;
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;
				if (uncle && uncle->_color == RED)
				{
					//情况1,叔叔节点存在,且是红色的
					parent->_color = uncle->_color = BLACK;
					grandfater->_color = RED;
					
					//更新cur和parent的位置
					cur = grandfater;
					parent = cur->_parent;
				}
				else 
				{
					//左边新增
					if (parent->_left == cur)
					{
						//情况2,叔叔节点存在是黑色的,在一条线上
						//右单旋
						RotaR(grandfater);

						parent->_color = BLACK;
						grandfater->_color = RED;
					}
					//右边新增
					else
					{
						//情况3,叔叔节点存在是黑色的,在一条折线上
						RotaL(parent);
						RotaR(grandfater);

						cur->_color = BLACK;
						grandfater->_color = RED;
					}
					//树进行了旋转调整,已经平衡,跳出循环
					break;
				}
				
			}
			else //parent在grandfater的右边
			{
				Node* uncle = grandfater->_left;
				if (uncle && uncle->_color == RED)
				{
					//情况1,叔叔存在且是黑色
					parent->_color = uncle->_color = BLACK;
					grandfater->_color = RED;

					//继续向上更新
					cur = grandfater;
					parent = cur->_parent;
				}
				else//uncle的颜色是黑色
				{
					//在右边插入
					if (parent->_right == cur)
					{
						//进行一个左单旋
						RotaL(grandfater);
						//父亲的颜色变成黑色
						parent->_color = BLACK;
						//祖父的的颜色变成红色
						grandfater->_color = RED;
					}
					else//左边插入
					{
						//右单旋
						RotaR(parent);
						//左单旋
						RotaL(grandfater);

						//改变颜色
						grandfater->_color = RED;
						cur->_color = BLACK;

					}
					break;
				}

			}
		}

		_root->_color = BLACK;
		return true;
	}

	void Inorder()
	{
		_Inorder(_root);
	}
	
	bool check(Node* root,int num,int BLACKNUM)
	{
		if (root == nullptr)
		{
			if (num != BLACKNUM)
			{
				cout << "某条路径上的黑色节点和其他路径不相等" << endl;
				return false;
			}
			return true;
		}

		if (root->_color == RED && root->_parent->_color == RED)
		{
			cout << "连续出现两个红色节点" << endl;
			return false;
		}
		if (root->_color == BLACK)
		{
			num++;
		}
		return check(root->_left, num, BLACKNUM)
		     &&check(root->_right, num, BLACKNUM);
	}
	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return false;
		}
		//根节点的颜色一定要是黑色
		if (_root->_color != BLACK)
		{
			return false;
		}
		Node* ret = _root;
		int _blacknum = 0;
		while (ret)
		{
			if (ret->_color == BLACK)
			{
				_blacknum++;
			}
			ret = ret->_left;
		}
		return check(_root,0,_blacknum);
	}
private:
	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return ;
		}

		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.first << endl;
		_Inorder(root->_right);
	}
		void RotaL(Node* pparent)
		{
			Node* subR = pparent->_right;
			Node* subRL = subR->_left;

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

			Node* pNode = pparent->_parent;
			pparent->_parent = subR;
			subR->_left = pparent;

			if (pNode == nullptr)
			{
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				if (pNode->_left == pparent)
				{
					pNode->_left = subR;
				}
				else if (pNode->_right == pparent)
				{
					pNode->_right = subR;
				}
				subR->_parent = pNode;
			}
		}
		void RotaR(Node* pparent)
		{
			Node* subL = pparent->_left;
			Node* subLR = subL->_right;

			pparent->_left = subLR;
			if (subLR != nullptr)
			{
				subLR->_parent = pparent;
			}

			Node* pNode = pparent->_parent;
			subL->_right = pparent;
			pparent->_parent = subL;

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


private:
	Node* _root = nullptr;
};

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

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

相关文章

5.2 对射式红外传感器旋转编码器计次

对射式红外传感器1.1 接线图VCC GND分别接电源的正负极DO数字输出端&#xff0c;随意选择一个GPIO口1.2 硬件原理当挡光片或者编码盘在对射式红外传感器中间经过时&#xff0c;DO就会输出电平变化信号&#xff0c;电平跳变信号触发STM32 PB14号口中断&#xff0c;在中断函数中执…

01: 新手学SpringCloud前需知道的5点

目录 第一点&#xff1a; 什么是微服务架构 第二点&#xff1a;为什么需要学习Spring Cloud 第三点&#xff1a; Spring Cloud 是什么 第四点&#xff1a; SpringCloud的优缺点 1、SpringCloud优点 2、SpringCloud缺点 第五点&#xff1a; SpringCloud由什么组成 1&…

数据结构——链表OJ题目讲解(2)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年3月10日 内容&#xff1a;数据结构链表OJ题目讲解 来源&#xff1a;牛客网和力扣 目录 前言&#xff1a; 刷题&#xff1a; 1.反转链表&#xff1a; 1.改变指向的解法&#xff1a; 2.取头结点插入到新链表&#xff1a; …

参考 | 辨别真假笔记本三星内存条 (ddr4)

参考 | 辨别真假笔记本三星内存条 (ddr4) 文章目录参考 | 辨别真假笔记本三星内存条 (ddr4)1. 三星内存条标签纸上编码的含义2. 三星内存颗粒上编码的含义3. 辨别内容参考1. 三星内存条标签纸上编码的含义 内存条贴张上面有两串值得注意的编码, 其中编码的具体意义参考三星官方…

docker-compress 配置

文章目录docker-compress下载安装常用命令Docker Compose配置常用字段docker compose案例yml 配置指令参考versionbuildcap_add&#xff0c;cap_dropcgroup_parentcommandcontainer_namedepends_ondeploydevicesdnsdns_searchentrypointenv_fileenvironmentexposeextra_hostshe…

【3.10】操作系统进程管理、KMP算法

多线程冲突了怎么办&#xff1f; 由于多线程执行操作共享变量可能会导致竞争状态&#xff0c;因此我们将此段代码称为临界区&#xff08;*critical section*&#xff09;&#xff0c;它是访问共享资源的代码片段&#xff0c;一定不能给多线程同时执行。 我们希望这段代码是互斥…

cadence skill 记录FPM不能保存问题

;FPM skill by Richard L. version0.08 fpmontrealgmail.com;Tree:杂项(Chinese)/简单范例;Desc:范例如何建立一个简单的二极管封装;Vendor:Richard L.;Count:1;CVG64:示意图字段(测试中);Datasheet:pL12.7 ;引脚间距pA7.6 pB3.5 pH3.5 ;长宽高pPad2.0 pHole1.2 ;焊盘直径和孔径…

论文阅读《Block-NeRF: Scalable Large Scene Neural View Synthesis》

论文地址&#xff1a;https://arxiv.org/pdf/2202.05263.pdf 复现源码&#xff1a;https://github.com/dvlab-research/BlockNeRFPytorch 概述 Block-NeRF是一种能够表示大规模环境的神经辐射场&#xff08;Neural Radiance Fields&#xff09;的变体&#xff0c;将 NeRF 扩展到…

渗透测试——信息收集(详细)

信息收集&#xff1a;前言&#xff1a;信息收集是渗透测试除了授权之外的第一步&#xff0c;也是关键的一步&#xff0c;尽量多的收集目标的信息会给后续的渗透事半功倍。收集信息的思路有很多&#xff0c;例如&#xff1a;页面信息收集、域名信息收集、敏感信息收集、子域名收…

Redis学习【12】之Redis 缓存

文章目录前言一 Jedis 简介二 使用 Jedis2.1 测试代码2.2 使用 JedisPool2.3 使用 JedisPooled2.4 连接 Sentinel 高可用集群2.5 连接分布式系统2.6 操作事务三 Spring Boot整合Redis3.1 创建工程3.2 定义 pom 文件3.3 完整代码3.4 总结四 高并发问题4.1 缓存穿透4.2 缓存击穿4…

全方位解读智能中控屏发展趋势!亚马逊Alexa语音+Matter能力成必备

随着智能家居行业逐步从碎片化的智能单品阶段&#xff0c;迈向体验更完整的全屋互联阶段&#xff0c;智能中控屏作为智能家居最佳的入口之一&#xff0c;在年轻人青睐全屋智能装修的风潮下&#xff0c;市场潜力彻底被引爆。 一、为什么是智能中控屏&#xff1f; 在智能音箱增…

诗一样的代码命名规范

有文化&#xff1a;落霞与孤鹜齐飞&#xff0c;秋水共长天一色&#xff1b;没文化&#xff1a;太阳落山的时候&#xff0c;看见一只鸟在水上飞&#xff1b;日常编码中&#xff0c;代码的命名是个大的学问。能快速的看懂开源软件的代码结构和意图&#xff0c;也是一项必备的能力…

Docker入门建议收藏 第二部分

二、Docker 容器技术与虚拟机的区别 Docker 到底是个什么东西呢&#xff1f;我们在理解 Docker 之前&#xff0c;首先得先区分清楚两个概念&#xff0c;容器和虚拟机。 虚拟机 虚拟机&#xff08;Virtual Machine&#xff09;指通过软件模拟的具有完整硬件系统功能的、运行在…

单链表的头插,尾插,头删,尾删等操作

前言顺序表要求是具有连续的物理空间&#xff0c;并且数据的话是在这些空间当中是连续的存储。但这样会带来很多问题&#xff0c;比如说在头部或者说中间插入的话&#xff0c;效率不是很高&#xff1b;并且申请空间可能需要扩容&#xff0c;并且越往后一般来说都是异地扩容&…

优思学院|精益生产中的“单件流”真的能够做到吗?

精益生产中提到的“一个流”&#xff08;One Piece Flow&#xff09;是一种生产方式&#xff0c;它的核心理念是通过合理配置作业场地、人员和设备&#xff0c;使产品从投入到成品产出的整个制造加工过程中始终处于不停滞、不堆积、不超越&#xff0c;按节拍一个一个地流动。 …

Idea+maven+spring-cloud项目搭建系列--11 整合dubbo

前言&#xff1a; 微服务之间通信框架dubbo&#xff0c;使用netty &#xff08;NIO 模型&#xff09;完成RPC 接口调用&#xff1b; 1 dubbo 介绍&#xff1a; Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提…

渲染十万条数据就把你难住了?不存在的!

虚拟列表的使用场景如果我想要在网页中放大量的列表项&#xff0c;纯渲染的话&#xff0c;对于浏览器性能将会是个极大的挑战&#xff0c;会造成滚动卡顿&#xff0c;整体体验非常不好&#xff0c;主要有以下问题&#xff1a;页面等待时间极长&#xff0c;用户体验差CPU计算能力…

pyqt5(二) 标签(QLabel)组件的属性说明及示例

使用语法 widget QLable() widget.function(parameter) widget&#xff1a;实例化QLablefunction&#xff1a;QLable里的函数parameter&#xff1a;函数需要用到的参数 参数说明&#xff1a; 参数说明参数解释 setText() 配置文本内容 setPixmap() 添加图片 setFixedSize(…

蓝桥杯--等差素数列

等差素数列 技巧 这里的等差数列–首项需要枚举列出 公差也需要枚举列出 在公差为1开始&#xff0c;对n-1也进行枚举 //重要代码段 判断一个数是否为素数 int check(int n) { for(int i2;i<n;i){if(n%i0){return 0 } return 1; } }这道题不是很简单 本题为填空题&#xff0…

Webstorm使用、nginx启动、FinalShell使用

文章目录 主题设置FinalShellFinalShell nginx 启动历史命令Nginx页面发布配置Webstorm的一些常用快捷键代码生成字体大小修改Webstorm - gitCode 代码拉取webstorm 汉化webstorm导致CPU占用率高方法一 【忽略node_modules】方法二 【设置 - 代码编辑 - 快速预览文档 - 关闭】主…