[数据结构]红黑树之插入操作(RBTree)

news2024/9/20 22:32:09

       这里只着重介绍插入操作的实现:)

一、红黑树的概念和性质

        红黑树(Red Black Tree)是一种自平衡的二叉搜索树。红黑树最初在1972年由Rudolf Bayer发明,当时被称为平衡二叉B树(symmetric binary B-trees)。随后,在1978年,Leo J. Guibas和Robert Sedgewick对其进行了修改,并正式命名为红黑树。

具有以下性质的二叉搜索树是红黑树:

1、所有结点要么是红色,要么是黑色

2、根结点永远是黑色的。

3、如果一个内部结点是红色的,那么它的两个子结点都必须是黑色的。

4、所有扩充的外部结点(空结点)都是黑色的。

5、所有叶子结点到根结点的路径都包含相同数目的黑色结点。

  一棵红黑树

        黑高度:从红黑树任意结点x出发(不包括结点x),到达一个外部结点(nil)的任一路径上黑结点的个数叫做结点x的黑高度(bh),也称为结点的阶(rank)。  一棵红黑树的黑高度指的是该树根节点的黑高度。上图和下图的红黑树黑高度都为2。

        为什么红黑树具有以上5个性质就可以让二叉搜索树自平衡呢?下面我们来看这张图:

        我把指向红色结点的指针视为有颜色的红色指针而指向黑色结点的指针视为有颜色的黑色指针来分析,例如上图中,50->20->10->nil的路径有3条指针,这条路径的叶子结点的高度为3

        现在假设一棵任意的红黑树,其黑高度为r。根据红黑树的所有叶子结点到根结点的路径都包含相同数目的黑色结点的性质,得出每条从根结点出发到nil路径上的黑色指针数目相同如果一条路径上的指针都是黑色,那么该路径肯定是红黑树里所能达到的最短路径,该条路径的指针数目为r,根结点到叶子结点高度为r。如果一条路径上的结点都是黑--黑-相间的指针,那么该路径肯定是红黑树里能达到最长的路径,该条路径的指针数目为2r,根结点到叶子结点高度为2r。

        所以可以知道从根结点位置出发,到外部结点nil的任意一条路径上的指针数目的范围是[r,2r]。设P,Q为红黑树中任意两条从根节点到外部结点nil的路径上的指针数目 ,P路径上的指针个数取值为[r,2r],Q路径上的指针个数取值也为[r,2r],那么总有P ≤ 2Q (或 Q ≤ 2P)

        PQ是任意两条路径,假设P就是红黑树中的最长路径,Q是红黑树中的最短路径,那么根据P ≤ 2Q就能得出结论:红黑树中的最长路径不超过最短路径的两倍,即红黑树的高度差不会超过r,这能保证红黑树永远保持近似平衡,这就是红黑树能够自平衡的关键。

二、红黑树的插入操作

        红黑树在插入一个结点时要不破坏红黑树的规则,根节点必须是黑色的,并且每条路径上的黑色结点个数都要相同,不能有相连的红色结点。所以我们在插入一个新的结点时都会选择插入红色的结点,因为这样不会影响这条路径上黑色结点的数目,但是新插入结点的双亲结点有可能也为红色,这就违反了红黑树不能有两个相连的红色结点的性质,这个时候就需要我们根据实际情况来调整红黑树的结构了。

下面出现的英文分别代表着对应的结点:

cur (c)—— 代表当前的红色结点

parent(p) —— cur的双亲结点

grand (g)—— parent的双亲结点

uncle (u)—— parent的兄弟结点

抽象结构:

        g

   p        u

c

(一)简单情况

情况一:插入结点前树为空,则新插入的结点颜色置为黑,并将其作为根节点。

情况二:在向上调整的过程中,cur结点的双亲结点parent颜色为黑,此时没有违反红黑树的性质,不需要处理,结束向上调整。

        下面的抽象图代表一颗完整的树或者一颗子树,圆形代表具体的结点,矩形代表结点的子树,如果cur为新插入的结点,那么子树(abcde)为空。

