C++_红黑树的学习

1. 红黑树的概念

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

2. 红黑树的性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
5. 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )

3. 红黑树节点的定义

新增结点给红色,因为给黑色会破坏各个路径黑色结点数量相同的条件

// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
	RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _data(data), _color(color)
	{}
	RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子
	RBTreeNode<ValueType>* _pRight;  // 节点的右孩子
	RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给
	出该字段)
	ValueType _data;            // 节点的值域
	Color _color;               // 节点的颜色
};

4. 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
        1. 按照二叉搜索的树规则插入新节点
        2. 检测新节点插入后,红黑树的性质是否造到破坏并更新
因为 新节点的默认颜色是红色 ,因此:如果 其双亲节点的颜色是黑色,没有违反红黑树任何 性质 ,则不需要调整;但 当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:
约定 :cur 为当前节点, p 为父节点, g 为祖父节点, u 为叔叔节点

4.1 cur为红,p为红,g为黑,u存在且为红

这个时候,p与g的左右关系没有影响,只有g是否为根有影响:
如果g是根结点,调整完后,需要将g更改为黑色
如果g是子树,g就一定有双亲,且如果g的双亲如果是红色,则需要继续向上调整

解决方式:将 p,u 改为黑, g 改为红,然后把 g 当成 cur ,继续向上调整

4.2 cur为红,p为红,g为黑,u不存在/u存在且为黑

这个时候,p与g的左右关系有影响:

这时有两种情况:
1. u 结点不存在,则cur 一定是新增结点,因为如果cur不是新增结点,则cur和p一定有一个结点的颜色是黑色,不然就破坏了各路径黑色结点数量相等的条件,但是这与更新条件相违背:只有c和p都是红色才进行更新

2.u 结点存在且为黑,则cur原来一定是黑色的,只是因为cur在的子树在先前已经更新完了,cur颜色由黑色改成红色

解决方式:p为g的左孩子,curp的左孩子,则进行右单旋转;
                  p为g的左孩子,curp的右孩子,则进行左右双旋转;相反,
                  p为g的右孩子,curp的右孩子,则进行左单旋转;
                  p为g的右孩子,cur为p的左孩子,则进行右左双旋转;
                  pg变色--p变黑,g变红
	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 (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和parent
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;


		//当parent存在且为红色时,需要更新颜色
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//uncle是grandfather的右孩子
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//uncle存在且为红色
				if (uncle && uncle->_col == RED)
				{
					//颜色更新
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					//继续向上遍历
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//uncle不存在,或者是uncle存在但为黑色
					if (cur == parent->_left)
					{
						//       g
						//    p    u
						// c
						//需要进行右单旋,更改颜色:parent->黑色,
                        //grandfather->红色
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g             c
						//    p     u  ->   p     g
						//      c                    u
						//需要进行左右双旋,更改颜色:cur->黑色,
                        //grandfather->红色
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//旋转后重新平衡,直接退出
				}
			}
			//uncle是grandfather的左孩子
			else
			{
				Node* uncle = grandfather->_left;
				//uncle存在且为红色
				if (uncle && uncle->_col == RED)
				{
					//颜色更新
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					//继续向上遍历
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//uncle不存在,或者是uncle存在但为黑色
					if (cur == parent->_right)
					{
						//       g
						//    u    p
						//           c
						//需要进行左单旋,更改颜色:parent->黑色,
                        //grandfather->红色
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g             c
						//    u     p  ->   g     p
						//        c       u          
						//需要进行右左双旋,更改颜色:cur->黑色,
                        //grandfather->红色
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//旋转后重新平衡,直接退出
				}
			}
		}
		//对根结点统一更改颜色为黑色
		_root->_col = BLACK;
		return true;
	}

	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;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}


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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;

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

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

5. 红黑树的验证

