当前位置: 首页 > article >正文

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

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

一、红黑树的概念和性质

        红黑树(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.kler.cn/a/301474.html

相关文章:

  • 大语言模型通用能力排行榜(2024年10月8日更新)
  • 基于Java Springboot编程语言在线学习平台
  • STL序列式容器之stack
  • Vue2教程002:Vue指令
  • Java 动态代理初步
  • spring-cache concurrentHashMap 自定义过期时间
  • 衡石分析平台使用手册-部署前准备
  • flink中disableChaining() 的详解
  • Redis面对数据量庞大处理方法
  • Jmeter_循环获取请求接口的字段,并写入文件
  • 如何实现视频数据的PES打包和传输?
  • 【JavaSE】基础学习以及简单的计算器应用程序GUI实现
  • 【Kubernetes】常见面试题汇总(十)
  • ffmpeg编译连接报错 undefined reference to `uncompress‘
  • leetcode练习 单词搜索
  • 【区块链 + 人才服务】基于区块链技术助力人才证书数字化 | FISCO BCOS应用案例
  • wordpress建立数据库连接失败 数据库删除恢复
  • 【Linux】信号的产生、保存与处理
  • 网页时装购物系统:Spring Boot技术的实际应用
  • 【双指针】N数之和
  • [SWPUCTF 2021 新生赛]web方向(一到六题) 解题思路,实操解析,解题软件使用,解题方法教程
  • 猫咪掉毛怎么处理?希喂、米家、范罗士宠物空气净化器用哪款?
  • Linux 删除 当前下的 mysql-8.0.31 空文件夹
  • ChatGPT的底层逻辑
  • 物联网的设计
  • ubuntu 安装 jdk