移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.红黑树

news2024/9/28 20:15:18

1.红黑树的概念

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

 

 2.红黑树的性质!!!!!

 1. 每个结点不是红色就是黑色

2. 根节点是黑色的 

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

3.红黑树节点的定义 

enum color
{
	RED,
	BLACK
};  //列举color的各种可能情况

template<class K, class V>
struct RBTtreenode
{
	RBTtreenode<K, V>* _left;
	RBTtreenode<K, V>* _right;
	RBTtreenode<K, V>* _parent;

	pair<K, V> kv;
	color col;


	RBTtreenode(const pair<K, V>& _kv)
		:_left(nullptr)     //左孩子
		, _right(nullptr)   //右孩子
		, _parent(nullptr)  //父亲
		, kv(_kv)
		, col(RED)
	{}
};

 4.红黑树结构

 为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了 与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft 域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:

5.红黑树的插入!!!!

 红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

5.1按照二叉搜索的树规则插入新节点

 

if (root == nullptr)
{
	root = new node(_kv);
	root->col = BLACK;//规定根必须是黑的
	return true;
}
node* parent = nullptr; //比bst多了一个parent
node* cur = root;       

while (cur)
{
	parent = cur;
	if (cur->kv.first < _kv.first)
	{
		cur = cur->_right;
	}
	else if (cur->kv.first > _kv.first)
	{
		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;

5.2 检测新节点插入后,红黑树的性质是否造到破坏

 因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何 性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:

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

 

1. 情况一: cur为红,p为红,g为黑,u存在且为红

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

2.情况二(单旋+变色): cur为红,p为红,g为黑,u不存在/u存在且为黑  (左左和右右)

细分就是:(1)g->left==p,p->left==cur;左左

(2)g->right==p,p->right==cur;右右

 p为g的左孩子,cur为p的左孩子,则进行右单旋转;

相反, p为g的右孩子,cur为p的右孩子,则进行左单旋转

p、g变色--p变黑,g变红

3.情况三(双旋+变色): cur为红,p为红,g为黑,u不存在/u存在且为黑  (左右和右左)

 

 

细分就是:(1)g->left==p,p->right==cur;左右

(2)g->right==p,p->left==cur;右左

 p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;

相反, p为g的右孩子,cur为p的左孩子,则针对p做右单旋转 

则转换成了情况2!!!!!,然后再用情况2的旋转处理一下就行了

 针对每种情况进行相应的处理即可。

while (parent&&parent->col == RED)//parent为黑不需要调整,如果cur变成root,parent就不存在退出循环
{
	node* grandparent = parent->_parent;//祖父一定存在,因为只有根节点是没有祖父的,而根节点一定是黑色的
	if (parent==grandparent->_left)
	{
		//      g
		//    p   u
		node* uncle = grandparent->_right;  //父亲在左则叔叔在右
		if (uncle && uncle->col == RED)     //情况一.如果叔叔存在且为红色
		{
			//变色
			parent->col = uncle->col = BLACK;
			grandparent->col = RED;
			//重置cur,parent,继续向上处理
			cur = grandparent;//变为祖父
			parent = cur->_parent;
		}
		else //叔叔不存在或为黑色,旋转加变色
		{
			//   g
			//  p
			// c

			if (cur == parent->_left)  //情况二.单旋
			{

				rotateR(grandparent);
				parent->col = BLACK;
				grandparent->col = RED;
			}

			//   g
			//  p
			//   c

			else      //情况三.cur==parent->_right,双旋
			{
				rotateL(parent);//经历一次左旋后变成情况二!!!!!!!!!!!(cur和parent换位置)
				rotateR(grandparent);
				cur->col = BLACK;
				grandparent->col = RED;
			}

			break;//调整一次就结束了,所以经历过旋转后不需要重置cur,parent,grandparent
		}
	}
	else
	{
		//      g
		//    u   p
		//
		node* uncle = grandparent->_left;  //父亲在右则叔叔在左
		if (uncle && uncle->col == RED)
		{
			parent->col = uncle->col = BLACK;
			grandparent->col = RED;
			//
			cur = grandparent;
			parent = cur->_parent;
		}
		else
		{
			//    g
			//  u   p
			//        c
			if (cur == parent->_right)
			{
				rotateL(grandparent);
				parent->col = BLACK;
				grandparent->col = RED;
			}
			else
			{
				//   g
				// u   p
				//    c
				rotateR(parent);
				rotateL(grandparent);
				cur->col = BLACK;
				grandparent->col = RED;

			}
			break;//调整一次就结束了,所以经历过旋转后不需要重置cur,parent,grandparent
		}
	}

6.红黑树的验证 

红黑树的检测分为两步:

1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

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

 1.中序输出

void inorder()
{
	_inorder(root);
}

void _inorder(node* root)
{
	if (root == nullptr)
		return;
	_inorder(root->_left);
	cout << root->kv.first << " ";
	_inorder(root->_right);
}

2.判断性质 (性质3和性质4)

bool check(node* it,int blacknum,int flag)
{
	if (it == nullptr)
	{
		if (blacknum == flag)
			return true;
		else
			return false;
	}
	else if (it->col == RED && it->_parent->col == RED)//十分巧妙,因为孩子的情况有很多,但父亲不是红就是黑,所以判断父亲更合适
		return false;
	else if (it->col == BLACK)
		blacknum++;
	return check(it->_left,blacknum,flag) && check(it->_right,blacknum,flag);
}



bool isbalance()
{
	return _isbalance(root);
}

bool _isbalance(node* root)
{
	if (root == nullptr)
		return true;
	else if (root->col == RED)
		return false;

	int blacknum = 0;
	int flag = 0;
	node* k = root;
	while (k)
	{
		if (k->col == BLACK)
			flag++;
		k = k->_left;//这里十分巧妙,因为如果为红黑树,从某一节点到空的所有路径上的黑节点数量是一致的,所以可以先随便选一条路径,算出这一条路径上的黑节点数作为基准值,在由递归去和其他路径比较
	}
	return check(root,blacknum,flag);
}

7.红黑树的删除

 可参考:《算法导论》或者《STL源码剖析》

红黑树 - _Never_ - 博客园

8 红黑树与AVL树的比较

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

 9 红黑树的应用

1. C++ STL库 -- map/set

2. Java 库

3. linux内核

4. 其他一些库

10.代码全览

rbt.h:

enum color
{
	RED,
	BLACK
};  //列举color的各种可能情况

template<class K, class V>
struct RBTtreenode
{
	RBTtreenode<K, V>* _left;
	RBTtreenode<K, V>* _right;
	RBTtreenode<K, V>* _parent;

	pair<K, V> kv;
	color col;


	RBTtreenode(const pair<K, V>& _kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, kv(_kv)
		, col(RED)
	{}
};



template<class K, class V>
class RBTtree
{
public:
	typedef RBTtreenode<K, V> node;

	bool insert(const pair<K, V>& _kv)
	{
		if (root == nullptr)
		{
			root = new node(_kv);
			root->col = BLACK;//规定根必须是黑的
			return true;
		}
		node* parent = nullptr; //比bst多了一个parent
		node* cur = root;       

		while (cur)
		{
			parent = cur;
			if (cur->kv.first < _kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->kv.first > _kv.first)
			{
				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)//parent为黑不需要调整,如果cur变成root,parent就不存在退出循环
		{
			node* grandparent = parent->_parent;//祖父一定存在,因为只有根节点是没有祖父的,而根节点一定是黑色的
			if (parent==grandparent->_left)
			{
				//      g
				//    p   u
				node* uncle = grandparent->_right;  //父亲在左则叔叔在右
				if (uncle && uncle->col == RED)     //情况一.如果叔叔存在且为红色
				{
					//变色
					parent->col = uncle->col = BLACK;
					grandparent->col = RED;
					//重置cur,parent,继续向上处理
					cur = grandparent;//变为祖父
					parent = cur->_parent;
				}
				else //叔叔不存在或为黑色,旋转加变色
				{
					//   g
					//  p
					// c

					if (cur == parent->_left)  //情况二.单旋
					{

						rotateR(grandparent);
						parent->col = BLACK;
						grandparent->col = RED;
					}

					//   g
					//  p
					//   c

					else      //情况三.cur==parent->_right,双旋
					{
						rotateL(parent);//经历一次左旋后变成情况二!!!!!!!!!!!(cur和parent换位置)
						rotateR(grandparent);
						cur->col = BLACK;
						grandparent->col = RED;
					}

					break;//调整一次就结束了,所以经历过旋转后不需要重置cur,parent,grandparent
				}
			}
			else
			{
				//      g
				//    u   p
				//
				node* uncle = grandparent->_left;  //父亲在右则叔叔在左
				if (uncle && uncle->col == RED)
				{
					parent->col = uncle->col = BLACK;
					grandparent->col = RED;
					//
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right)
					{
						rotateL(grandparent);
						parent->col = BLACK;
						grandparent->col = RED;
					}
					else
					{
						//   g
						// u   p
						//    c
						rotateR(parent);
						rotateL(grandparent);
						cur->col = BLACK;
						grandparent->col = RED;

					}
					break;//调整一次就结束了,所以经历过旋转后不需要重置cur,parent,grandparent
				}
			}
		}


		//1.如果parent和uncle都为RED,则可以一起变黑
		// 2.parent为黑不处理
		// 3.uncle为黑或不存在,parent为红,旋转+变色


		root->col = BLACK;//最后以防万一让根变为黑
		return true;
	}

	void rotateL(node* parent)//左旋,(新节点插入到较高右子树的右侧)//   1.右右
	{
		node* subr = parent->_right;
		node* subrl = subr->_left;

		parent->_right = subrl;
		subr->_left = parent;

		node* ppnode = parent->_parent;
		parent->_parent = subr;

		if (subrl) //subrl可能为空!!!!!!!
		{
			subrl->_parent = parent;
		}

		if (parent == root) //即如果parent->_parent==nullptr
		{
			root = subr;
			subr->_parent = nullptr;
		}

		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subr;
			}
			else if (ppnode->_right == parent)
			{
				ppnode->_right = subr;
			}

			subr->_parent = ppnode;
		}
	}


	void rotateR(node* parent)//右旋,(新节点插入到较高左子树的左侧)//   2.左左
	{

		node* subl = parent->_left;
		node* sublr = subl->_right;
		parent->_left = sublr;


		if (sublr)               //sublr可能为空!!!!!!!
			sublr->_parent = parent;

		node* ppnode = parent->_parent;

		subl->_right = parent;
		parent->_parent = subl;

		if (root == parent)
		{
			root = subl;
			subl->_parent = nullptr;
		}

		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subl;
			}
			else if (ppnode->_right == parent)
			{
				ppnode->_right = subl;
			}

			subl->_parent = ppnode;
		}

	}



	void inorder()
	{
		_inorder(root);
	}

	void _inorder(node* root)
	{
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << root->kv.first << " ";
		_inorder(root->_right);
	}


	bool check(node* it,int blacknum,int flag)
	{
		if (it == nullptr)
		{
			if (blacknum == flag)
				return true;
			else
				return false;
		}
		else if (it->col == RED && it->_parent->col == RED)//十分巧妙,因为孩子的情况有很多,但父亲不是红就是黑,所以判断父亲更合适
			return false;
		else if (it->col == BLACK)
			blacknum++;
		return check(it->_left,blacknum,flag) && check(it->_right,blacknum,flag);
	}



	bool isbalance()
	{
		return _isbalance(root);
	}

	bool _isbalance(node* root)
	{
		if (root == nullptr)
			return true;
		else if (root->col == RED)
			return false;

		int blacknum = 0;
		int flag = 0;
		node* k = root;
		while (k)
		{
			if (k->col == BLACK)
				flag++;
			k = k->_left;//这里十分巧妙,因为如果为红黑树,从某一节点到空的所有路径上的黑节点数量是一致的,所以可以先随便选一条路径,算出这一条路径上的黑节点数作为基准值,在由递归去和其他路径比较
		}
		return check(root,blacknum,flag);
	}


private:
	node* root = nullptr;
};

