红黑树的基本原理

news2024/11/19 13:16:47

目录

一.概念与性质

二.基本操作

1.建树

2.插入

情况一

情况二

3.查找

4.验证

三.红黑树与AVL树的比较


一.概念与性质

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

红黑树的性质:

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

注意:

1.我们平时所说的叶子节点指的是没有子节点的节点,但在红黑树中,叶子节点指的是空节点,即图中NIL节点。这样有助于红黑树的效率与维护,但在简单实现中,其体现感不强。

2.红黑树中不能有连续的红色节点,但可以有连续的黑色节点

3.红黑树最坏情况下最长路径是最短路径的两倍。由性质3、4决定,最长路径为黑-红-黑交替,最短路径为全黑。

二.基本操作

1.建树

树的节点跟树的建立跟二叉搜索树与AVL树相近, 但需要添加颜色标记,采用枚举体。

注意:插入的节点应该是红色的因为如果插入黑色节点,一定会违反性质4,但如果插入红色节点,只可能会违反性质3(红色节点下连接了一个新的红色节点)。因此,应选择默认插入红色节点,减少修改次数。

enum Colour
{
	RED,   //0
	BLACK  //1
};

//RBTree树节点
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:

private:
	Node* _root = nullptr;
};


2.插入

红黑树的插入方式与AVL树相似,即按照大小走到底部,找到插入位置,插入后自下而上维护颜色性质即可

关键在于如何在插入后进行调整:

当新插入的节点的父节点是黑色的,没有违反红黑树任意一条性质,不需要改动;当新插入的节点的父节点是红色的,违反了性质三,即不能有连续的红节点,此时需分情况讨论。

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

注意:以下图解均为叔叔在祖父的右边,代码中给出了全部情况

情况一

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

       

对此,可以将抽象子树a, b, c, d的高度按h = 0,h = 1举例,当h > 2后的种类很多,不再展示,0和1对于规律的推出已经足够。

h = 0时

h = 1时

由此,我们得出规律:当cur为红,p为红,g为黑,u存在且为红时:将p,u改为黑,g改为红。如果g的父节点存在且为红,则继续向上调整,否则停止。若根节点变红了,则要把其调整回黑色。

代码:

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

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

		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 = new Node(kv);
		cur->_col = RED;

		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		// 别忘记指向父亲
		cur->_parent = parent;
		

		// 往上更新  如果父亲的颜色是黑色就停止
		while (parent && parent->_col == RED)
		{
			// 关键看叔叔
			Node* grandfather = parent->_parent;


			//如果叔叔在右边
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//叔叔存在且为红, 往上变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上
					cur = grandfather;
					parent = cur->_parent;
				}
				
			
			}
			else//叔叔在左边
			{
				Node* uncle = grandfather->_left;

				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				

			}
			
		}

		// 如果根节点变红了 设回黑
		_root->_col = BLACK;
		return true;
		
	}

情况二

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

依旧是选取h=0 和 h = 1时举例:

h = 0,u不存在时

h = 1,u存在时,两种情况

因为在旋转时不对颜色进行处理,所以u不存在时和u为黑归为一类,所以当cur为红,p为红,g为黑,u不存在/u为黑且u在其祖父节点的右边时:若cur在p的左边,则进行右单旋将p变为黑,g变为红;若cur在p的右边,则先进行左单选,再进行右单旋,将cur变为黑,g变为红。

注意:只要进行了上述两种带旋转的操作,涉及到的节点的最上端祖先一定变为了黑色,无需再向上调整。

插入完整代码:

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

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

		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 = new Node(kv);
		cur->_col = RED;

		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		// 别忘记指向父亲
		cur->_parent = parent;
		

		// 往上更新  如果父亲的颜色是黑色就停止
		while (parent && parent->_col == RED)
		{
			// 关键看叔叔
			Node* grandfather = parent->_parent;


			//如果叔叔在右边
			if (parent == grandfather->_left)
			{
				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);
						RotateR(grandfather);
						//cur去了g的位置  g去了u的位置
						cur->_col = BLACK;
						grandfather->_col = RED;
						
					}
					//根一定是黑色的
					break;
				}
			}
			else//叔叔在左边
			{
				Node* uncle = grandfather->_left;

				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在,或者存在且为黑
				{
					//    g
					// u     p
					//          c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//    g
						// u     p
						//    c
						RotateR(parent);
						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和SubLR
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//再处理parent和 subl
		//因为 后面需要把 parent的parent和subl链接
		//需要先保存parent的parent
		subL->_right = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subL;

		//处理 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;
		}
	}
	

3.查找

与二叉搜索树,AVL树一致,从上往下按大小走即可。

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 nullptr;
}

