C++学习记录——이십이 红黑树

news2024/11/22 19:03:29

文章目录

  • 1、了解概念
  • 2、模拟实现
    • 1、插入
    • 2、插入代码
    • 3、测试是否是红黑树
  • 3、封装map、set


1、了解概念

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

性质:

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

对于第四条,也就是说(从根节点走到空)每个路径的黑结点数量都相同。

根据以上性质,最短路径是全黑,最长路径是红黑相间。

在这里插入图片描述

一颗红黑树可能不是最长和最短路径都有。假设有N个黑节点,那么最短路径长度就是logN,整棵树的节点数量区间是【N, 2N】,最长路径长度是2logN。

单纯比较增删查改效率的话,红黑树比不过AVL树。在插入10亿个结点的情况下,AVL树最多查找30次,红黑树则是60次,不过这个次数没有差别。但实际生活中用红黑树多,红黑树性能不弱于AVL树,虽然没有超过,但因为它是近似平衡,不像AVL树那样严格的平衡,旋转次数少的原因,所以红黑树用得多,AVL树用得很少。

2、模拟实现

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)
	{
		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);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		return true;
	}
private:
	Node* _root;
};

1、插入

创建新节点的时候,应该让这个结点是什么颜色的?如果是黑色,那么就不能保证每条路径黑节点数都相同了,所以要红色。新增后,如果父节点是黑节点,那就不做什么,如果是红色,这就有一些情况。

在这里插入图片描述

现在只看右边新插入节点的情况,假设新增结点是cur。cur颜色不能动,那么parent一定要变黑才符合性质;爷节点是黑的;那么还有一个改变就是叔叔结点要变为黑结点,也就是和父结点同一层的那个。这样改变后为了保证每个路径黑节点数量一样,那么爷节点就得变红。

在这里插入图片描述

这样之后还需要判断,爷节点的父节点如果是黑,那就不做什么,如果是红,就把爷节点当作cur,重复刚才的动作。

相当于parent和uncle变黑,grandfather变红,然后g变为cur,继续向上调整。

另外一个插入节点的情况,也就是1–6–节点,这时候没有叔叔结点,此时1是黑,6是红,连红肯定不行,那么6要变为黑。因为这个红节点的插入,这个路径相关的性质以及整个树的高度已经变化了,所以这里需要旋转,结果就是6成为根,1和红节点都是子节点,并且把1变为红,那么路径性质就符合了。如果插在6的右边,那就左旋。

相当于旋转+变色,parent变黑,grandfather变红,parent做新的根,其他两个为子节点。

当整理完一个子树后,向上调整,还会遇到上方的其他节点的子点的子树。

在这里插入图片描述

左边变色变为右边后

在这里插入图片描述

cde的情况有四种,都是包含一个黑节点的红黑子树

在这里插入图片描述

写代码:

		while (parent&& parent->_col == RED)
		{
			Node* gf = parent->_parent;
			if (gf->_left == parent)
			{
				Node* uncle = gf->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					gf->_col = RED;
					cur = gf;
					parent = cur->_parent;
				}
			}
		}
		_root->_col = BLACK;

叔叔节点不存在或者存在且为黑的话,节点为黑,可能是下面变色而来,也可能c是某一种,cd可能是空或者红节点,那就旋转+变色。

cur为红,parent为红,g为黑,叔叔结点存在且为黑或不存在,那么则需要双旋,以p为轴点进行单选,再对g进行单旋,cur变黑,g变红。

2、插入代码

bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(data);
		if (parent->_kv.first > data.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		
		while (parent && parent->_col == RED)
		{
			Node* gf = parent->_parent;
			if (gf->_left == parent)
			{
				Node* uncle = gf->_right;
				//叔叔存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					gf->_col = RED;
					cur = gf;
					parent = cur->_parent;
				}
				else//叔叔不存在或者叔叔存在且为黑,旋转+变色
				{
					//      g   
					//   p      u
					// c
					if (cur == parent->_left)
					{
						RotateR(gf);
						parent->_col = BLACK;
						gf->_col = RED;
					}
					else
					{
						//      g
						//   p     u
						//     c
						RotateL(parent);
						RotateR(gf);
						cur->_col = BLACK;
						//parent->_col = RED;
						gf->_col = RED;
					}
					break;
				}
			}
			else//gf->_right == parent
			{
				//      g
				//   u     p
				//            c
				Node* uncle = gf->_right;
				//叔叔存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					gf->_col = RED;
					cur = gf;
					parent = cur->_parent;
				}
				else//叔叔不存在或者叔叔存在且为黑,旋转+变色
				{
					//     g
					//   u   p
					//         c
					if (cur == parent->_right)
					{
						RotateL(gf);
						gf->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//     g
						//   u   p
						//      c
						RotateR(parent);
						RotateL(gf);
						cur->_col = BLACK;
						gf->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

3、测试是否是红黑树

判断路径2倍这个点不可取,不是充要条件。我们要判断的是根节点颜色,是否有连红,以及黑节点的数量。我们可以添加一个blackNum以及定义一个benchmark变量。

	void IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点是红色" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK) ++benchmark;
			cur = cur->_left;
		}
		return _Check(_root, 0, benchmark);
	}
	
	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑节点的数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == BLACK) ++blackNum;
		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}
		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}
	
	int _High(Node* root)//测量高度
	{
		if (root == NULL) return 0;
		int leftH = _High(root->_left);
		int rightH = _High(root->_lright);
		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

3、封装map、set

我们让map和set都是两个模板参数,并且写仿函数来实现对其中变量的提取。也用到了模板复用。

Set.h

	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};

Map.h

	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

RBTree.h

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;
	int blackNum;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
		, blackNum(0)
	{}
};

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
    bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

迭代器

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
};

迭代器的++有一些考虑。

在这里插入图片描述