test.cpp:

#include<iostream>
using namespace std;

#include"RBT.h"

int main()
{
	int arr[] = { 790,760,969,270,31,424,377,24,702 };
	//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTtree<int, int> it;
	for (auto i : arr)
	{
		it.insert(make_pair(i, i));
	}
	it.inorder();
	cout << endl << it.isbalance() << endl;

	return 0;
}

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

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

相关文章

产品包装检测系统源码分享

货架产品包装检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

Java文件I/O处理之RandomAccessFile【随意存取文件】

Java语言有一个处理文件输入输出的RandomAccessFile类&#xff0c;既可以读取文件内容&#xff0c;也可以向文件输出数据。 RandomAccessFile类在国内的技术文档和书籍中都翻译为“随机访问文件”类&#xff0c;确实令人不解。 在中文中“随机”的意思&#xff1a; 不设任何条…

流量新密码?AI宠物定制写真在小红书爆火,有人搞了10W+

大家好&#xff0c;我是灵魂画师向阳 不知道大家发现没&#xff1f;消费者对于情感价值的需求猛增&#xff0c;宠物服务行业衍生出越来越多的“拟人化”新业态。 宠物摄影和写真成为宠物经济中的新兴行业&#xff0c;吸引了越来越多的摄影师和养宠人的关注。 一些摄影师和摄…