(二)只需变色的情况

        parent和uncle都为红色,cur为parent的左孩子或为右孩子。

        此时cur和parent违反红黑树不能有连续红色结点的性质,需要想办法将parent变黑。

        这种情况我们只需将parent和uncle改为黑色,grand改为红色即可。

        修改后以grand为根的这棵子树不再违反红黑树的性质,但是grand的颜色变红了,如果grand双亲节点不为空,则还需要向上考察grand和双亲结点的颜色是否也为红色,否则还需要进行调整。

       grand和双亲结点为红色,还需要进行继续向上调整:

        需要注意的是,调整完后,如果grand就为整个二叉搜索树的,则不继续再向上调整,但是结果根结点变为了红色,根据红黑树的性质,根是不能为红色的,为了保证根节点一定是黑色的,我们在每次进行插入操作后,将根节点的颜色置为黑色。

(三)旋转加变色的情况 

         cur和parent为红,uncle结点不存在或uncle颜色为黑色。

1、如果cur为parent的左孩子

        这种情况需要进行一个右单旋,以grand为轴,parent顺时针旋转,旋转后将grand和parent的颜色互换。

        经过旋转变色后,从前的子树变成了以parent为根的子树,该子树满足红黑树的性质,因为parent为黑,不再需要进行向上调整了,就算parent的双亲结点为红也无伤大雅。

2、如果cur为parent的右孩子

        这个时候我们需要进行左右双旋加变色,先以parent为轴,cur逆时针旋转后,再以grand为轴,cur顺时针旋转。然后交换cur和grand的颜色。

        经过旋转变色后,从前的子树变成了以cur为根的子树,该子树满足红黑树的性质,因为cur为黑,不再需要进行向上调整了,就算cur现在的双亲结点为红也无伤大雅。

(四)对称情况的处理

        parent和uncle都为红色。 cur为parent的左孩子或为右孩子。

        

         cur和parent为红,uncle结点不存在或uncle颜色为黑色。

1、如果cur为parent的右孩子

左单旋和变颜色

 2、如果cur为parent的左孩子

右左双旋和变颜色

三、红黑树插入的代码和测试代码

RBTree.h

#pragma once
#include <iostream>
using namespace std;

//红黑树结点定义
template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode* _pLeft;	//指向左孩子
	RBTreeNode* _pRight;	//指向右孩子
	RBTreeNode* _pParent;	//指向双亲结点
	bool _isRed;	//为false代表该结点为红,true代表该结点为黑

	RBTreeNode(const T& data = T()) :
		_data(data),
		_pLeft(nullptr), _pRight(nullptr), _pParent(nullptr),
		_isRed(true)
	{}
};