4.验证

红黑树的检验主要在两个方面:

1.其中序遍历是否为有序(二叉搜索树的性质)

2.是否满足红黑树的性质

关于性质4,我们可以先预处理一条路径的黑色节点数,再中序遍历时判断是否相等即可。

public:

void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		if (_root->_col == RED)
		{
			return false;
		}

		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}

			cur = cur->_left;
		}

		return _Check(_root, 0, refNum);
	}

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

	int Size()
	{
		return _Size(_root);
	}

private:

	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

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

		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}

	bool _Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			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 _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

三.红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2 N),红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。

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

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

相关文章

构建大语言模型友好型网站

以大语言模型为代表的AI 技术迅速发展&#xff0c;将会影响原有信息网络的方式。其中一个明显的趋势是通过chatGPT 对话代替搜索引擎和浏览器来获取信息。 互联网时代&#xff0c;主要是通过网站&#xff08;website&#xff09;提供信息。网站主要为人类阅读的方式构建的。主要…

✊构建浏览器工作原理知识体系(网络协议篇)

🌻 前言 书接上回~ 系列文章目录: # ✊构建浏览器工作原理知识体系(开篇)# ✊构建浏览器工作原理知识体系(浏览器内核篇)# ✊构建浏览器工作原理知识体系(网络协议篇)✊构建浏览器工作原理知识体系(网页加载超详细全过程篇)为什么你觉得偶尔看浏览器的工作原理,…

【稳定检索/投稿优惠】2024年艺术、语言与文化交流国际会议(ALCE 2024)

2024 International Conference on Art, Language, and Cultural Exchange 2024年艺术、语言与文化交流国际会议 【会议信息】 会议简称&#xff1a;ALCE 2024 截稿时间&#xff1a;点击查看 大会地点&#xff1a;中国桂林 会议官网&#xff1a;www.icalce.com 会议邮箱&#…

重生奇迹mu套装掉的地点一览

1、目前只有三个地方掉套装&#xff1a;赤色要塞&#xff0c;不是100%掉&#xff0c;靠运气。卡利玛7&#xff0c;杀困顿能掉。魔炼之地&#xff0c;只有城主盟成员可以进入。 2、只有攻城城主盟可以进入的地图“魔炼之地”掉套装&#xff0c;暴率几乎为0。如果你是敏法的话&am…

深圳宝安餐饮行业揭秘:可燃气体报警器校准方法与周期的重要性

在日益注重餐饮安全的今天&#xff0c;深圳宝安区的餐饮行业也在不断探索和实践更加有效的安全管理措施。其中&#xff0c;可燃气体报警器的使用与校准成为了保障餐饮场所安全的重要一环。 在这篇文章中&#xff0c;佰德将详细解析可燃气体报警器的重要性、深圳宝安餐饮现状、…

LLM定制的四个层次

LLM(Large Language Models)代表了一种提高生产力的创新方法。他们能够简化各种任务&#xff0c;显著提高整体效率。从提示工程到Agents可以分为四个层次。 Level-1: Prompt engineering Prompt是简明的输入文本&#xff0c;用作查询或指令&#xff0c;引导语言模型产生所需输…

借助Historian Connector + TDengine,打造工业创新底座

在工业自动化的领域中&#xff0c;数据的采集、存储和分析是实现高效决策和操作的基石。AVEVA Historian (原 Wonderware Historian) 作为领先的工业实时数据库&#xff0c;专注于收集和存储高保真度的历史工艺数据。与此同时&#xff0c;TDengine 作为一款专为时序数据打造的高…

关于会议论文/CPCI/ISTP会议论文

关于会议论文 会议论文是公开发表的学术论文&#xff0c;一般正式的国际学术会议都会公开征稿&#xff0c;并要求录用的论文在会议上进行宣读、交流&#xff0c;然后集结出版&#xff0c;这就是我们常说的会议论文集&#xff0c;而这些发表的论文也可用于硕博毕业、项目结题、…

视频直播点播EasyDSS平台授权时,出现授权时间即将到期的提示是什么原因?

视频直播点播EasyDSS平台具备灵活的视频能力&#xff0c;包括直播、点播、转码、管理、录像、检索、时移回看等&#xff0c;平台支持音视频采集、视频推拉流、播放H.265编码视频、存储、分发等能力服务&#xff0c;可应用在无人机推流、在线直播、虚拟直播、远程培训等场景中。…

跃入AI新纪元:亚马逊云科技LLM全景培训,解锁AI构建者之路

