首页 > 其他分享 >红黑树(STL-ordered_map)

红黑树(STL-ordered_map)

时间:2024-03-20 17:34:03浏览次数:24  
标签:Node map 结点 right ordered cur parent STL left

目录

一、概念:

 二、红黑树的结点

三、红黑树的插入

四、迭代器 (核心:结点指针)

五、应用 


一、概念:

        为了保持AVL树的平衡性,AVL树需要频繁地调整全树整体拓扑结构,代价较大。为此在AVL树的平衡标准上进一步放宽条件,引入红黑树的概念:

  1. 每个结点都是黑色或者红色
  2. 根结点都是黑色
  3. 每个叶子结点(虚构的外部节点,NULL结点)都是黑色
  4. 不存在两个相邻的红结点
  5. 对于每个结点,从该结点到任一叶节点(3中的叶子结点)的简单路径上,黑结点的个数相同

 结论1:从根到叶结点的最长路径不大于最短路径,由④和⑤可知最短路径全是黑结点,最长路径是黑红结点依次相交的路径

结论2:有n个内部结点的红黑树的高度为h<=log2(n+1),由①和③可知,任何一条简单路径上都至少由一半是黑结点,树的黑高为h/2,即树高为h时并且所有黑结点能组成的高为h/2的满二叉树时,黑结点个数最多,所以n>=2^(h/2)-1-->log2(n+1)

总结:由此可见红黑树的平衡条件比AVL树宽松不少,降低了树的动态调整的次数,如果一颗动态查找树,需要较多的插入和删除,选择红黑树比AVL树合适。

 二、红黑树的结点

template<class V>
class RBTreeNode {
public:

	RBTreeNode(const V& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}

	V _data;
	Colour _col;

	RBTreeNode<V>* _left;
	RBTreeNode<V>* _right;
	RBTreeNode<V>* _parent;
};

三、红黑树的插入

        首先,我们需要知道每个新插入的结点都是红色,否则若是黑色结点,该条路径会比其他路径多一个黑色结点,会破坏红黑树的规则。

        其插入过程如下:

  1. 第一步:通过比较查找合适的插入位置,然后建立新结点并且插入树中。让cur为插入结点指针,分四种情况。进入第二步:
  2. 第一种:插入为根节点,将颜色变为红色,否则判断插入结点的父节点的颜色
  3. 第二种:parent为黑色不用处理,直接return结束。
  4. 第三种:parent为红色,uncle(父节点的兄弟结点  )也为红色,那么将parent和uncle变为黑色,将grandparent(父节点的父节点)变为红色,parent变为RED,grandparent变为为BLACK,cur=grantparent,判断的结点向上递增两位。返回第二步判断后处理。
  5. 第四种: parent为红色,uncle不存在或者为黑色,根据cur和parent以及grantparent的位置,旋转后变色,旋转分为右单旋,左单旋,先右后左单选,先左后右单旋。

第二种情况如图:

右单旋:图中情况二 类似AVL树,复用旋转函数,但是少了平衡因子变化AVL树

//旋转加变色,然后return后结束插入
rotateR(ppNode);//ppNode是爷爷结点
parent->_col = BLACK;
ppNode->_col = RED;

右单旋:

void rotateR(Node* parent)
{
	Node* cur = parent->_left;
	Node* cur_right = cur->_right;

	parent->_left = cur_right;		//旋转第一步parent和cur_right的链接;
	if (cur->_right != nullptr)//X
		cur_right->_parent = parent;


	cur->_right = parent;		//再处理cur和parent的链接
	Node* parent_parent = parent->_parent;//保存结点,用于处理,cur->parent;
	parent->_parent = cur;

	if (parent == _root)		//处理cur和parent_parent的链接
	{
		_root = cur;
		cur->_parent = nullptr;//X
	}
	else {
		cur->_parent = parent_parent;
		if (parent_parent->_left == parent)
		{
			parent_parent->_left = cur;
		}
		else {
			parent_parent->_right = cur;
		}
	}
	//parent->_bf = 0;//旋转后平衡因子为0
	//cur->_bf = 0;
}

 先左后右双旋 :情况1

//双旋+变色
rotateLR(ppNode);
cur->_col = BLACK;
ppNode->_col = RED;

左右双旋函数:  类似AVL树,复用左单旋和右单旋函数,但是少了平衡因子变化AVL树

void rotateRL(Node* parent) {
	Node* cur = parent->_right;
	Node* cur_left = cur->_left;
	/*int bf = cur_left->_bf;*/

	rotateR(parent->_right);
	rotateL(parent);
}

两种对称情况: 左单旋和先右后左双旋函数

void rotateRL(Node* parent) {
	Node* cur = parent->_right;
	Node* cur_left = cur->_left;
	/*int bf = cur_left->_bf;*/

	rotateR(parent->_right);
	rotateL(parent);
}
void rotateL(Node* parent) {
	Node* cur = parent->_right;
	Node* cur_left = cur->_left;

	parent->_right = cur_left;
	if (cur_left != nullptr)
		cur_left->_parent = parent;


	cur->_left = parent;
	Node* ppNode = parent->_parent;
	parent->_parent = cur;

	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = cur;
		}
		else
		{
			ppNode->_right = cur;
		}
		cur->_parent = ppNode;
	}
	/*parent->_bf = 0;
	cur->_bf = 0;*/
}

四、迭代器 (核心:结点指针)

iterator++:iterator++的所指结点,为其右子树的最大值(最左结点),无右子树时,为某一祖先,即从下向上遍历,第一个祖先的左孩子为该iterator所指结点的祖先。