template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree():_root(nullptr){}

	bool insert(const T& data)
	{
		Node* pPos = nullptr;	//pPos记录新插入结点的双亲节点的位置
		Node* pCur = _root;

		while (pCur)
		{
			pPos = pCur;
			if (data > pCur->_data)
				pCur = pCur->_pRight;
			else if (data < pCur->_data)
				pCur = pCur->_pLeft;
			else
				return false;
		}

		//两种简单的情况之一:插入的结点作为根节点插入
		if (pPos == nullptr)
		{
			//新插入的结点作为根结点
			_root = new Node(data);
			_root->_isRed = false;
			return true;
		}


		//插入新的结点
		pCur = new Node(data);
		if (data > pPos->_data)
			pPos->_pRight = pCur;
		else
			pPos->_pLeft = pCur;

		pCur->_pParent = pPos;

		//向上调整
		Node* pParent = pPos;
		while (pParent && pParent->_isRed)	//当parent为黑或者调整到根节点为止结束
		{
			Node* pGrand = pParent->_pParent;
			if (pParent == pGrand->_pLeft)	//其中的一种对称情况
			{
				//		g
				//   p	   u
				//c
				Node* pUncle = pGrand->_pRight;
				if (pUncle && pUncle->_isRed)	//只需变色的情况:parent和uncle都为红色。 cur为parent的左孩子或为右孩子。
				{
					
					//这种情况我们只需将parent和uncle改为黑色,grand改为红色即可。
					pParent->_isRed = pUncle->_isRed = false;
					pGrand->_isRed = true;

					//继续向上调整
					pCur = pGrand;
					pParent = pGrand->_pParent;
				}
				else  //需要旋转+变色的情况:cur和parent为红,uncle结点不存在或uncle颜色为黑色。
				{
					
					if (pCur == pParent->_pLeft)	//右单旋
					{
						//这种情况需要进行一个右单旋,旋转后将parent和grand的颜色互换。
						//		g
						//   p	   u
						//c
						rotateR(pGrand);
						pParent->_isRed = false;
						pGrand->_isRed = true;
						break;	//结束向上调整
					}
					else   //左右双旋
					{
						//这个时候我们需要进行左右双旋加变色,然后交换cur和grand的颜色。
						//		g
						//   p	   u
						//      c
						rotateL(pParent);
						rotateR(pGrand);
						pCur->_isRed = false;
						pGrand->_isRed = true;
						break;	//结束向上调整
					}
				}
			}
			else  //对称的情况
			{
				//		g
				//   u	   p
				//            c
				Node* pUncle = pGrand->_pLeft;
				if (pUncle && pUncle->_isRed)	//只需变色的情况:parent和uncle都为红色。 cur为parent的左孩子或为右孩子。
				{
					//将parent和uncle变为红色
					pUncle->_isRed = pParent->_isRed = false;
					pGrand->_isRed = true;

					//继续向上调整
					pCur = pGrand;
					pParent = pGrand->_pParent;
				}
				else  //需要旋转加变色的情况
				{
					if (pCur == pParent->_pRight)  //cur为parent的右孩子,左单旋
					{
						//		g
						//   u	   p
						//            c
						rotateL(pGrand);
						pParent->_isRed = false;
						pGrand->_isRed = true;
						break;	//结束向上调整
					}
					else  //右左单旋
					{
						//		g
						//   u	   p
						//      c
						rotateR(pParent);
						rotateL(pGrand);
						pCur->_isRed = false;
						pGrand->_isRed = true;
						break;
					}
				}
			}
		}

		_root->_isRed = false;	//将根节点置为黑
		return true;
	}

	// 查找
	Node* find(const T& data)
	{
		Node* pCur = _root;	

		while (pCur)
		{
			if (data > pCur->_data)
				pCur = pCur->_pRight;
			else if (data < pCur->_data)
				pCur = pCur->_pLeft;
			else
				return pCur;
		}

		return nullptr;
	}

	// 检测红黑树是否为有效的红黑树
	bool checkRBTree()
	{
		int pathBlack = 0;
		Node* pCur = _root;

		while (pCur)	//找最左侧那条路径的黑色结点总数作为基准值
		{
			if (pCur->_isRed == false)
				pathBlack++;

			pCur = pCur->_pLeft;
		}

		return _checkRBTree(_root, 0, pathBlack);
	}

	//计算树的高度
	int height()
	{
		return _height(_root);
	}

	//计算结点个数
	int size()
	{
		return _size(_root);
	}

	//中序遍历并打印结点数据
	void inOrder()
	{
		_inOrder(_root);
	}