往后++的话,如果右边不为空,那就找到右边的最左结点;如果右边为空,沿着根的路径,找孩子是父亲左的那个祖先。

	Self& operator++(const Self& s)
	{
		if (_node->_right)
		{
			//1、右不为空,下一个就是右子树的最左节点
			Node* subL = _node->_right;
			while (subL->_left)
			{
				subL = subL->_left;
			}
			_node = subL;
		}
		else
		{
			//2、右为空,沿着根的路径,找孩子是父亲左的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

测试

	void test_set1()
	{
		int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		set<int> s;
		for (auto e : a)
		{
			s.insert(e);
		}
		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;
	}

    void test_map1()
	{
		map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("string", "字符串"));
		dict.insert(make_pair("count", "计数"));
		map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;
	}

–则是反过来

	Self& operator--(const Self& s)
	{
		if (_node->_left)
		{
			//1、左不为空,找左子树最右结点
			Node* subR = _node->_left;
			while (subR->_right)
			{
				subR = subR->_right;
			}
			_node = subR;
		}
		else
		{
			//2、左为空,沿着根的路径,找孩子是父亲右的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

要实现迭代器的方括号功能,得修改一下insert,把bool类型换成pair<iterator, bool>,相应地return的true和false也需要改变,Map.h和Set.h也需要改一下Insert的类型。这样map和set就可以就[]了。具体的会在下面的链接中。

不过这里有个bug,就是不同的编译器可能会出现迭代器中可以修改元素的操作,实际上我们都知道迭代器不能修改,如果单纯地加const,要修改的地方很多,还会出现其他问题。库里的源代码是这样做的,普通和const迭代器都是const迭代器。

在这里插入图片描述

如果直接按照这样写,会有编译错误。因为const_iterator和iterator用的模板不同,看看源代码在处理这方面做了什么

在这里插入图片描述

最后一行这里

如果这个类模板被实例化成iterator,那这个就是拷贝构造
如果这个类模板被实例化成const_iterator,那就不再是拷贝构造了,这就是一个支持用iterator去构造初始化const_iterator的函数。相当于出现了一个隐式类型转换。

当然还有其他函数,不过这篇就只写了重要的函数。

完整代码:https://gitee.com/kongqizyd/start-some-c-codes-for-learning.c/tree/master/%E7%BA%A2%E9%BB%91%E6%A0%91

结束。

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

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

相关文章

Dcip的学习1-计算器

文章目录 前言一、配置安装环境1.1 网址1.2 再次打开需要进行的操作1.3 NodeJS控制台的操作1.4 出现的页面 二、Dcip生成计算器2.1 软件的基本单位 - Unitform中添加内容 2.2 OnleftChange(); 前言 只是为方便学习&#xff0c;不做其他用途&#xff0c; 一、配置安装环境 1.1 …

Jetpack之Navigation技术解密

Navigation是什么 官方的话&#xff1a; Navigation 是一个框架&#xff0c;用于在 Android 应用中的“目标”之间导航&#xff0c;该框架提供一致的 API&#xff0c;无论目标是作为 Fragment、Activity 还是其他组件实现。 自己的话&#xff1a; Navigation是管理Fragment…

win11、VS2019下配置PCL1.11.1

1、PCL安装配置 下载pcl-1.11.1-pdb-msvc2019-win64与PCL-1.11.1-AllInOne-msvc2019-win64.exe文件。以管理员身份运行PCL-1.11.1-AllInOne-msvc2019-win64.exe程序&#xff0c;截图如下&#xff1a; 安装过程中没有弹出OpenNI2的安装&#xff0c;但是要安装在3rdParty下&#…

MySQL空间查询

MySQL空间查询 文章目录 MySQL空间查询1. 空间数据支持2. 空间函数支持3. 操作示例3.1 创建表结构3.2 插入数据3.3 查询空间相交数据 4. 参考链接 1. 空间数据支持 MySQL 具有对应于 OpenGIS 类的空间数据类型。一些空间数据类型包含单个几何值&#xff1a; GEOMETRYPOINTLIN…

PCS-2022-VVC中帧内和帧间预测的统一快速划分算法

本文来自PCS 2022的论文《Unified Fast Partitioning Algorithm for Intra and Inter Predictions in Versatile Video Coding》 介绍 VVC (Versatile Video Coding) 标准采用了比HEVC (High Efficiency Video Coding) 标准更灵活的划分结构&#xff0c;在HEVC的四叉树划分结构…

前端CSS经典面试题总结

前端CSS经典面试题总结 2.1 介绍一 下 CSS 的盒子模型&#xff1f;2.2 css 选择器优先级&#xff1f;2.3 垂直居中几种方式&#xff1f;2.4 简明说一下 CSS link 与 import 的区别和用法&#xff1f;2.5 rgba和opacity的透明效果有什么不同&#xff1f;2.6 display:none和visib…

在大模型的喧嚣中,SAS向企业AI押注70亿

ChatGPT和GPT所代表的大模型&#xff0c;已经在国内形成了海啸效应&#xff0c;几乎所有顶级科技企业都在想方设法进入大模型赛道。大模型的最大价值在于普遍提升个人生产力&#xff0c;而各行各业的公司都在积极寻找应用大模型和生成式AI的机会&#xff0c;以普遍提升全员生产…

IntelliJ IDEA 安装及配置详细教程

IDEAIntelliJ IDEA 安装及配置详细教程 1、下载2、安装3、IDEA使用设置3.1 进入设置界面3.2 JDK配置3.3 主题样式设置3.4 字体样式设置3.5 编辑器背景颜色设置3.6 字符集和配置文件编码格式设置3.7 设置鼠标悬浮提示 4、idea配置maven5、idea创建springboot工程 IntelliJ IDEA …

MYSQL02高级_目录结构、默认数据库、表文件、系统独立表空间

文章目录 ①. MySQL目录结构②. 查看默认数据库③. MYSQL5.7和8表文件③. 系统、独立表空间 ①. MySQL目录结构 ①. 如何查看关联mysql目录 [rootmysql8 ~]# find / -name mysql /var/lib/mysql /var/lib/mysql/mysql /etc/selinux/targeted/tmp/modules/100/mysql /etc/seli…

服务(第二十篇)mysql高级查询语句(一)

准备环境&#xff1a; 两张表&#xff1a;location和store_info&#xff1b; 1、查询语句类型 ①SELECT "字段" FROM "表名"&#xff1b; 只查看表中的指定字段&#xff1b; 还可以根据查询的字段位置进行排序&#xff1b; ②SELECT DISTINCT "字段…

C语言生成随机数【简易抽卡代码为例】

文章目录 前言一、生成随机数rand() 函数srand()函数time()函数生成一个真正的随机数 二、使用小技巧三、使用代码实例&#xff08;简易抽卡&#xff09;总结 前言 本文将详细解释如何在C语言中生成随机数&#xff0c;并介绍应用的小技巧 一、生成随机数 c语言生成一个真正的…

ESP32 partition(分区表)(15)

提示&#xff1a;本博客作为学习笔记&#xff0c;有错误的地方希望指正&#xff0c;主要参考乐鑫技术手册说明结合实例代码分析&#xff0c;结合理论知识学习后示例分析以及常见问题说明。 文章目录 一、ESP32 Partition概述二、内置分区表三、创建自定义分区表四、生成二进制分…

设备树(属性)简介

1 设备树 简单的整理记录&#xff1b; 学习参考内容&#xff1a; Linux 笔记 https://xuesong.blog.csdn.net/article/details/109522945?spm1001.2014.3001.5502正点原子-左盟主 驱动开发网络资料&#xff1a;IT界小生 https://www.zhihu.com/column/itlife 1.1 设备树简介…

springboot+freemarker+restful

什么是freemarker? FreeMarker是一种模板引擎&#xff0c;它可以用于生成各种类型的文档&#xff0c;比如HTML、XML、PDF、Word等。它可以通过简单的模板语法和数据模型来生成文档内容。与传统的JSP相比&#xff0c;FreeMarker的语法更加简洁和易读&#xff0c;并且可以很好地…

女生学习软件测试怎么样?

在IT技术行业&#xff0c;女生学习还是有很大优势的。女生相较于男生更有耐心&#xff0c;包容性强&#xff0c;心思细腻&#xff0c;对细节把控更好&#xff0c;同时还能帮助团队男女平衡&#xff0c;活跃气氛。 编程是一个只要你肯学习就会有回报的行业&#xff0c;不论男生…

1W字理解Java虚拟机——JVM

目录 一、初识JVM 二、JVM执行流程 三、内存区域划分&#xff08;JVM运行时数据区&#xff09; 3.1 本地方法栈&#xff08;线程私有&#xff09; 3.2 程序计数器&#xff08;线程私有&#xff0c;无并发问题&#xff09; 3.3 JVM虚拟机栈&#xff08;线程私有&#xff0…

【小程序】微信云托管对象存储管理

微信云托管对象存储用于存放数据或文件&#xff0c;一般用于较大数据或较大文件上传时的中转对象&#xff0c;避免直接上传到服务端&#xff0c;影响服务性能。 对象存储 开通了云托管平台后会自动开通对象存储功能。简单理解就是一个文件目录即可。 存储文件 文件名称&…

串口全双工通信与串口中断

1.串口通信编程 STC-ISP串口助手的使用&#xff1a; 文本模式和HEX模式的区别&#xff1a;文本模式就是那些可打印的字符。HEX模式就是这些可打印字符对应的16进制。它们都对应相同的ASCII码&#xff08;用十进制表示&#xff09;。 很多小白在程序编写完成后调试时会搞不清楚…

利用notepad++处理数据,再用excel做则线图

1、利用串口调试XCOM V2.8得到数据 2、利用Notepad编辑数据 利用正则表达式 删除时间戳 移除空行 继续删掉不要的数据 3、用excel生成折线图 复制数据到excel excel自动根据上文公式填充计算 输入0.1和0.2 框选0.1和0.2&#xff0c;下拉

JavaScript-jQuery的使用 + JS的案例

目录 点击更换图片 猜数字 搜索页面展示 表白墙 点击更换图片 我们先看下面这个例子: 使用input里面的button按钮, 并且利用函数, 将一个搜狗logo转换为百度logo: <!DOCTYPE html> <html lang"en"> <head><meta charset&…