红黑树

2023-11-09

1.概念:

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

2.性质:

1.每个节点不是红色就是黑色;
2.根节点是黑色;
3.叶子节点(NULL节点)是黑色的;
4.红色节点的两个孩子必须是黑色的;
5.对于每个节点,从该节点出发到叶子节点所有路径上的黑色节点数相等。
在这里插入图片描述
因为要满足红黑树的这五条性质,如果我们插入的是黑色节点就一定会违反规则5,造成大规模调整,得不偿失;但如果插入的节点默认是是红色,只有在插入节点是根节点违反规则2,或者插入节点的父节点是红色时才会违反规则4,所以,我们要把插入节点默认设置为红色。

3.插入:

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

情况1:cur是根节点(树为空)时:直接将根节点涂黑即可
在这里插入图片描述

情况2:p是黑色时:直接插入

在这里插入图片描述

情况3:p为红色,g为黑色,u存在且为红色时:将p和u涂黑,将g涂红,从g的位置继续向上调整,直至
在这里插入图片描述

情况4:p为红色,g为黑,u为黑/不存在

  • 4.1:p为g的左孩子,且cur为p的左孩子时:对p进行右旋,将p变黑,g变红
    在这里插入图片描述
  • 4.2:p为g的右孩子,且cur为p的右孩子时:对p进行左旋,将p变黑,g变红
    在这里插入图片描述

情况5:p为红色,g为黑,u为黑/不存在

  • 5.1:p为g的左孩子,且cur为p的右孩子时:先对p进行一次左旋,就变成了4.1的情况
    在这里插入图片描述
  • 5.2:p为g的右孩子,且cur为p的左孩子时:先对p进行一次右旋,就变成了4.2的情况
    在这里插入图片描述

4.实现:

#pragma once 
namespace YD
{
	enum Color{ RED, BLACK };

	//节点类
	template<class T>
	class RBTreeNode
	{
	private:
		T m_data;
		RBTreeNode<T>* m_lchild;
		RBTreeNode<T>* m_rchlid;
		RBTreeNode<T>* m_parent;
		Color m_color;
	public:
		RBTreeNode(const T& val = T(), Color color = RED) :
			m_color(color),
			m_data(val),
			m_lchild(nullptr),
			m_rchlid(nullptr),
			m_parent(nullptr)
		{}
		template<class T>
		friend class RBTree;
	};

	template<class T>
	class RBTree
	{
	private:
		RBTreeNode<T>* m_head;//head节点的父节点指向root,root的父节点指向head
		size_t m_size;

		//左旋
		void lRound(RBTreeNode<T>* pre)
		{
			RBTreeNode<T>* parent = pre->m_parent;
			RBTreeNode<T>* cur = pre->m_rchlid;
			cur->m_parent = parent;
			//1.和父节点建立关系
			//要旋转的父节点不是head
			if (parent != m_head)
			{
				if (parent->m_lchild == pre)//pre是父节点的左孩子
				{
					parent->m_lchild = cur;
				}
				else
				{
					parent->m_rchlid = cur;
				}
			}
			else
			{
				//父节点是head,就直接对head操作
				m_head->m_parent = cur;
				cur->m_parent = m_head;
			}

			//2.交换孩子,cur被左旋上去,作为pre的父节点,
			//pre成为cur的做左孩子,cur的左孩子成为pre的右孩子
			pre->m_rchlid = cur->m_lchild;
			if (cur->m_lchild)
			{
				//孩子认父亲
				cur->m_lchild->m_parent = pre;
			}
			cur->m_lchild = pre;
			pre->m_parent = cur;
		}

		//右旋
		void rRound(RBTreeNode<T>* pre)
		{
			RBTreeNode<T>* parent = pre->m_parent;
			RBTreeNode<T>* cur = pre->m_rchlid;
			cur->m_parent = parent;

			if (parent != m_head)
			{
				if (pre == parent->m_lchild)
				{
					parent->m_lchild = cur;
				}
				else
				{
					parent->m_rchlid = cur;
				}
			}
			else
			{
				m_head->m_parent = cur;
				cur->m_parent = m_head;
			}

			pre->m_lchild = cur->m_rchlid;
			if (cur->m_rchlid)
			{
				cur->m_rchlid->m_parent = pre;
			}
			cur->m_rchlid = pre;
			pre->m_parent = cur;
		}