红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树 ( 中序遍历是否为有序序列 )
2. 检测其是否满足红黑树的性质
	bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		//在一条路径走完后再判定黑色结点的数量是否有异常,有就报错
		//什么时候走完? 当前结点走到空结点就走完一条路径,这里用前序遍历
		if (cur == nullptr)
		{
			if (blackNum != refBlackNum)
			{
				cout << "黑色结点数量异常,错误!" << endl;
				cout << blackNum << endl;
				return false;
			}
			//cout << blackNum << endl;
			return true;
		}

		//有连续的红色结点就报错:找到一个红结点再看它的parent是不是红结点
		if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED)
		{
			cout << "有连续的红结点,错误!" << endl;
			return false;
		}

		//遇到黑色结点,blackNum++
		if (cur->_col == BLACK)
			blackNum++;

		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

	bool IsBalance()
	{
		//空结点也是红黑树
		if (_root == nullptr)
			return true;

		//根存在但是根的颜色是红就报错
		if (_root && _root->_col == RED)
		{
			cout << "根是红色,错误!" << endl;
			return false;
		}

		//先遍历最左路径,得到黑色结点的数量
		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				refBlackNum++;
			cur = cur->_left;
		}

		return Check(_root, 0, refBlackNum);
	}

6. 红黑树的模拟实现

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

enum Colour
{
	RED,
	BLACK
};

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

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)//如果是根,则为黑色,新增结点默认是红色
	{}
};


template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
        ......
	}


	void RotateL(Node* parent)
	{
        ......
	}


	void RotateR(Node* parent)
	{
        ......
	}

	bool Check(Node* cur, int blackNum, int refBlackNum)
	{
        ......
	}


	bool IsBalance()
	{
        ......
	}


	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return NULL;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	Node* _root = nullptr;
};

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

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

相关文章

【活动】如何通过AI技术提升内容生产的效率与质量

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 如何通过AI技术提升内容生产的效率与质量引言一、自然语言处理&#xff08;NLP&…

JAVA排序相关习题7

