红黑树:变色旋转规则化抽象逻辑分析

news2024/11/26 14:44:25

在这里插入图片描述
在这里插入图片描述

文章目录

  • 一.红黑树的定义
    • 红黑树平衡性论证
  • 二.红黑树的节点插入
    • 插入新节点后最小违规子结构(抽象分析)
      • 最小违规子结构一号的规则化算法分析
      • 最小违规子结构二号的规则化算法分析
  • 三.红黑树类代码托管
  • 四.红黑树与AVL树的对比

在这里插入图片描述

旷世奇才发明的数据结构

一.红黑树的定义

  • 红黑树的节点定义:
enum Colour
{
	RED,
	BLACK,
};

template<class KEY,class VALUE>
struct RBTreeNode
{
	RBTreeNode<KEY,VALUE>*  _left;
	RBTreeNode<KEY, VALUE>* _right;
	RBTreeNode<KEY, VALUE>* _parent;
	//KEY_VALUE键值对
	pair<KEY, VALUE> _Key_Value;

	//节点的颜色
	Colour _color;


	//默认构造红色节点
	RBTreeNode(const pair<KEY, VALUE>& KEY_VALUE)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _Key_Value(KEY_VALUE)
		, _color(RED)
	{}
};
  • 红黑树(或者其子结构)中的连通路径:红黑树(或者其子结构)中的连通路径指的是红黑树的根空节点的路径,比如:在这里插入图片描述

  • 红黑树数据结构定义:红黑树满足二叉搜索树的所有性质

    • (1).红黑树的所有节点被标记为红色或黑色
    • (2).整颗红黑树的根节点必须为黑色
    • (3).红黑树中的红色节点的左右孩子不能是红色节点(即在连通路径上红色节点不能连续出现)
    • (4).红黑树的所有连通路径上黑色节点数量必须相同
  • 红黑树的路径特征值:

    • 红黑树的规则(4)保证了红黑树及其任何一个子结构都分别有一个唯一的路径特征值,该特征值记录了红黑树(或其子结构)的连通路径上黑色节点的数量,规则(3)和规则(4)是红黑树平衡性的保证

红黑树平衡性论证

  • 平衡性结论1:根据红黑树的定义,设整颗红黑树最长连通路径上节点总个数为X,整颗红黑树最短连通路径上节点总个数为Y,则有 X ≤ 2 Y X\leq2Y X2Y

    • 论证结论1:设红黑树的路径特征值为Z,在最长连通路径上只能间隔地在Z个黑色节点间插入红色节点,因此最长连通路径的路径长度最大为2*Z最短连通路径上可以不存在红色节点,因此最短连通路径的路径长度最小为Z;从而结论1得证: X ≤ 2 Z ≤ 2 Y X\leq2Z\leq2Y X2Z2Y在这里插入图片描述
  • 平衡性结论2:根据平衡性结论1,当一颗红黑树的节点个数为N时,红黑树的高度H满足: H ≤ 2 l o g 2 N H\leq 2log_2N H2log2N

    • 可以在满树的基础上改变节点分布证明平衡性结论2
  • 综上,红黑树具有较好的平衡性,可以实现数据的高速检索.

  • 相比于AVL树,红黑树要更抽象一些,它是根据自身的定义间接地控制树的高度从而保证平衡性