self& operator++() {

	if (_node->_right) {
		//右子树最小结点
		Node* subLeft= _node->_right;
		while (subLeft->_left) {
			subLeft = subLeft->_left;
		}
		
		_node = subLeft;
	}
	else{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent) {
			if (parent->_left == cur) {
				_node = parent;
				break;
			}
			else {
				cur = parent;
				parent = parent->_parent;
			}
		}
		if (parent == nullptr) {
			_node = _node->_right;
		}
	}
	return *this;
}

iterator--:同理

self& operator--() {
	if (_node->_left) {
		//左子树最大结点
		Node* maxLeft = _node->_left;
		while (maxLeft->_right) {
			maxLeft = maxLeft->_right;
		}

		_node = maxLeft;
	}
	else {
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent) {	//找到祖先的右孩子是该结点的祖先,就是前一个结点
			if (parent->_right == cur) {
				_node = parent;
				break;
			}
			else {
				cur = parent;
				parent = parent->_parent;
			}
		}
		if (parent == nullptr) {//除了存在的结点,其他任何指向都为空,多此一举
			_node = _node->_left;
		}
	}
	return *this;
}

五、应用 

map和set

标签:Node,map,结点,right,ordered,cur,parent,STL,left
From: https://blog.csdn.net/weixin_50470247/article/details/136505675

相关文章

  • Elasticsearch-Mapping映射
    Mapping映射自动或手动为index中的_doc建立一种数据结构和相关配置动态映射:dynamicmapping,自动为我们建立index,以及对应的mapping,mapping中包含了每个field对应的数据类型,以及如何分词等设置。PUT/web_site/_doc/1{"post_date":"2023-01-01","title":"Thelonger",......
  • nmap 参数详解。
    #读取文件扫描,一行一个,可以是主机名或者网段。nmap-iLtarget.txt#随机选择5个目标进行扫描。模拟对网络中随机主机的扫描,以便评估网络安全性。nmap-iR5#排除在扫描范围之外的主机或网络。nmap192.168.1.0/24--exclude192.168.1.1nmap192.168.1.0/24--exclude19......
  • 【算法训练营】STL算法 Stack 栈的压入、弹出序列+最小栈
    Stack刷题1.最小栈2.栈的压入、弹出序列1.最小栈题目链接:最小栈题目描述解决思路创建一个辅助栈只保存最小的元素代码classMinStack{public:MinStack(){}voidpush(intval){//只要是压栈,先将元素保存到_elem中......
  • [Java基础学习][集合]java常见集合:Java中集合框架提供了大量的集合类:常见的list、set
    总结与区别:Set:去重;      set去重本质:equals+hashcode;    常见的HashSet、TreeSet。    HashSet基于哈希表实现,插入、删除、查找。不保证顺序    TreeSet基于红黑树实现,保证顺序,查找较快;treeSet:排序继承comparable接口进行比较排序   Se......
  • flutter中Map<String, dynamic>与Map<String, String>的区别
    在Flutter中,Map<String,dynamic>和Map<String,String>都是Map类型的数据结构,但它们之间有一些重要的区别: 1.Map<String,dynamic>:这种Map的值可以是任何类型,包括基本数据类型(如int,double,String等),List,Map以及自定义对象。使用dynamic类型会导致更灵活的数据处理,但在编码时......
  • leaflet频繁切换mapbox矢量图层-短暂空白问题
    leaflet加载mapbox矢量图层-最佳方案推荐闪烁问题比如现在有卫星图和mapboxgl矢量图层,两者有时常常需要切换,但在切换回矢量图层时,会出先短暂的空白问题(就是初始化图层),那有什么办法,可以实现平滑过渡切换呢解决思路大概讲一下思路,就是在切换卫星图时,矢量图层不要立刻移除,通过......
  • MapReduce执行流程
    MapReduce执行流程MapTask执行流程Read:读取阶段MapTask会调用InputFormat中的getSplits方法来对文件进行切片切片之后,针对每一个Split,产生一个RecordReader流用于读取数据数据是以Key-Value形式来产生,交给map方法来处理。每一个键值对触发调用一次map方法Map:映射阶......
  • AOSP平台编写Android-ebpf程序(tracepoint)的一些map定义和使用问题,导致map和prog无法
     前言本片文章并不主要讲解在AOSP平台ebpf程序的整个编写流程,只是一些的map的定义使用问题,如有需要可查看,aosp平台的整个下载流程,以及简单的程序的编译和如何push到手机运行,这位up是我在ebpf领域探索的领路人,本站ID:LiujiaHuan13,如果有需要up本人后面会考虑写一篇aosp程序书写......
  • MyBatis3源码深度解析(十六)SqlSession的创建与执行(三)Mapper方法的调用过程
    文章目录前言5.9Mapper方法的调用过程5.10小结前言上一节【MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取】已经知道,调用SqlSession对象的getMapper(Class)方法,传入指定的Mapper接口对应的Class对象,即可获得一个动态......
  • 从 Linux 内核角度探秘 JDK MappedByteBuffer
    本文涉及到的内核源码版本为:5.4,JVM源码为:OpenJDK17,RocketMQ源码版本为:5.1.1在之前的文章《一步一图带你深入剖析JDKNIOByteBuffer在不同字节序下的设计与实现》中,笔者为大家详细剖析了JDKBuffer的整个设计体系,从总体上来讲,JDKNIO为每一种Java基本类型定义了对......