1.插入排序 1.1 基本思想 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 。 /*** 时间复杂度&…

自然资源-地质勘查工作的流程梳理

自然资源-地质勘查工作的流程梳理 地质勘查从广义上可理解为地质工作&#xff0c;地质队员就好像是国家宝藏的“寻宝人”&#xff0c;通过地质勘查&#xff0c;为国家找矿&#xff0c;以保障国家能源资源安全和服务国计民生&#xff0c;发挥着地质工作在国民经济建设中的基础性…

跟TED演讲学英文:Teachers need real feedback by Bill Gates

Teachers need real feedback Link: https://www.ted.com/talks/bill_gates_teachers_need_real_feedback Speaker: Bill Gates Date: May 2013 文章目录 Teachers need real feedbackIntroductionVocabularyTranscriptSummary后记 Introduction Until recently, many teach…

电子版图书制作,一键转换可仿真翻页的画册

在数字化浪潮的冲击下&#xff0c;传统纸质图书逐渐被电子版图书取而代之。电子版图书以其便携、环保、更新快速等特点&#xff0c;吸引了越来越多的读者。制作一款既具备电子图书的便捷性&#xff0c;又能仿真翻页的画册&#xff0c;成为当下图书出版行业的新趋势 1.要制作电子…

企业数据保护,从严防内部信息泄露开始

在当今的数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;随之而来的是数据安全威胁&#xff0c;尤其是内部信息泄露&#xff0c;这不仅会导致企业面临巨大的经济损失&#xff0c;还可能损害企业的品牌形象和客户信任。因此&#xff0c;从严防内部信息…

56 关于 linux 的 oom killer 机制

前言 这里主要讲的是 linux 的 oom killer 机制 在系统可用内存较少的情况下&#xff0c;内核为保证系统还能够继续运行下去&#xff0c;会选择杀掉一些进程释放掉一些内存。 通常oom_killer的触发流程是&#xff1a;进程A想要分配物理内存&#xff08;通常是读写内存&#…

新能源汽车中HEV与PHEV分别代表什么车型,它们与传统燃油车都有什么区别?

前言 新能源汽车正逐渐成为全球汽车工业的主流方向&#xff0c;而HEV&#xff08;Hybrid Electric Vehicle&#xff09;和PHEV&#xff08;Plug-in Hybrid Electric Vehicle&#xff09;这两种混合动力车型在这一转型过程中扮演着重要角色。下面我们详细探讨HEV与PHEV的定义&a…

基于FPGA的视频矩阵 视频拼接 无缝切换解决方案

视频矩阵 视频矩阵 视频拼接 无缝切换 1. 最大支持144路HDMI视频输入&#xff0c;最大支持144路路HDMI输出&#xff0c;完全交叉切换。 2. 与包括1080p/60的所有HDTV分辨率和高达1920*1200的PC的分辨率兼容&#xff1b; 3. 支持HDMI 1.3a、HDCP 1.3、HDCP 1.4、以及DVI 1.0协…

如何使用visual vm和jstat进行远程监控

如何使用visual vm和jstat进行监控 安装visual vm 好像从jdk某个版本开始&#xff0c;jdk的bin目录下就不自带jvisualvm了&#xff0c;需要从官网下载一个visual vm。 打开visual vm Local是你本地的&#xff0c;无需多言。 先准备下必备的插件 如何通过visual vm观测远程…

Prometheus监控Kubernetes Pod状态

本文将介绍如何配置Prometheus的告警规则&#xff0c;实现对于Kubernetes Pod状态的监控。 1.Pod的状态类型 在Prometheus 监控Kubernetes Pod 状态时&#xff0c;通常可以观察到以下几种状态情况&#xff1a; 1. Running&#xff08;运行中&#xff09; Pod 处于运行状态意…

Spring Framework-IoC详解

IoC的概念和作用 在介绍Ioc之前&#xff0c;我们首先先了解一下以下内容 什么是程序的耦合 耦合性(Coupling)&#xff0c;也叫耦合度&#xff0c;是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度…

Java毕业设计 基于SpringBoot vue新能源充电系统

Java毕业设计 基于SpringBoot vue新能源充电系统 SpringBoot 新能源充电系统 功能介绍 首页 图片轮播 充电桩 充电桩类型 充电桩详情 充电桩预约 新能源公告 公告详情 登录注册 个人中心 余额充值 修改密码 充电桩报修 充电桩预约订单 客服 后台管理 登录 个人中心 修改密码…

【Linux】模拟实现bash(简易版)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

redis深入理解之数据存储

1、redis为什么快 1&#xff09;Redis是单线程执行&#xff0c;在执行时顺序执行 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线…

权力集中,效率提升,中心化模式的优势与挑战

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;权力集中…

Microsoft Project使用简明教程

一.认识Microsoft Project Microsoft Project 是微软公司开发的项目管理软件&#xff0c;用于规划、协调和跟踪项目的进度、资源和预算&#xff0c;如下图所示&#xff0c;左边是任务的显示&#xff0c;右边是一个日程的显示图&#xff0c;最上方的长方形处在我们项目设定日程…

【oracle数据库安装篇三】Linux6.8单机环境oracle11g容灾ADG搭建

说明 DataGuard 是在主节点与备用节点间通过日志同步来保证数据的同步&#xff0c;可以实现数据库快速切换与灾难性恢复。用户能够在对主数据库影响很小的情况下&#xff0c;实现主备数据库的同步。 关联文章 【oracle数据库安装篇一】Linux5.6基于LVM安装oracle11gR2单机 【…

Pandas数据取值与选择

文章目录 第1关&#xff1a;Series数据选择第2关&#xff1a;DataFrame数据选择方法 第1关&#xff1a;Series数据选择 编程要求 本关的编程任务是补全右侧上部代码编辑区内的相应代码&#xff0c;要求实现如下功能&#xff1a; 添加一行数据&#xff0c;时间戳2019-01-29值为…

vue开发网站—①调用$notify弹窗、②$notify弹窗层级问题、③js判断两个数组是否相同等。

一、vue中如何使用vant的 $notify&#xff08;展示通知&#xff09; 在Vue中使用Vant组件库的$notify方法来展示通知&#xff0c;首先确保正确安装了Vant并在项目中引入了Notify组件。 1.安装vant npm install vant --save# 或者使用yarn yarn add vant2.引入&#xff1a;在ma…
最新文章