		//销毁树
		void destory(RBTreeNode<T>* root)
		{
			if (root)
			{
				destory(root->m_lchild);
				destory(root->m_rchlid);
				delete root;
			}
		}

		//获得根节点
		RBTreeNode<T>* &getRoot()
		{
			return m_head->m_parent;
		}


		//调整3,4,5种情况
		void Adjust(RBTreeNode<T>*& parent, RBTreeNode<T>*& cur)
		{
			RBTreeNode<T>* grand = parent->m_parent;
			RBTreeNode<T>* uncle;
			//情况3. 4.1 5.1 左左,左右
			if (parent == grand->m_lchild)
			{
				//只要没找到根节点,或者父节点不为黑就必须一直向上调整
				while (parent != m_head && parent->m_color == RED)
				{
					grand = parent->m_parent;
					uncle = grand->m_rchlid;
					//情况3:父红,叔红,爷黑
					if (uncle && uncle->m_color == RED)
					{
						//调整为父叔黑,爷红,从爷位置继续向上调
						parent->m_color = BLACK;
						uncle->m_color = BLACK;
						grand->m_color = RED;
						cur = grand;
						parent = cur->m_parent;
					}
					//情况4,5
					else
					{
						//情况5.1:左右
						if (cur == parent->m_rchlid)
						{
							//一次左旋,变成左左,变成情况4.1
							lRound(parent);
							RBTreeNode<T>* tmp = parent;
							parent = cur;
							cur = tmp;
						}
						//情况4.1;左左,一次右旋,父变黑,爷变红
						rRound(grand);
						parent->m_color = BLACK;
						grand->m_color = RED;
						//情况4,5只需一次调整
						break;
					}
				}
			}
				//情况3 4.2. 5.2 右右,右左
			else
			{
				//只要没找到根节点,或者父节点不为黑就必须一直向上调整
				while (parent != m_head && parent->m_color == RED)
				{
					grand = parent->m_parent;
					uncle = grand->m_lchild;
					//情况3:父红,叔红,爷黑
					if (uncle && uncle->m_color == RED)
					{
						//调整为父叔黑,爷红,从爷位置继续向上调
						parent->m_color = BLACK;
						uncle->m_color = BLACK;
						grand->m_color = RED;
						cur = grand;
						parent = cur->m_parent;
					}
					//情况4,5
					else
					{
						//情况5.2:右左
						if (cur = parent->m_lchild)
						{
							//一次右旋,变成右右,变成情况4.2
							rRound(parent);
							RBTreeNode<T>* tmp = parent;
							parent = cur;
							cur = tmp;
						}
						//情况4.2;右右,一次左旋,父变黑,爷变红
						lRound(grand);
						parent->m_color = BLACK;
						grand->m_color = RED;
						//情况4,5只需一次调整
						break;
					}
				}
			}

		}

		static RBTreeNode<T> * increasement(RBTreeNode<T> * cur)
		{
			RBTreeNode<T> * tmp = cur;
			if (cur->m_right)
			{
				tmp = cur->m_right;
				for (; tmp->m_left; tmp = tmp->m_left);
			}
			else
			{
				tmp = tmp->m_parent;
				for (; cur != tmp->m_left && cur == tmp->m_right; tmp = tmp->m_parent)
				{
					cur = tmp;
				}
			}
			return tmp;
		}