private:
	//blackCount是根节点到该结点路径上的所有黑色结点的数量,pathBlack是基准值
	bool _checkRBTree(Node* pRoot, size_t blackCount, size_t pathBlack)
	{
		if (pRoot == nullptr)	//到达外部结点nil
		{
			if (blackCount == pathBlack)//检查该路径上的黑色结点数量和基准值是否一样
				return true;
			else
			{
				cout << "data:" << pRoot->_data;
				cout << " blackCount = " << blackCount << "and pathBlack = " << pathBlack << endl;
				return false;

			}
		}

		if (pRoot->_isRed && pRoot->_pParent->_isRed)	//如果该结点和其双亲结点都为红色
		{
			cout << "data:" << pRoot->_data;
			cout << " 有相邻的红色" << endl;
			return false;
		}


		if (pRoot->_isRed == false)	//统计到该路径为止黑色结点的数目
			blackCount++;

		return _checkRBTree(pRoot->_pLeft, blackCount, pathBlack) && _checkRBTree(pRoot->_pRight, blackCount, pathBlack);
	}


	// 左单旋
	void rotateL(Node* pParent)
	{
		//     grandparent
		//		 parent
		//				sub
		//			subL
		Node* pGrandParent = pParent->_pParent;
		Node* sub = pParent->_pRight;
		Node* subL = sub->_pLeft;

		pParent->_pRight = subL;
		if (subL)
			subL->_pParent = pParent;

		sub->_pLeft = pParent;
		pParent->_pParent = sub;

		if (pGrandParent == nullptr)
			_root = sub;
		else if (pGrandParent->_pLeft == pParent)
			pGrandParent->_pLeft = sub;
		else
			pGrandParent->_pRight = sub;

		sub->_pParent = pGrandParent;
	}

	// 右单旋
	void rotateR(Node* pParent)
	{
		//	   grandparent
		//		 parent
		//	 sub
		//		 subR
		Node* pGrandParent = pParent->_pParent;
		Node* sub = pParent->_pLeft;
		Node* subR = sub->_pRight;

		pParent->_pLeft = subR;
		if (subR)
			subR->_pParent = pParent;

		sub->_pRight = pParent;
		pParent->_pParent = sub;

		if (pGrandParent == nullptr)
			_root = sub;
		else if (pGrandParent->_pLeft == pParent)
			pGrandParent->_pLeft = sub;
		else
			pGrandParent->_pRight = sub;

		sub->_pParent = pGrandParent;
	}

	//中序遍历
	void _inOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_inOrder(root->_pLeft);
		cout << root->_data << " ";
		_inOrder(root->_pRight);
	}

	int _height(Node* root)//计算树的高度
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _height(root->_pLeft);
		int rightHeight = _height(root->_pRight);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int _size(Node* root)	//计算树的结点个数
	{
		if (root == nullptr)
			return 0;

		return _size(root->_pLeft) + _size(root->_pRight) + 1;
	}

private:
	Node* _root;
};

Main.c

#include "RBTree.h"

//压力测试
void performanceTest()
{
	const int N = 10000;
	RBTree<int> tree;
	clock_t start1 = clock();
	for (int i = 0; i < N; i++)
	{
		tree.insert(i);	//插入有序序列或随机值都行
	}

	cout << tree.checkRBTree() << endl;
	clock_t end1 = clock();

	cout << "插入用时:" << (float)(end1 - start1) / CLOCKS_PER_SEC << "秒" << endl;
	cout << "高度:" << tree.height() << endl;
	cout << "结点个数" << tree.size() << endl;

	int count = 0;
	start1 = clock();
	for (int i = 0; i < N; i++)
	{
		if (!tree.find(i))
			count++;
	}
	end1 = clock();

	cout << "查找所有值用时:" << (float)(end1 - start1) / CLOCKS_PER_SEC << "秒" << endl;
	cout << count << "个找不到!" << endl;

	//查找随机值
	count = 0;
	start1 = clock();
	for (int i = 0; i < N; i++)
	{
		if (!tree.find(rand()))
			count++;
	}
	end1 = clock();

	cout << "查找随机值用时:" << (float)(end1 - start1) / CLOCKS_PER_SEC << "秒" << endl;
	cout << count << "个找不到!" << endl;

	cout << tree.size() << endl;
}

int main()
{
	performanceTest();
	return 0;
}

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

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

相关文章

2024 年高教社杯全国大学生数学建模竞赛B题解题思路(第一版)