《开题报告》基于SSM框架的电影评论网站的设计与实现源码++学习文档+答辩讲解视频

开题报告 研究背景 随着互联网技术的飞速发展&#xff0c;网络已成为人们获取信息、交流思想、分享体验的重要平台。在电影产业蓬勃发展的今天&#xff0c;观众对于电影的选择不再仅仅依赖于传统的宣传渠道&#xff0c;而是更加倾向于通过在线平台了解影片内容、观看预告片、…

C++基础知识十

1.string尾插 在一个string类型的字符后面插入其它的字符可以用push_back和append&#xff0c;这俩个是常用的。 #include<iostream> using namespace std; int main() {string s1("hello");s1.push_back( );s1.push_back(w);s1.push_back(o);s1.push_back(r…

实施自动化测试的五个条件

摘要&#xff1a; 谈到什么是组成一次自动化测试的“恰当实施”经常会关注你需要用的工具&#xff0c;但是那仅仅是等式的一部分。巴斯 迪杰斯特拉详细说明了你需要考虑的其他四件事&#xff0c;他们如何致力于你的自动化测试的成功&#xff0c;以及关联到不能适当关注它们中任…

如何存储你需要的信息:变量——WEB开发系列42

变量是一种用于存储数据的基本工具。它允许我们在代码中给某个数据赋予一个名字&#xff0c;便于在后续的代码中引用、更新、甚至重新赋值。 一、什么是变量&#xff1f; 变量本质上是一个“容器”&#xff0c;它存储特定的信息或数据。你可以把变量想象成一个标签&#xff0c…