亲爱的技术爱好者们&#xff0c;你是否也对大语言模型&#xff08;LLM&#xff09;的神奇魅力所吸引&#xff0c;渴望深入探索其背后的技术奥秘&#xff1f;今天&#xff0c;我要为大家推荐一份超级硬核的学习资源——亚马逊云科技 对话AI 构建者&#xff1a;从基础到应用的LLM…

Linux安装Docker | 使用国内镜像

环境 CentOS7 先确认能够上网 curl www.baidu.com返回该输出说明网络OK 步骤一&#xff1a;安装gcc 和 gcc-c yum -y install gccyum -y install gcc-c步骤二&#xff1a;安装Docker仓库 yum install -y yum-utils接下来配置yum的国内镜像 yum-config-manager --add-re…

基于变分自动编码器VAE的电池剩余使用寿命RUL估计

加载模块 import math import itertools import numpy as np import pandas as pd import seaborn as sns import tensorflow as tf from keras import layers from sklearn.svm import SVR from tensorflow import keras from keras import backend as K import matplotlib.p…

Unity | Shader基础知识(番外:了解内置Shader-Standard-含specular模式<二>)

目录 前言 一、Standard参数详解 1.NormalMap法线贴图 2.HeightMap高度贴图 3.Occlusion遮挡贴图 4.DetailMask细节遮挡 5.Emission自发光 6.Tiling铺地砖和Offset偏移度 二、Standard-Specular setup模式 三、作者的碎碎念 前言 Unity | Shader基础知识(番外&#xf…

【ATU Book-i.MX8系列 - TFLite 进阶】 NXP i.MX8M Plus 实现高效 Mobilenet SSD 物体检测

NXP i.MX8M Plus 实现高效 Mobilenet SSD 物体检测 一、概述 在 边缘运算(Edge Computing) 领域中&#xff0c;轻量级的模型扮演着举足轻重的角色。因此&#xff0c;如何在有限硬体资源下实现电脑视觉&#xff08;Computer vision&#xff09; 应用是个极具挑战性的课题。特别…

Flow Launcher:Windows高效启动与搜索工具

目录 一、软件简介 二、软件安装 三、软件功能介绍 3.1 快速启动应用 3.2 文件快速搜索 3.3 多功能操作中心 3.4 支持插件扩展 一、软件简介 Flow Launcher 是基于C#编程语言开发一款专为Windows设计的高效启动与搜索工具&#xff0c;它以创新简洁的界面重新定义了用户…

基于SWIFT和Qwen1.5-14B-Chat进行大模型LoRA微调测试

基于SWIFT和Qwen1.5-14B-Chat进行大模型LoRA微调测试 环境准备 基础环境 操作系统&#xff1a;Ubuntu 18.04.5 LTS (GNU/Linux 3.10.0-1127.el7.x86_64 x86_64)Anaconda3&#xff1a;Anaconda3-2023.03-1-Linux-x86_64根据服务器网络情况配置好conda源和pip源&#xff0c;此…

揭秘!义乌理阳是否涉足海外拼多多选品师项目?

在全球化的今天&#xff0c;跨境电商已成为一种趋势&#xff0c;越来越多的企业开始关注并投入这一领域。而拼多多作为国内知名的电商平台&#xff0c;其海外业务也在迅速扩展。那么&#xff0c;义乌理阳信息咨询服务有限公司是否有海外拼多多选品师的项目呢?下面我们将对此进…

JavaSE中的IO(输入/输出)字节流字符流

JavaSE中的IO&#xff08;输入/输出&#xff09;知识是一个广泛的领域&#xff0c;它涵盖了如何在Java程序中进行数据的读取和写入。以下是对JavaSE中IO知识的一个清晰归纳&#xff1a; 一、基础知识 流&#xff08;Stream&#xff09;的概念 流是一组有顺序的、有起点和终点…

大模型应用之基于 Langchain 的测试用例生成

一 用例生成实践效果 在组内的日常工作安排中&#xff0c;持续优化测试技术、提高测试效率始终是重点任务。近期&#xff0c;我们在探索实践使用大模型生成测试用例&#xff0c;期望能够借助其强大的自然语言处理能力&#xff0c;自动化地生成更全面和高质量的测试用例。 当前…

【通过新能源汽车的智慧数字底盘技术看计算机的相关技术堆栈?以后是软硬结合的全能程序员的天下,取代全栈(前后端都会的全栈程序员)】

汽车的“智慧数字底盘”是一个综合性的技术平台&#xff0c;旨在提升车辆的性能、安全性和驾驶体验。它集成了多种先进的技术和系统&#xff0c;是全能程序员的必杀技&#xff01; 1. 传感器技术 a. 激光雷达&#xff08;LiDAR&#xff09; 用于生成高分辨率的3D地图&#…