		static RBTreeNode<T> * decreasement(RBTreeNode<T> * cur)
		{
			RBTreeNode<T> * tmp = cur;
			if (cur->m_left)
			{
				tmp = cur->m_left;
				for (; tmp->m_right; tmp = tmp->m_right);
			}
			else
			{
				tmp = tmp->m_parent;
				for (; cur != tmp->m_right && cur == tmp->m_left; tmp = tmp->m_parent)
				{
					cur = tmp;
				}
			}
			return tmp;
		}
		public:
			RBTree() :
				m_size(0)
			{
				m_head = new RBTreeNode<T>;
			}
			~RBTree()
			{
				m_size = 0;
				destory(m_head);
				m_head->m_lchild = m_head->m_rchlid = m_head->m_parent = nullptr;
			}

			size_t size()
			{
				return m_size;
			}

			bool empty()
			{
				return m_head->m_parent = nullptr;
			}

			//第一个孩子,最小值
			RBTreeNode<T>* FirstChild()
			{
				RBTreeNode<T>* cur = getRoot();
				for (; cur->m_lchild; cur = cur->m_lchild);
				return cur;
			}
			//最后一个孩子,最大值
			RBTreeNode<T>* LastChild()
			{
				RBTreeNode<T>* cur = getRoot();
				for (; cur->m_rchlid; cur = cur->m_rchlid);
				return cur;
			}

			RBTreeNode<T>* Find(const T& val)
			{
				RBTreeNode<T>* root = getRoot();
				if (root)
				{
					RBTreeNode<T>* cur = root;
					while (cur)
					{
						if (cur->m_data == val)
						{
							return cur;
						}
						else if (cur->m_data < data)
						{
							cur = cur->m_lchild;
						}
						else
						{
							cur = cur->m_rchlid;
						}
					}
					return nullptr;
				}
				return nullptr;
			}

			pair<RBTreeNode<T>*,bool> Insert(const T& val)
			{
				pair<RBTreeNode<T>*, bool> ret(nullptr, false);
				RBTreeNode<T>* & root = getRoot();
				//树不为空
				if (root)
				{
					RBTreeNode<T>* cur = root;
					RBTreeNode<T>* pre = nullptr;
					while (cur)
					{
						if (cur->m_data < val)
						{
							pre = cur;
							cur = cur->m_rchlid;
						}
						else if (cur->m_data > val)
						{
							pre = cur;
							cur = cur->m_lchild;
						}
						else//树中该值插入失败
						{
							ret.first = cur;
							return ret;
						}
					}

					cur = new RBTreeNode<T>(val);
					ret.first = cur;
					if (val < pre->m_data)
					{
						pre->m_lchild = cur;
					}
					else
					{
						pre->m_rchlid = cur;
					}
					cur->m_parent = pre;

					//开始调整红黑树
					//情况3.4.5
					if (pre->m_color == RED)
					{
						Adjust(pre, cur);
					}
					//情况2:父黑不会违反任何规则,直接插入
					else
					{
						//do nothing
					}

				}
				//情况1:树为空,插入的是根节点
				else
				{
					root = new RBTreeNode<T>(val, BLACK);
					root->m_parent = m_head;
					m_head->m_parent = root;
					ret.first = root;
				}
				root->m_color = BLACK;
				m_head->m_lchild = FirstChild();
				m_head->m_rchlid = LastChild();
				m_size++;
				ret.second = true;
				return ret;
			}