巧用switch-case消除条件判断

shigen坚持更新文章的博客写手&#xff0c;记录成长&#xff0c;分享认知&#xff0c;留住感动。个人IP&#xff1a;shigen 在之前的文章中&#xff0c;我们有提交消除if-else代码的方法&#xff1a; 结合HashMap与Java 8的Function和Optional消除ifelse判断巧用枚举消除逻辑判…

工控主板在工业控制中扮演什么角色

工控主板在工业控制中扮演着至关重要的角色&#xff0c;它是工业控制系统的核心组件&#xff0c;负责连接、控制和管理各种工业设备&#xff0c;实现自动化生产和智能化管理。具体来说&#xff0c;工控主板在工业控制中的作用可以归纳为以下几个方面&#xff1a; 一、核心控制…

MNIST手写数字数据集

数据集 官网链接失效&#xff0c;我找到数据集后&#xff0c;上传到码云&#xff0c;并在这里分享。 打开链接&#xff0c;进入如下目录&#xff0c;即可找到如下八个文件&#xff1a; 下面是一些可有可无的介绍。 Mnist数据集介绍 Mnist数据集包含70000张手写数字图片&#x…

IO(Reader/Writer)

1.Reader a.简介 i.是Java的IO库提供的另一种输入流。和InputStream的区别是&#xff1a;InputStream是字节流&#xff0c;以byte为单位&#xff0c;Reader是字符流&#xff0c;以char为单位。 ii.java.io.Reader是所有字符输入流的超类。 b.FileReader i.FileReader默认的编…