二.红黑树的节点插入

  • 空树只有一个黑色节点的树都满足红黑树的性质.在这里插入图片描述

  • 第一步先根据二叉搜索树的节点插入方式在红黑树的叶子节点位置插入新节点,比如:在这里插入图片描述

  • 红黑树的插入操作只插入红色节点(插入黑色节点会导致路径特征值改变,不方便数据结构的调整)

	bool insert(const Pair& KEYVALUE)
	{
		if (_root == nullptr)
		{
			_root = new Node(KEYVALUE);
			_root->_color = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;

		//查找插入的位置
		while (cur != nullptr)
		{
			if (cur->_Key_Value.first < KEYVALUE.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_Key_Value.first > KEYVALUE.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//KEY值重复,无法插入
				return false;
			}
		}

		//找到空位置执行插入
		cur = new Node(KEYVALUE);
		cur->_color = RED;
		if (parent->_Key_Value.first < KEYVALUE.first)
		{
			//向右孩子插入
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
	}

插入新节点后最小违规子结构(抽象分析)

  • 要想设计数据结构的调整算法,就必须先抽象出要调整的结构对象
  • 调整算法中会用到旋转算法,旋转平衡化参见: 旋转平衡化
    在这里插入图片描述
    在这里插入图片描述
  • 新插入节点cur前驱parent为红色时,红黑树局部违规,由于红黑树的根一定是黑色的,因此新节点cur的前驱parent一定不是整棵树的根,于是parent一定存在前驱grandparent,grandparent的另一个孩子我们称为uncle(uncle与parent同层,变色操作要求保持同层节点同一性,因此我们需要关注uncle).
  • 前述的分析中,我们一共在违规结构中具象化出了四个节点(其中uncle存在与否及其颜色我们是不确定的),将与这四个节点相连其他子结构抽象化,就可以得到下面四种最小违规子结构.
    • 最小违规子结构一号: 在这里插入图片描述
    • 最小违规子结构二号:
      在这里插入图片描述
    • 最小违规子结构三号:
      在这里插入图片描述
    • 最小违规子结构四号:
      在这里插入图片描述
  • 最小违规子结构一号和二号分别与三号和四号是对称的,因此:
    一号与三号的处理方式是相同的(只是旋转方向不同)
    二号和四号的处理方式是相同的(只是旋转方向不同)
  • 最小违规子结构的处理要保证如下几点要求:
    • 1.维持二叉搜索树的性质
    • 2.最小违规子结构中各个连通路径(从根到空节点)上的黑色节点数量不发生改变
    • 3.消除节点之间的颜色冲突

最小违规子结构一号的规则化算法分析

  • 根据uncle的情况的不同可以将最小违规子结构进一步分为三种子情形进行处理:

    • uncle节点存在且为红色
    • uncle节点存在且为黑色或uncle为空节点
  • 调整算法整体图解:在这里插入图片描述

  • 分块展示:在这里插入图片描述

    • uncle节点存在且为红色:变色规则化在这里插入图片描述

    • uncle节点存在且为黑色或uncle为空节点:单旋+变色规则化在这里插入图片描述

最小违规子结构二号的规则化算法分析

  • 根据uncle的情况的不同可以将最小违规子结构进一步分为三种子情形进行处理:

    • uncle节点存在且为红色
    • uncle节点存在且为黑色或uncle为空节点
  • 调整算法整体图解:在这里插入图片描述

  • 分块展示:在这里插入图片描述

    • uncle节点存在且为红色:变色规则化在这里插入图片描述

    • uncle节点存在且为黑色或uncle为空节点:双旋+变色规则化在这里插入图片描述

  • 最小违规子结构三号和四号处理方式分别与一号和二号相同(只是旋转方向不同)

三.红黑树类代码托管

enum Colour
{
	RED,
	BLACK,
};

template<class KEY,class VALUE>
struct RBTreeNode
{
	RBTreeNode<KEY,VALUE>*  _left;
	RBTreeNode<KEY, VALUE>* _right;
	RBTreeNode<KEY, VALUE>* _parent;
	//KEY_VALUE键值对
	pair<KEY, VALUE> _Key_Value;

	//节点的颜色
	Colour _color;


	//默认构造红色节点
	RBTreeNode(const pair<KEY, VALUE>& KEY_VALUE)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _Key_Value(KEY_VALUE)
		, _color(RED)
	{}
};


template<class KEY, class VALUE>
class RBTree
{
	typedef RBTreeNode<KEY, VALUE> Node;
	typedef pair<KEY, VALUE> Pair;
public:
	RBTree<KEY, VALUE>()
	{
		_root = nullptr;
	}
	
	//红黑树插入
	bool insert(const Pair& KEYVALUE)
	{
		if (_root == nullptr)
		{
			_root = new Node(KEYVALUE);
			_root->_color = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;

		//查找插入的位置
		while (cur != nullptr)
		{
			if (cur->_Key_Value.first < KEYVALUE.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_Key_Value.first > KEYVALUE.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//KEY值重复,无法插入
				return false;
			}
		}

		//找到空位置执行插入
		cur = new Node(KEYVALUE);
		cur->_color = RED;
		if (parent->_Key_Value.first < KEYVALUE.first)
		{
			//向右孩子插入
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		
		//如果cur的前驱parent是红色节点则我们需要对违规子结构进行迭代调整
		while (parent && parent->_color == RED)
		{
			Node * grandparent = parent->_parent;
			//区分最小违规子结构一二和三四号
			if (grandparent->_right == parent)
			{
				Node* uncle = grandparent->_left;
				//如果uncle为红色则按照情形(1)变色处理
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = BLACK;
					parent->_color = BLACK;
					grandparent->_color = RED;
					//变色后将grandparent作为新的cur向上层迭代调整(最小违规子结构的逻辑形式不变)
					cur = grandparent;
					parent = cur->_parent;
				}
				//情形(2)-->先区分最小违规子结构一号和二号
				else if (parent->_right == cur)
				{
					//一号最小违规子结构-->左单旋+变色
					_RotationSL(grandparent);
					grandparent->_color = RED;
					parent->_color = BLACK;
					//子结构的根变为黑色,无需再向上层继续迭代调整
					break;
				}
				else
				{
					//二号最小违规子结构-->右左双旋+变色
					_RotationSR(parent);
					_RotationSL(grandparent);
					cur->_color = BLACK;
					grandparent->_color = RED;
					//子结构的根变为黑色,无需再向上层继续迭代调整
					break;
				}
			}
			else
			{
				Node* uncle = grandparent->_right;
				//如果uncle为红色则按照情形(1)变色处理
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = BLACK;
					parent->_color = BLACK;
					grandparent->_color = RED;
					//变色后将grandparent作为新的cur向上层迭代调整(最小违规子结构的逻辑形式不变)
					cur = grandparent;
					parent = cur->_parent;
				}
				//情形(2)-->先区分最小违规子结构三号和四号
				else if (parent->_left == cur)
				{
					//三号最小违规子结构-->右单旋+变色
					_RotationSR(grandparent);
					grandparent->_color = RED;
					parent->_color = BLACK;
					//子结构的根变为黑色,无需再向上层继续迭代调整
					break;
				}
				else
				{
					//四号最小违规子结构-->左右双旋+变色
					_RotationSL(parent);
					_RotationSR(grandparent);
					cur->_color = BLACK;
					grandparent->_color = RED;
					//子结构的根变为黑色,无需再向上层继续迭代调整
					break;
				}
			}
		}
		//根据红黑树性质将根节点调整为黑色
		_root->_color = BLACK;
		return true;
	}


	//中序遍历
	void Inorder()
	{
		_Inorder(_root);
		std::cout << std :: endl;
	}

	void _CheckStruct()
	{
		//求路径特征值
		int Benchmark = 0;
		Node* tem = _root;
		while (tem)
		{
			if (tem->_color == BLACK)
			{
				Benchmark++;
			}
			tem = tem->_left;
		}

		if (_CheckStruct(_root, 0, Benchmark))
		{
			std::cout << "StructureLegal" << std::endl;
		}
	}

private:
	//左单旋成员接口
	void _RotationSL(Node* parent)
	{
		//整个子结构的祖先(可能为空,即parent就是整棵树的树根)
		Node* Graparent = parent->_parent;

		Node* parentR = parent->_right;
		//parentR的左子树(可能为空)
		Node* parentRLSon = parentR->_left;

		//左单旋
		parent->_right = parentRLSon;
		//注意parentLSon可能为空
		if (parentRLSon)
		{
			parentRLSon->_parent = parent;
		}

		parentR->_left = parent;
		parent->_parent = parentR;

		//注意GGraparent可能为空
		if (Graparent)
		{
			if (Graparent->_left == parent)
			{
				Graparent->_left = parentR;
			}
			else
			{
				Graparent->_right = parentR;
			}
		}
		else
		{
			_root = parentR;
		}
		parentR->_parent = Graparent;
	}

	//右单旋成员接口
	void _RotationSR(Node* parent)
	{
		Node* Graparent = parent->_parent;
		Node* parentL = parent->_left;
		Node* parentLRSon = parentL->_right;

		//右单旋
		parent->_left = parentLRSon;
		if (parentLRSon)
		{
			parentLRSon->_parent = parent;
		}

		parentL->_right = parent;
		parent->_parent = parentL;

		if (Graparent)
		{
			if (Graparent->_left == parent)
			{
				Graparent->_left = parentL;
			}
			else
			{
				Graparent->_right = parentL;
			}
		}
		else
		{
			_root = parentL;
		}
		parentL->_parent = Graparent;
	}

	//中序遍历搜索树
	void _Inorder(Node * root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		std::cout << root->_Key_Value.first << ' ';
		_Inorder(root->_right);
	}

	//检查红黑树结构的合法性
	bool _CheckStruct(Node* root,int BlackNum,const int& benchmark)
	{
		if (root == nullptr)
		{
			//检查各路径的黑色节点数量是否相同
			if (BlackNum == benchmark)
			{
				std::cout << "BlackNum:" << BlackNum << std::endl;
				return true;
			}
			else
			{
				return false;
			}
		}
		else if (root->_color == BLACK)
		{
			BlackNum++;
		}
		else if (root->_color == RED)
		{
			//检查红色节点在某条路径上是否连续出现
			if (root->_parent && root->_parent->_color == RED)
			{
				return false;
			}
		}
		return _CheckStruct(root->_left, BlackNum, benchmark) && _CheckStruct(root->_right, BlackNum, benchmark);
	}
private:
	Node* _root;
};

四.红黑树与AVL树的对比

  • AVL树是严格平衡的搜索二叉树,由于AVL树的平衡性要求很高,因此面对大量的数据时,建树和删树的过程中很有可能会引发大量的旋转操作,从而导致性能下降,而红黑树是一种相对平衡的搜索二叉树,面对大量的数据时,建树和删树的过程中引发的旋转操作次数相对较少,在性能实测中,红黑树效率更胜一筹,从而在各种语言的库以及Linux内核中被频繁使用.
    在这里插入图片描述

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

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

相关文章

【C语言初阶(10)】函数练习题

文章目录 1. 判断素数2. 判断闰年3. 函数实现二分查找4. 记录函数调用次数 1. 判断素数 题目内容 写一个函数可以判断一个数是不是素数。 素数 素数也叫做质数&#xff0c;一个只能被 1 和它本身整除的数字称之为素数。 例如&#xff1a;7 这个数字只能被 1 和 它本身&#x…

赛效:怎么将PPT转为PDF

1&#xff1a;在电脑网页上打开云组件&#xff0c;点击“PPT转换”菜单里的“PPT转PDF”。 2&#xff1a;点击“选择文件”可以将本地PPT文件添加上去。 3&#xff1a;文件添加成功后&#xff0c;点击下方的“开始转换”按钮。 4&#xff1a;文件转换成功后&#xff0c;在预览页…

vue动态修改浏览器标题和logo

问题描述 需要将一个系统&#xff0c;更改一下标题、logo&#xff0c;然后部署成另一个系统&#xff0c;由于不想单独拉出一套代码&#xff08;单独拉出来后维护成本增加&#xff09;&#xff0c;所以想要动态改变系统标题和图标 解决方案 将项目制造一个入口可以修改项目的…

20230703 -- scRNAseq from gastric cancer

文章标题&#xff1a;《Single-cell atlas of lineage states, tumor microenvironment and subtypespecific expression programs in gastric cancer》 DOI: 10.1158/2159-8290.CD-21-0683 数据集组织形式快照&#xff1a; step1 利用Seurat包整合数据 #! conda env R4libra…

Hombrew中AdoptOpenJDK已废弃更换Eclipse Temurin安装最新版JDK,并实现不同JDK版本之间切换

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Python基础 —— 运算符

每天提升一 。。。。。。 〇、概述 Python 中有很多运算符&#xff0c;大体分为 算数运算符、赋值远算符、比较运算符、逻辑运算符。通过这些运算符能够更好地完成一些列的数据运算。 一、算数运算符 1. 算数运算符 算数运算符 主要用于 数值类型&#xff08;整型、浮点型…

TCP三次和四次握手:

内容来自思学堂&#xff1a; TCP三次握手&#xff1a;确保双方都在线上 TCP四次握手&#xff1a;处理客户端要断开连接的需求

JavaWeb学习路线(11)—— Maven延伸

一、分模块设计 &#xff08;一&#xff09;概念&#xff1a; 将项目按功能拆分出若干个子模块。 &#xff08;二&#xff09;作用&#xff1a; 方便项目管理维护、扩展&#xff0c;也方便模块间相互调用&#xff0c;资源共享。 &#xff08;三&#xff09;具体实现 1、抽取…

深度学习项目实战二: LetNet5网络结构搭建

深度学习项目实战二: LetNet5网络结构搭建 文章目录 深度学习项目实战二: LetNet5网络结构搭建@[TOC](文章目录)一、卷积基本运算公式二、LetNet5网络1. 网络结构![在这里插入图片描述](https://img-blog.csdnimg.cn/0008fe6e5886414eac09eed49556ad99.png)2. 导入相关包3. 代码…

Apikit 自学日记:流程用例

添加普通用例 进入自动化测试用例管理页面&#xff0c;点击 添加用例 按钮&#xff0c;在弹窗中输入用例名称等信息&#xff0c;然后点击确定即可。 发起用例测试 创建好测试用例之后&#xff0c;点击 执行测试 按钮即可运行测试&#xff0c;系统会自动按顺序执行测试流程里的…

汽车下半年行情启动?概念全线爆发

2023年上半年&#xff0c;车市整体的基调是打价格战和加速“内卷”&#xff0c;在资本市场&#xff0c;汽车产业链相关概念股的表现整体也是不温不火。然而&#xff0c;下半年刚开始&#xff0c;汽车产业链股集体高调反弹。 7月4日&#xff0c;A股市场涨幅居前的概念板块几乎全…

MapstructPlus的快速集成

https://www.mapstruct.plus/https://www.mapstruct.plus/ # 博主技术栈如下 springboot:2.4.5 lombok:1.8.20 mapstruct-plus:1.3.4 knife4j:4.0.0目录 一、添加依赖&#xff08;谨防依赖冲突&#xff09; 二、如果依赖下不下来&#xff0c;要在maven的setting文件中加入腾讯…

NSS [SWPUCTF 2021 新生赛]no_wakeup

NSS [SWPUCTF 2021 新生赛]no_wakeup 先看题目&#xff0c;反序列化&#xff0c;绕过weakup。 exp&#xff1a; <?php class HaHaHa{public $admin;public $passwd;public function __construct(){$this->admin "admin";$this->passwd "wllm";…

【Python爬虫开发实战②】使用urllib以及jsonpath爬取即将上映电影信息

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;python网络爬虫从基础到实战 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a; ⭐️首先&#xff0c;我们前面讲了多篇基础内容&…

【JUC并发编程】集合类安全问题

一、并发下&#xff0c;ArrayList类是不安全的 代码演示package CollectionSafe;import java.util.ArrayList; import java.util.List; import java.util.UUID;/*** author swaggyhang* create 2023-07-02 17:26*/ public class Test01 {public static void main(String[] arg…

ubuntu下 C/C++程序读取设置环境变量

设置环境变量很简单比如&#xff1a; export QMCY_LOCAL_PORT8888 追加的话 export QMCY_LOCAL_PORT$QMCY_LOCAL_PORT:8000 可以通过echo回显 读取的话 main函数多加一个env参数 一个字符串数组 然后遍历这个数组 即可 使用的时候 如下&#xff1a; bool QMCY_APP::Init(s…

【若依框架学习】day1-启动项目

若依开源框架&#xff0c;前后端分离项目&#xff0c;地址&#xff1a;http://doc.ruoyi.vip/ruoyi-vue/ 先配置环境 JDK1.8&#xff0c; MySQL5.7 &#xff0c;Maven3.6&#xff0c;redis、nginx(可以不配)、 node 具体见&#xff1a;https://ygstriver.blog.csdn.net/articl…

day28-JSP

0目录 JSP 1.为什么使用JSP 2.B/S和C/S的区别 3.URL 4.Tomcat 5.JSP实战综合项目 1.为什么使用JSP 1.1 JSP定义&#xff1a; &#xff08;1&#xff09;是一种动态网页技术 &#xff08;2&#xff09;Java Server Pages&#xff08;Java服务器端页面技术&#xff09; 1.2 …

docker进阶

Docker网络 [rootecs-56325218 ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 2c63c1a8145c bridge bridge local 70d3439bbb55 host host local ffc74cf89143 none null local[rootecs-56325218 ~]# docker network cre…

day 42 01背包

01背包裸题 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。 每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 二维数组 dp含义&#xff1a; dp[ i ][ j ] 表示从下标为 [ 0 - i ]的物品里…