			bool erase(const T &val)
			{
				if (m_root == nullptr)
				{
					return false;
				}

				RBTreeNode<T> * cur = m_root;
				RBTreeNode<T> * pre = m_root;

				while (cur)
				{
					if (val < cur->m_data)
					{
						pre = cur;
						cur = cur->m_left;
					}
					else if (val > cur->m_data)
					{
						pre = cur;
						cur = cur->m_right;
					}
					else
					{
						break;
					}
				}

				if (cur == nullptr)
				{
					return false;
				}

				if (cur->m_left && cur->m_right)
				{
					RBTreeNode<T> * cur2 = cur->m_left;
					RBTreeNode<T> * pre2 = cur;

					if (cur2->m_right)
					{
						for (; cur2->m_right; pre2 = cur2, cur2 = cur2->m_right);
						pre2->m_right = cur2->m_left;
						cur2->m_left = cur->m_left;
					}

					cur2->m_right = cur->m_right;

					if (cur == pre)
					{
						m_root = cur2;
					}
					else
					{
						if (cur->m_data < pre->m_data)
						{
							pre->m_left = cur2;
						}
						else
						{
							pre->m_right = cur2;
						}
					}

					delete cur;
				}
				else if (cur->m_left)
				{
					if (cur == pre)
					{
						m_root = cur->m_left;
					}
					else
					{
						if (cur->m_data < pre->m_data)
						{
							pre->m_left = cur->m_left;
						}
						else
						{
							pre->m_right = cur->m_left;
						}
					}
					delete cur;
				}
				else
				{
					if (cur == pre)
					{
						m_root = cur->m_right;
					}
					else
					{
						if (cur->m_data < pre->m_data)
						{
							pre->m_left = cur->m_right;
						}
						else
						{
							pre->m_right = cur->m_right;
						}
					}

					delete cur;
				}
			}
	};
};

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

红黑树 的相关文章

  • json.net自定义jobject反序列化

    我正在尝试使用 JsonConvert DeserializeObject string 将字符串反序列化为可与动态一起使用的 jobject 来动态访问 json 文档 但是我想避免知道文档的大小写 以便我可以输入 dynamic doc
  • Poco c++Net:Http 从响应中获取标头

    我使用 POCO C Net 库进行 http 我想尝试制定持久缓存策略 首先 我认为我需要从缓存标头中获取过期时间 并与缓存值进行交叉检查 如果我错了 请告诉我 那么我如何从中提取缓存头httpResponse 我已经看到你可以用 Jav
  • 在 OnModelCreating 期间设置列名称

    Issue 我目前正在尝试通过设置的属性为我的表及其列添加前缀 我正在使用实体框架核心 我已经正确地为表名添加了前缀 但我似乎无法弄清楚列的前缀 我有一种感觉 我需要使用反射 我已经留下了我的 可能很糟糕的 反思尝试 有人有办法在实体中设置
  • linq 中使用字符串数组 c# 的 'orderby'

    假设我有一个这样的方法定义 public CustomerOrderData GetCustomerOrderData string CustomerIDs var query from a in db Customer join b in
  • 如何使用 SOAP 且不使用 WSE 在 .NET 中签署 Amazon Web 服务请求

    亚马逊产品广告 API 以前称为 Amazon Associates Web Service 或 Amazon AWS 实施了一项新规则 即自 2009 年 8 月 15 日起 向其发送的所有 Web 服务请求都必须经过签名 他们在其网站上
  • Gwan C#,如何获取HTTP标头?

    我需要它来重写 url 以了解我正在处理哪个友好的 url 用于用户代理和其他东西 EDIT public class Gwan MethodImplAttribute MethodImplOptions InternalCall exte
  • 如何制作可启动程序?

    所以 这个问题可能看起来很奇怪 但假设我编译了 int main void int x 3 int y 4 int z x y 是否可以让CPU这样运行 如何 例如 这允许我写入监视器吗 如果我没记错的话 内存中有些地方可以写入要显示的内容
  • C# 5 async/await 线程机制感觉不对?

    为什么让调用线程进入异步方法直到内部 等待 一旦调用异步方法就生成一个线程 这不是更干净吗 这样您就可以确定异步方法会立即返回 您不必担心在异步方法的早期阶段没有做任何昂贵的事情 我倾向于知道某个方法是否要在 我的 线程上执行代码 不管是堵
  • 计算另一个表达式中的 C# 表达式

    我想在另一个表达式中使用一个表达式 Expression
  • 访问 ascx 文件中的母版页控件

    我有一个母版页文件 其中包含 2 个面板控件中的 2 个菜单 我还使用控件来检查用户是否登录并获取用户类型 根据我想要显示 隐藏面板的类型 控件本身不在母版页中引用 而是通过 CMS 系统动态引用 我想在用户控件中使用findcontrol
  • 使用查询表达式对 List 进行排序

    我在使用 Linq 订购这样的结构时遇到问题 public class Person public int ID get set public List
  • MFC:如何设置CEdit框的焦点?

    我正在开发我的第一个简单的 MFC 项目 但我正在努力解决一个问题 想要设置所有的焦点CEdit其中一个对话框中的框 我的想法是 当打开对话框时 焦点位于第一个编辑框上 然后使用 选项卡 在它们之间交换 我看到了方法SetFocus 但我无
  • 引用/指针失效到底是什么?

    我找不到任何定义指针 引用无效在标准中 我问这个问题是因为我刚刚发现 C 11 禁止字符串的写时复制 COW 据我了解 如果应用了 COW 那么p仍然是一个有效的指针并且r以下命令后的有效参考 std string s abc std st
  • 逆向工程 ASP.NET Web 应用程序

    我有一个 ASP NET Web 应用程序 我没有源代码 该 bin 包含 10 个程序集和一个 compiled 文件 我在 App Code dll 上使用 Reflector 它向我显示了类和命名空间之类的东西 但它太混乱了 有没有什
  • .NET 4 的条件编译[重复]

    这个问题在这里已经有答案了 可能的重复 条件编译和框架目标 https stackoverflow com questions 2923210 c sharp conditional compilation and framework ta
  • 使用 jQuery 从 ASP.Net JSON 服务获取数据

    我正在尝试调用 Google 地图地理编码 API 从纬度 经度对中获取格式化的地址 然后将其记录到控制台 我正在尝试获取为给定位置返回的第一个 formatted address 项目 我很简单无法从 JSON 中提取该项目 我不知道为什
  • 为什么以下 C 程序会出现总线错误?

    我认为这是第一个失败的 strtok 调用 好久没写C了 有点不知所措 非常感谢 include
  • 如何将 SQL“LIKE”与 LINQ to Entities 结合使用?

    我有一个文本框 允许用户指定搜索字符串 包括通配符 例如 Joh Johnson mit ack on 在使用 LINQ to Entities 之前 我有一个存储过程 该存储过程将该字符串作为参数并执行以下操作 SELECT FROM T
  • 结构化绑定的用例有哪些?

    C 17 标准引入了新的结构化绑定 http en cppreference com w cpp language structured binding功能 最初是proposed http www open std org jtc1 sc
  • 使用未分配的局部变量

    我遇到了一个错误 尽管声明了变量 failturetext 和 userName 错误仍然出现 谁能帮帮我吗 Use of Unassigned local variable FailureText Use of Unassigned lo