【Kubernetes】常见面试题汇总(四十三)

目录 98. kube-apiserver 和 kube-scheduler 的作用是什么&#xff1f; 99.您对云控制器管理器了解多少&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;…

甘肃辣椒油:舌尖上的热辣诱惑

&#x1f4a5;宝子们&#xff0c;今天必须要给你们安利甘肃食家巷的辣椒油&#x1f336;️&#xff01;✨甘肃辣椒油&#xff0c;那可是有着独特魅力的美食瑰宝&#x1f60d;。它以其鲜艳的色泽、浓郁的香气和醇厚的辣味&#xff0c;瞬间点燃你的味蕾&#x1f525;。&#x1f3…

关于聚类算法的一份介绍

在这篇文章中我将介绍无监督算法中“聚类”部分的知识&#xff0c;其中关于K均值聚类、层次聚类、密度聚类部分我将各附上一份实际运用的代码&#xff0c;而其余的像学习向量量化、高斯混合聚类部分则只是简单介绍下概念。 一、 关于聚类 首先我先简单介绍下聚类算法有关的东…

2024年7月大众点评温州美食店铺基础信息

在做一些城市分析、学术研究分析、商业选址、商业布局分析等数据分析挖掘时&#xff0c;大众点评的数据参考价值非常大&#xff0c;截至2024年7月&#xff0c;大众点评美食店铺剔除了暂停营业、停止营业后的最新数据情况分析如下。 温州餐饮美食店铺约6.5万家&#xff0c;有均…

CTF-SSH私钥泄露

CTF-SSH私钥泄露 一.信息探测--查看开放的服务--分析探测结果-- 探测大端口的信息 深入挖掘ssh信息![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6baf0b5de72d537c7093d3e2394d93cd.png#pic_center)解密ssh秘钥信息 工具&#xff1a;kali Linux 一.信息探测…

IO相关知识(Filter/序列化)

1.Filter模式 a.简介 i.直接使用继承&#xff0c;为各种InputStream附加更多功能&#xff0c;根本无法控制代码的复杂度&#xff0c;很快就会失控。 ii.为了解决依赖继承会导致子类数量失控的问题&#xff0c;JDK首先将InputStream分为两大类&#xff1a; …

SpringBoot+Activiti7工作流入门实例

目录 文章目录 目录准备Activiti建模工具1、BPMN-js在线设计器1.1 安装1.2 使用说明1.3运行截图 2、IDEA安装Activiti Designer插件2.1安装插件2.2 设置编码格式防止中文乱码2.3 截图 简单工作流入门实例1. 新建Spring Boot工程2. 引入Activiti相关依赖添加版本属性指定仓库添加…

10.1软件工程知识详解上

软件工程概述 软件开发生命周期 软件定义时期&#xff1a;包括可行性研究和详细需求分析过程&#xff0c;任务是确定软件开发工程必须完成的总目标&#xff0c;具体可分成问题定义、可行性研究、需求分析等。软件开发时期&#xff1a;就是软件的设计与实现&#xff0c;可分成…

一文上手SpringSecuirty【六】

自定义认证流程完成之后,前端收到了后端生成的token,那么在之后的所有请求当前,都必须携带token.作为服务器来说,得验证这个token,是否合法. 一、验证token是否合法 1.1 OncePerRequestFilter过滤器 OncePerRequestFilter是 Spring 框架中的一个过滤器&#xff0c;用于确保在…