原文链接&#xff1a;https://www.cnblogs.com/qimoxuan/articles/18399372 赛题&#xff1a; 问题 1&#xff1a;抽样检测方案设计 分析&#xff1a; 抽样检测方案需要在保证决策准确性的同时&#xff0c;尽量减少检测成本。需要考虑抽样误差对决策的影响&#xff0c;以及如…

OCR经典神经网络(一)文本识别算法CRNN算法原理及其在icdar15数据集上的应用

OCR经典神经网络(一)文本识别算法CRNN算法原理及其在icdar15数据集上的应用 文本识别是OCR&#xff08;Optical Character Recognition&#xff09;的一个子任务&#xff0c;其任务为&#xff1a;识别一个固定区域的的文本内容。 在OCR的两阶段方法里&#xff0c;文本识别模型接…

若依框架登录鉴权详解(动态路由)

若依框架登录鉴权&#xff1a;1.获取token&#xff08;过期在响应拦截器中实现&#xff09;,2.基于RBAC模型获取用户、角色和权限信息&#xff08;在路由前置守卫&#xff09;&#xff0c;3.根据用户权限动态生成&#xff08;从字符串->组件&#xff0c;根据permission添加动…

linux搭建深度学习平台

linux搭建深度学习平台&#xff08;Ubuntu&#xff09; /home/guangyao/anaconda3 我服务器的anaconda地址 ~/anaconda3 1 首先就是打开浏览器&#xff0c;我实验室的是火狐&#xff0c;搜索anaconda下载&#xff0c;找到下载目录&#xff0c;cd进去&#xff0c; 2安装 bas…

【佳学基因检测】在bagisto中,grouped products(同组产品)和bundled products(打包产品)有什么不同?

【佳学基因检测】在bagisto中&#xff0c;grouped products&#xff08;同组产品&#xff09;和bundled products&#xff08;打包产品&#xff09;有什么不同&#xff1f; 在Bagisto电商平台中&#xff0c;**grouped products&#xff08;同组产品&#xff09;和bundled prod…

iceberg存储结构详解

iceberg底层组织方式 下图是Iceberg中表格式&#xff0c;s0、s1代表的是表Snapshot信息&#xff0c;每个表示当前操作的一个快照&#xff0c;每次commit都会生成一个快照Snapshot&#xff0c;每个Snapshot快照对应一个manifest list 元数据文件&#xff0c;每个manifest list …

2024国赛数学建模预测算法-BP神经网络模型的预测精度分析(MATLAB 实现)

人工神经网络 第一节 人工神经网络概述 在您阅读这本书的时候&#xff0c;大约有个相互连接的神经元在帮助您阅读、呼吸、思考&#xff0c;以及完成各种各样的动作。这些神经元中&#xff0c;有些有着与生俱来的功能&#xff0c;比如呼吸、吮吸&#xff0c;有些则是由后天训练…

动态规划DP--背包问题

文章目录 0-1背包问题 -- 问题定义动态规划解法代码题目&#xff1a;分割等和子集题解 0-1背包问题 – 问题定义 在 0-1 背包问题中&#xff0c;给定一个背包的最大容量 W&#xff0c;以及 n 个物品&#xff0c;每个物品有两个属性&#xff1a; 重量&#xff1a;第 i 个物品的…

[数据集][目标检测]电动车入梯进电梯电单车入梯检测数据集VOC+YOLO格式7106张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;7106 标注数量(xml文件个数)&#xff1a;7106 标注数量(txt文件个数)&#xff1a;7106 标注…

广义回归神经网络(GRNN)

