C++_红黑树的学习

news2024/11/24 19:48:49

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.coloradmin.cn/o/1660536.html

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

相关文章

06.配置邮件报警

配置邮件报警 我的授权码&#xff1a;HCHNVOAENURLOACG 1.定义发件人 密码是163邮箱的授权码 2.配置收件人 我就配置收件人是qq邮箱了 3.启动动作 验证邮件发送成功

2024年湖北省专升本C语言程序设计大题真题解析

2024年湖北省的专升本考试已于4月30日举行&#xff0c;考试中&#xff0c;出现了许多不同的考试题目&#xff0c;我在网上找到一所高校专升本的大题&#xff08;好像是湖北师范的&#xff0c;后续会有湖北理工的大题真题解析&#xff0c;敬请期待&#xff09;&#xff0c;那么我…

Adobe Photoshop PS 25.6.0 解锁版 (最流行的图像设计软件)

前言 Adobe Photoshop 是一款专业强大的图片处理工具&#xff0c;从照片编辑和合成到数字绘画、动画和图形设计&#xff0c;一流的图像处理和图形设计应用程序是几乎每个创意项目的核心所在。利用 Photoshop 在桌面上的强大功能&#xff0c;您可以在灵感来袭时随时随地进行创作…

通义千问2.5中文能力地表最强

随着人工智能技术的不断进步&#xff0c;智能问答系统已成为人们日常生活中不可或缺的一部分。阿里巴巴集团作为全球领先的科技公司&#xff0c;一直致力于AI领域的研发和创新。最近&#xff0c;阿里巴巴发布了其最新的智能问答系统——通义千问2.5。 通义千问2.5在AI问答领域…

ETL中如何执行Python脚本

Python的解读 Python 是一种高级、通用的编程语言&#xff0c;由荷兰程序员吉多范罗苏姆&#xff08;Guido van Rossum&#xff09;于1990年代初设计并发布。Python的设计哲学强调代码的可读性和简洁性&#xff0c;它的语法清晰且表达力强&#xff0c;使得开发者能够以更少的代…

Python | Leetcode Python题解之第71题简化路径

题目&#xff1a; 题解&#xff1a; class Solution:def simplifyPath(self, path: str) -> str:names path.split("/")stack list()for name in names:if name "..":if stack:stack.pop()elif name and name ! ".":stack.append(name)re…

【鸟叔的Linux私房菜】1-Linux是什么与如何学习

文章目录 Linux是什么Linux的发展Linux的内核版本 Linux的学习学习方法学习重点处理问题 总结 Linux是什么 Linux是一个操作系统&#xff0c;包括内核和系统调用。开源的操作系统。 同一个操作系统无法在不同的硬件上运行&#xff0c;将操作系统修改代码从一个硬件平台迁移到…

react+antd --- 日期选择器,动态生成日期表格表头

先看一下效果---有当前月的日期 技术: 1: react 2:antd-UI库 -- table 3:moment--时间处理库 代码效果: import { Button, DatePicker, Table } from antd; import { useEffect, useState } from react; import moment from moment;function Club() {const [selecte…

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

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

怎样用Python语言实现远程控制两路开关

怎样用Python语言实现远程控制两路开关呢&#xff1f; 本文描述了使用Python语言调用HTTP接口&#xff0c;实现控制两路开关&#xff0c;两路开关可控制两路照明、排风扇等电器。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能…

并行执行的4种类别——《OceanBase 并行执行》系列 4

OceanBase 支持多种类型语句的并行执行。在本篇博客中&#xff0c;我们将根据并行执行的不同类别&#xff0c;分别详细阐述&#xff1a;并行查询、并行数据操作语言&#xff08;DML&#xff09;、并行数据定义语言&#xff08;DDL&#xff09;以及并行 LOAD DATA 。 《并行执行…

新能源 锂电池行业创业的财富方案,锂电池回收实战攻略课(36节课)

实战攻略 12年锂电池回收行业经验与坑全收录 课程内容&#xff1a; 001-课程介绍.mp4 002-锂电池的全种类认识.mp4 003-废品锂电池到级片粉末价值估算,mp4 004-锂电池的生产应用回收,mp4 005-梯次回收到粉未提纯全流程,mp4 006-锂电池行业术语,mp4 007-回收所需必备工具…

【二分查找 滑动窗口】100257找出唯一性数组的中位数

本文涉及知识点 二分查找算法合集 C算法&#xff1a;滑动窗口总结 LeetCode 100257找出唯一性数组的中位数 给你一个整数数组 nums 。数组 nums 的 唯一性数组 是一个按元素从小到大排序的数组&#xff0c;包含了 nums 的所有非空子数组中不同元素的个数。 换句话说&#xf…

智能创作时代:AI 如何重塑内容生成游戏规则

文章目录 前言一&#xff1a;自动化内容生成文章生成视频制作音频创作 二&#xff1a;内容分发与推广智能推荐系统社交媒体优化 三&#xff1a;内容分析与优化数据分析用户反馈质量控制 结语 前言 在数字化时代的浪潮中&#xff0c;内容生产与消费已成为信息传播的核心。随着人…

高效视频剪辑:批量剪辑添加srt字幕,快速制作专业视频

在视频制作过程中&#xff0c;字幕扮演着至关重要的角色&#xff0c;它们不仅能增强观众对视频内容的理解&#xff0c;还能提高视频的观感体验。然而&#xff0c;手动为每一个视频添加字幕是一项既耗时又繁琐的任务。现在有了云炫AI智剪和技巧&#xff0c;我们可以轻松地实现批…

2024年4月17日在《自然能源》上发表:恒久飞行已实现

​奥地利研究团队所打造的轻质准二维钙钛矿太阳能电池&#xff0c;每克竟能输出高达44瓦的惊人功率&#xff0c;这项革命性的成就堪称科技领域的璀璨明星。来自林茨约翰开普勒大学和林茨有机太阳能电池研究所的研究人员携手共创&#xff0c;将此超轻且功率强大的电池带入现实。…

Window如何运行sh文件以及wget指令

Git下载 官网链接如下&#xff1a;https://gitforwindows.org/ 安装就保持一路无脑安装就行&#xff0c;不需要改变安装过程中的任何一个选项。 配置Git 切刀桌面&#xff0c;随便右击屏幕空白处&#xff0c;点open Git Bash here 把这行复制过去&#xff0c;回车&#xff1…

OFDM802.11a的FPGA实现(十一)IFFT(含verilog和matlab代码)

原文链接&#xff08;相关文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA实现 目录 1.前言2.时序参数3.IFFT4.Matlab仿真5.ModelSim仿真6.结果对比验证7.verilog代码 1.前言 在前面的博客当中&#xff0c;已经实现了星座图的映射和导频插入&#xff0c;得到了在频…

【Linux-I.MX6ULL裸机学习】中断向量表

代码来自于正点原子阿尔法Linux开发板光盘 比如在中断向量表中规定了&#xff1a;在某个地址0x80000A对应着某个中断服务函数&#xff0c;那么在产生这个中断时&#xff0c;就会从0x80000A这个地址去读取中断服务函数&#xff0c;并执行。 如果想改变这个地址&#xff0c;也就是…

国债期货怎么买?十年国债交易手册

国债&#xff0c;简单来说&#xff0c;就是国家为了筹集资金而向大众借钱的一种方式。国家通过发行债券&#xff0c;向投资者承诺在约定的时间里支付利息&#xff0c;并在到期时归还本金。因为是国家发行的&#xff0c;所以国债的信用等级非常高&#xff0c;通常被认为是非常安…