随机推荐

  • Vue组件(插槽)

    1 插槽属于Vue组件的三个核心之一 其余两个分别是属性和事件 今天主要学习插槽的使用 2 插槽 slot 将子组件和父组件进行组合 可以弥补视图的不足 是组件具有更好的拓展性 组件的封装方式 抽取共性 3 插槽的使用方式 1 vue2 0
  • 解决'Unknown custom element'问题

    报错截图 解决方法 模板少写了一个 然后删除多余的模板就行啦
  • 如何安装mysql5.7包_安装mysql 5.7 最完整版教程

    安装环境 CentOS7 64位 MINI版 安装MySQL5 7 1 配置YUM源 在MySQL官网中下载YUM源rpm安装包 http dev mysql com downloads repo yum 下载mysql源安装包 shell
  • anaconda python3.8目录_Linux系统下Anaconda的安装和使用教程

    一 Anaconda的安装 去官网下载 https www anaconda com products individual 下载到本地后利用FileZilla软件上传到服务器 我这里上传到了 data bioinfosoftware文件夹
  • vcpkg编译第三方库leveldb

    vcpkg编译leveldb 1 安装vcpkg 使用git命令直接pull vcpkg源码 git clone https github com microsoft vcpkg 2 在vcpkg目录执行bootstrap vcpkg ba
  • Android 之 intent内容解析

    文章目录 intent intent 属性 1 Action 匹配规则 Action匹配只要有一个与Intent中携带Action相同即可 2 Category 注意 3 Data 4 Component 5 Type 6 Extras 存
  • DirectShow音视频同步实验报告(2)

    单一视频流 Filter Graph如图2 图2 单一视频流的Filter Graph 注意 紧靠Video Renderer的上一级Filter的Video输出Pin 其GetMediaType函数提供的Media Type的VIDEOI
  • CMake学习之set

    文章目录 一 set关键字 二 变量的使用 一 set关键字 将一个cmake变量设置为给定值 将变量
  • 搭建jupyter notebook,开启线上IDE学习

    一 windows搭建jupyter notebook 在jupyter notebook中利用本地虚拟环境 1 激活本地虚拟环境 activate py36 安装nb conda conda install nb conda 3 在ana
  • 发布一套很有本土特色的闽南语QQ表情

    发布一套很有本土特色的闽南语QQ表情 作为福建本地人 对闽南语在熟悉不过了 平时朗朗上口的俗话 现在演变成活泼可爱有趣的QQ表情咯 大家喜欢的话可以来收藏 底下有QQ表情导入包 直接导入QQ即可使用了
  • 如何安装EasyX图形库

    如何下载 1 打开EasyX官网点我 应该是这样子的 2 点击 下载 EasyX 在图片的右边 找不到算你眼瞎 3 直接打开安装包 4 下一步 来到选择界面 5 点击安装 EasyX文档也可以安装一下 但下面的必须点一个 6 点击关闭 结束
  • 服务器运维基础知识,运维技术必须了解的服务器基础知识

    小影提醒 文章部分内容源于互联网收集整理 不代表影速观点 若有咨询 运维技术必须了解的服务器基础知识 等有关服务器 云主机租用 托管 配置 价格问题 请随时咨询影速科技客服 享受1v1贴心服务 1 双路等于双核么 常听说双路至强XX式服务器
  • Spring Boot单元测试

    目录 什么是单元测试 单元测试的好处 单元测试的实现 断言 修改操作 删除操作 添加数据 返回受影响的行数 返回自增id 什么是单元测试 是指对软件中的最小可测试单元进行检查和验证的过程 单元测试的好处 可以非常简单 直观 快速的测试某一个
  • 模块化 组合化_一流的组合模块系统

    模块化 组合化 这是本系列的第二篇文章 介绍了用于组合的反转控制类型系统 本文讨论了比上一篇文章的 一流过程类型 更通用的 模块类型 系统 注意 某些功能性编程语言也尝试定义一流模块 本文定义的First Class Modules是从反向
  • 前端基础学习之Sass

    一 概念 1 Sass是什么 Sass 英文全称 Syntactically Awesome Stylesheets 是一个最初由 Hampton Catlin 设计并由 Natalie Weizenbaum 开发的层叠样式表语言 Sass
  • AI读天涯神贴----人,应该怎么活

    接着上一期AI读天涯神贴 开悟其实很简单 这期我们来用AI读另外一篇神帖 人 应该怎么活 下面是帖子中的一些节选 老家有句话 叫春天戳一棍 秋天吃一顿 意思是 春天用个棍子在地上捅个窟窿 扔进种子 那秋天就有可能因此吃上一顿的果实 春天 这
  • Prometheus - SSL 证书过期监控

    目录 一 环境 二 部署 Exporter 2 1 配置 blackbox exporter 2 2 配置 Prometheus 2 3 Grafana 监控面板 一 环境 数据采集 Exporter blackbox exporter V
  • Java实现登录[数据库]

    和上篇的随机点名系统一样 都是使用MySQL数据库来实现 因为刚学所以写点简单例子满足下自己 需求分析 1 输入用户名和密码 2 与数据库中的记录进行比较 原理比较 简单 直接贴代码吧 import java sql Connection
  • 黑马点评给店铺类型查询业务添加缓存(List实现)

    代码如下 public Result queryShopTypeList String key CACHE SHOP TYPE KEY List 1 从Redis中查询店铺类型 获取所有 List
  • 红黑树

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