一、简介 广义回归神经网络 (General Regression Neural Network &#xff0c; GRNN) 的概念是由德 国科学家多纳德提出的&#xff0c;是径向基网络的其中一种 。因为其是以数理统计为基 础的&#xff0c;因此 GRNN 可以依据样本数据逼近其中包含的非线性映射关系。即使样本 数…

家里有猫用宠物空气净化器有用吗?希喂、米家、有哈哪款更好

在快节奏的现代生活中&#xff0c;越来越多的人选择宠物作为心灵的慰藉与生活的伴侣。起初&#xff0c;这份陪伴的需求简单而纯粹&#xff0c;但随着日子一天天过去&#xff0c;那份简单的情感逐渐生根发芽&#xff0c;成长为深厚的责任与爱。我在前两年养了两只猫&#xff0c;…

一款云笔记支持在线协同文档,脑图,白板演示的工具,多个设备同步,让灵感与你同行(附源码)

前言 在快节奏的工作环境中&#xff0c;如何高-效地记录、整理并分享工作笔记已经成为了一项重要的技能。传统的笔记方式往往难以满足跨设备、即时同步以及团队协作的需求&#xff0c;导致信息孤岛和工作效率低下。面对这样的挑战&#xff0c;我们迫切需要一种全新的工具来改变…

【ArcGIS Pro原理第一期】各种空间插值原理:GPI、LPI、IDW等

ArcGIS Pro原理第一期&#xff1a;空间插值原理 空间插值方法概述1.1 全局多项式&#xff08;global polynomial interpolation, GPI&#xff09;方法原理使用范围 1.2 局部多项式&#xff08;local polynomial interpolation, LPI&#xff09;方法原理精度测量&#xff08;Mea…

echarts进度

echarts图表集 let numdata["I级",II级,III级,IV级,V级,劣V级] let pricedata40 option {backgroundColor: #0f375f,title: {show: false,text: ,left: center,top: 30%,textStyle: {color: #00D5FF,fontSize: 16,},},tooltip: {show: false},grid: {// show: true,…

鸿蒙界面开发——组件(5):菜单Menu 绑定菜单

菜单组件Menu Menu组件需和bindMenu或bindContextMenu方法配合使用&#xff0c;不支持作为普通组件单独使用。 Menu 以垂直列表形式显示的菜单。包含MenuItem、MenuItemGroup子组件。 Menu()作为菜单的固定容器&#xff0c;无参数。 MenuItem(value?: MenuItemOptions| Cust…

DC-DC开关稳压电路

前面所讲的线性稳压电路具有结构简单、调节方便、输出电压稳定性强、纹波电压小等优点。但是&#xff0c;由于调整管始终工作在放大状态&#xff0c;自身功耗较大&#xff0c;故效率较低&#xff0c;甚至仅为30%~40%。而且&#xff0c;为了解决调整管散热问题&#xff0c;必须安…

Linux-(系统启动、用户管理)

目录 前言 关机&重启命令 基本介绍 注意细节 用户登录和注销 注意&#xff1a; 用户管理 基本介绍 添加用户 指定/修改密码 删除用户 查询用户信息 切换用户 查看当前用户登录用户 用户组 新增组 删除组 查看所有组 修改用户所属组 创建用户时指定用户…

如何在车载中控上进行UI自动化测试

说到车载测试&#xff0c;很多人都很好奇&#xff0c;车载中控是否需要UI自动化测试&#xff0c;从市场反馈来说&#xff0c;在6-7年之前的车载中控测试就已经介入UI自动化测试&#xff0c;那时候还是使用javaUIautomator框架。现在大部分都已经更新为PythonAppium框架进行自动…

Fiddler安卓设备抓包基础

Fiddler安卓设备抓包基础 一、下载二、Fiddler设置三、安卓设备设置四、Fiddler工具页面介绍 一、下载 工具名称&#xff1a;Fiddler 下载地址&#xff1a;官网 二、Fiddler设置 1.打开Fiddler 2.打开Tools > Options > Connections进行如下设置&#xff0c;Fiddler…

【C++ Primer Plus习题】10.7

问题: 解答: main.cpp #include <iostream> #include "plorg.h" using namespace std;int main() {plorg p("Plorga");p.showCI();p.setCI(10);p.showCI();return 0; }plorg.h #pragma once#define SIZE 19 class plorg { private:char m_fillName…