010 数据结构_红黑树

前言

本文将会向你介绍红黑树的概念、性质,以及如何手撕红黑树

1 文章重点

文本首先引入红黑树的概念和性质,性质非常重要对于后面的插入操作来说,文章的核心放在了插入部分,另外看插入部分之前记得看声名和节点的定义哦~

2 引入红黑树

2.1概念

首先红黑树是一颗二叉搜索树,每个节点都有颜色,红色或黑色,最长路径最多是最短路径的二倍

2.2性质

1、 每个节点不是红色就是黑色。
2、 根部节点是黑色的。
3、 一条路径上不能出现连续的红色节点
4、每条路径都必须包含相同数量的黑色节点。

3 测试

c 复制代码
	// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node* Find(const pair<K, V>& kv)
	{
		Node* cur = _root;
		Node* parent = _root;
		Node* grandParent = parent->_parent;
		//判断是否为空树
		if (_root == nullptr)
		{
			return nullptr;
		}
		//寻找插入位置
		else
		{
			Node* parent = cur;
			while (cur)
			{
				//记录父节点的位置,便于后续的链接操作
				parent = cur;
				//向左遍历
				if (cur->_kv.first > kv.first)
				{
					cur = cur->_left;
				}
				//向右遍历
				else if (cur->_kv.first < kv.first)
				{
					cur = cur->_right;
				}
				//已有
				else return cur;
			}
		}
	}

	//中序遍历,验证是否为二叉搜索树
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	size_t _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 Height()
	{
		return _Height(_root);
	}
	bool Check(Node* root, int blackNum, const int ref)
	{
		if (root == nullptr)  //到根部看看当前路径黑色节点和标准值是否一致
		{
			//cout << blackNum << endl;
			if (blackNum != ref)
			{
				cout << "存在黑色节点数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		// 检查子比较复杂,可以反过来去检查红节点父是否为黑色
			if (root->_cl == RED && root->_parent->_cl == RED)
			{
				cout << "有连续的红色节点" << endl;

				return false;
			}

		if (root->_cl == BLACK)
		{
			++blackNum;  //为黑节点加一
		}

		return Check(root->_left, blackNum, ref)
			&& Check(root->_right, blackNum, ref);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_cl == RED)
		{
			return false;
		}
		int ref = 0;
		Node* cur = root;
		//计算一条路径的黑色节点标准值
		while (cur)
		{
			if (cur->_cl == BLACK)
			{
				ref++;
			}
			cur = cur->_left;
		}
		int blackNum = 0;
		return Check(root, blackNum, ref); 
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
	

Check 函数是一个递归函数,用于检查从当前节点到叶子节点的路径上的黑色节点个数是否与参考值 ref 相等。
参数 root 表示当前节点指针,blackNum 表示根节点到当前节点的路径上的黑色节点个数,ref 是参考值。
如果当前节点为空指针,即到达了叶子节点,那么检查路径上的黑色节点个数 blacknum 是否等于参考值 ref,如果不相等则返回false,否则返回 true。
如果当前节点的颜色为红色,并且父节点也是红色,表示存在连续的红节点,不符合红黑树的性质,返回 false。
如果当前节点的颜色为黑色,将 blackNum 值加一。 递归地调用 Check 函数检查左子节点和右子节点,并将当前节点的黑色节点个数blacknum 作为参数传递。
IsBalance 函数用于检查整个红黑树是否符合红黑树的性质。
如果红黑树为空树,即根节点为空,认为是一棵合法的红黑树,返回 true。
如果根节点的颜色是红色,违反了红黑树的性质,返回 false。
初始化 blacknum 为 0,用于记录每条路径的黑色节点个数。 初始化 ref 为 0,作为参考值。
通过遍历从根节点到最左子节点的路径,统计参考值 ref,即路径上的黑色节点个数。 调用 Check
函数,传入根节点、路径上的黑色节点个数 blacknum 和参考值 RefVal 进行检查

插入一千个数据(未全列出)

4 插入(重点)

声名

为了方便叙述,将进行以下定义

cur©------当前节点

parent§------父节点

grandParent(g)------父节点的父节点

uncle(u)------父节点的兄弟节点

节点的定义

c 复制代码
enum Color
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	RBTreeNode* _pHead;
	Color _cl;
	pair<K, V> _kv;
template<class K, class V>
	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_pHead(nullptr)
		, _cl(RED)
		, _kv(kv)
	{}
};

_left:指向左子节点的指针。

_right:指向右子节点的指针。

_parent:指向父节点的指针。

_kv:存储键值对的成员变量。这里使用了 pair<K, V> 类型来表示键值对,其中 K 是键的类型,V 是值的类型。

_cl:表示节点的颜色。这里使用枚举类型来表示,其中 RED 表示红色,BLACK 表示黑色,构造出来的节点默认为红色

插入

对于新插入节点,我们设置为红色,原因是红黑树每条路径都必须包含相同数量的黑色节点(性质4),新插入红节点不一定破坏红黑树的结构,新插入黑色节点一定不符合性质4而且很难调整(新增一条路径的黑色节点,其他路径也需要增加(会很麻烦))

c 复制代码
bool Insert(const pair<K, V>& kv)
	{				
		//记录当前节点
		Node* cur = _root;
		//记录父节点
		Node* parent = _root;
		//判断是否为空树
		if (_root == nullptr)
		{
			//直接插入
			_root = new Node(kv);
			//插入成功
			_root->_cl = BLACK;
			return true;
		}
		//寻找插入位置
		else
		{	
			while (cur)
			{
				//记录父节点的位置,便于后续的链接操作
				parent = cur;
				//向左遍历
				if (cur->_kv.first > kv.first)
				{
					cur = cur->_left;
				}
				//向右遍历
				else if (cur->_kv.first < kv.first)
				{
					cur = cur->_right;
				}
				//已有
				else return false;
			}
			cur = new Node(kv);
			//插入+链接
			if (parent->_kv.first > kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			//链接
			cur->_parent = parent;
		}
		//父亲为红,变色调整
		while (parent && parent->_cl == RED)
		{
			//以下代码暂时省略......
			
			//1、parent为grandparent的左
			//情况一:uncle存在且为红,变色即可
			//无需讨论cur的位置,变色即可
			//情况二:uncle不存在或者为黑,旋转+变色
			//需讨论cur的位置
					//(1)cur为parent的左(进行右单旋)
					//	 g
					// p   u
					//c
					//(2)cur为parent的右(进行左右双旋)
					//	 g
					// p   u
					//	c
			
			//2、parent为grandparent的右
				//情况一:uncle存在且为红,变色即可
				//无需讨论cur的位置,变色即可
				//情况二:uncle不存在或者为黑,旋转+变色
					//(1)cur为parent的右(进行左单旋)
					//	 g
					// u   p
					//		c
					//(2)cur为parent的右(进行右左双旋)
					//	 g
					// u   p
					//	 c
		}	
		//保持根部为黑色
		_root->_cl = BLACK;
		return true;
	}

注意:所有的情况解决方案都是根据规律总结出来的,由于将所有的情况列出来过于复杂,所以用抽象的子树来代替(三角形代表的就是抽象的子树),在这一情况中将会告诉你如果不用抽象子树来代替会有多复杂

(1)情况一:parent为grandparent的左

1.1uncle存在且为红,变色即可

c 复制代码
Node* uncle = grandParent->_right;
if (uncle && uncle->_cl == RED)
{
	parent->_cl = uncle->_cl = BLACK;
	grandParent->_cl = RED;
	//继续向上调整
	cur = grandParent;
	parent = cur->_parent;
}


三角形表示抽象子树,下面会举例说明这里为什么会用抽象子树来表示

当c,d,e是没有黑色节点的子树,此时cur是新增,此时c,d,e子树处为一个红节点或者为空,a,b为空。我们只需要通过新插入节点以及p,u、g节点进行变色即可,如图将p,u变黑,将g变红即可

当c,d,e子树是包含一个黑色节点的子树,如图此时c(cur)便不是新插入的节点了,最下边的红色节点才是

这是c,d,e子树可能的情况,然而插入位置有4个,cde形状组合:44 4,合计组合为64*4 = 256种情况,如果c,d,e子树是包含2个黑色节点及以上的,那么情况是分析不完的

其实你会发现,只要满足某个规律(如下)

然后做出应有的调整

最后在向上调整就好了

以下情况(非必须)都会用抽象子树来模拟规律

1.2uncle不存在或者为黑,旋转+变色

1.2.1cur为parent的左(进行右单旋)
c 复制代码
//(1)cur为parent的左
//	 g
// p   u
//c
if (cur == parent->_left)
{
	RotateR(grandParent);
	grandParent->_cl = RED;
	parent->_cl = BLACK;
}

为了能让你更好的理解。此情况(uncle不存在或者为黑)的调整将会结合uncle存在且为红,变色即可的情况
如图:c、d、e是包含一个黑色节点的子树

规律:uncle存在且为红,变色即可并向上调整

我们将会发现此时满足u为黑

右旋


变色

注意c、d、e是包含一个黑色节点的子树

此时你便可以看看是否满足红黑树的性质了

1.2.2cur为parent的右(进行右左双旋)
c 复制代码
//(1)cur为parent的右
//	 g
// p   u
//	c
else
{
	//左右双旋
	RotateL(parent);
	RotateR(grandParent);
	grandParent->_cl = RED;
	cur->_cl = BLACK;
}

(2)情况二:parent为grandparent的右

2.1uncle存在且为红,变色即可

c 复制代码
Node* uncle = grandParent->_left;
if (uncle && uncle->_cl == RED)
{
	parent->_cl = uncle->_cl = BLACK;
	grandParent->_cl = RED;
	//继续向上调整
	cur = grandParent;
	parent = cur->_parent;
}

2.2uncle不存在或者为黑,旋转+变色

2.2.1cur为parent的右(进行左单旋)
c 复制代码
//(1)cur为parent的右(左单旋)
//	 g
// u   p
//		c
if (cur == parent->_right)
{
	RotateL(grandParent);
	grandParent->_cl = RED;
	parent->_cl = BLACK;
}
2.2.2cur为parent的左(进行右左双旋)
c 复制代码
//(1)cur为parent的右(右左双旋)
//	 g
// u   p
//	 c
RotateR(parent);
RotateL(grandParent);
grandParent->_cl = RED;
cur->_cl = BLACK;

5 全部代码

c 复制代码
enum Color
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	RBTreeNode* _pHead;
	Color _cl;
	pair<K, V> _kv;
template<class K, class V>
	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_pHead(nullptr)
		, _cl(RED)
		, _kv(kv)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//左单旋
	void RotateL(Node* pParent)
	{

		Node* subR = pParent->_right;
		Node* subRL = subR->_left;	//可能为空


		pParent->_right = subRL;
		subR->_left = pParent;

		Node* pPnode = pParent->_parent; //原来父亲的父亲

		pParent->_parent = subR;
		//可能为空
		if (subRL)
		{
			subRL->_parent = pParent;
		}
		
		//链接:旋转整棵树
		if (_root == pParent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		//链接:旋转子树
		else
		{
			if (pPnode->_left == pParent)
			{
				pPnode->_left = subR;
			}
			else if (pPnode->_right == pParent)
			{
				pPnode->_right = subR;
			}
			subR->_parent = pPnode;
		}
	}
	// 右单旋
	void RotateR(Node* pParent)
	{

		Node* subL = pParent->_left;
		Node* subLR = subL->_right;

		pParent->_left = subLR;
		//可能为空
		if (subLR)
		{
			subLR->_parent = pParent;
		}

		Node* pPnode = pParent->_parent;

		subL->_right = pParent;
		pParent->_parent = subL;

		//旋转部分子树
		if (_root == pParent)
		{			
			_root = subL;
			subL->_parent = nullptr;
		}
		//旋转整棵子树
		else
		{
			if (pPnode->_left == pParent)
			{
				pPnode->_left = subL;
			}
			else
			{
				pPnode->_right = subL;
			}
			subL->_parent = pPnode;
		}
	}
	bool Insert(const pair<K, V>& kv)
	{				
		//记录当前节点
		Node* cur = _root;
		//记录父节点
		Node* parent = _root;
		//判断是否为空树
		if (_root == nullptr)
		{
			//直接插入
			_root = new Node(kv);
			//插入成功
			_root->_cl = BLACK;
			return true;
		}
		//寻找插入位置
		else
		{	
			while (cur)
			{
				//记录父节点的位置,便于后续的链接操作
				parent = cur;
				//向左遍历
				if (cur->_kv.first > kv.first)
				{
					cur = cur->_left;
				}
				//向右遍历
				else if (cur->_kv.first < kv.first)
				{
					cur = cur->_right;
				}
				//已有
				else return false;
			}
			cur = new Node(kv);
			//插入+链接
			if (parent->_kv.first > kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			//链接
			cur->_parent = parent;
		}
		//父亲为红,变色调整
		while (parent && parent->_cl == RED)
		{
			//记录原来节点的父亲的父节点
			Node* grandParent = parent->_parent;
			//情况一:uncle存在且为红,变色即可
			if (parent == grandParent->_left)
			{
				Node* uncle = grandParent->_right;
				if (uncle && uncle->_cl == RED)
				{
					parent->_cl = uncle->_cl = BLACK;
					grandParent->_cl = RED;
					//继续向上调整
					cur = grandParent;
					parent = cur->_parent;
				}
				//情况二:uncle不存在或者为黑,旋转+变色
				else
				{
					//(1)cur为parent的左
					//	 g
					// p   u
					//c
					if (cur == parent->_left)
					{
						RotateR(grandParent);
						grandParent->_cl = RED;
						parent->_cl = BLACK;
					}
					//(1)cur为parent的右
					//	 g
					// p   u
					//	c
					else
					{
						//左右双旋
						RotateL(parent);
						RotateR(grandParent);
						grandParent->_cl = RED;
						cur->_cl = BLACK;
					}
					break;
				}
			
			}
			//parent为grandparent的右
			else
			{
				//情况一:uncle存在且为红,变色即可
				Node* uncle = grandParent->_left;
				if (uncle && uncle->_cl == RED)
				{
					parent->_cl = uncle->_cl = BLACK;
					grandParent->_cl = RED;
					//继续向上调整
					cur = grandParent;
					parent = cur->_parent;
				}
				//情况二:uncle不存在或者为黑,旋转+变色
				else
				{
					//(1)cur为parent的右(左单旋)
					//	 g
					// u   p
					//		c
					if (cur == parent->_right)
					{
						RotateL(grandParent);
						grandParent->_cl = RED;
						parent->_cl = BLACK;
					}
					//(1)cur为parent的右(左单旋)
					else
					{
						//(1)cur为parent的右(右左双旋)
						//	 g
						// u   p
						//	 c
						RotateR(parent);
						RotateL(grandParent);
						grandParent->_cl = RED;
						cur->_cl = BLACK;
					}
					break;
				}
				
			}
		}	
		//保持根部为黑色
		_root->_cl = BLACK;
		return true;
	}

小结

今日的分享就到这里啦,如果本文存在疏漏或错误的的地方,还请您能够指出

相关推荐
御风@户外19 分钟前
质数生成函数、质数判断备份
算法·acm
闻缺陷则喜何志丹32 分钟前
【C++动态规划】1105. 填充书架|2104
c++·算法·动态规划·力扣·高度·最小·书架
Dong雨43 分钟前
六大排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
数据结构·算法·排序算法
达帮主1 小时前
7.C语言 宏(Macro) 宏定义,宏函数
linux·c语言·算法
是十一月末1 小时前
机器学习之KNN算法预测数据和数据可视化
人工智能·python·算法·机器学习·信息可视化
chenziang11 小时前
leetcode hot100 路径总和
算法
lyx1426061 小时前
leetcode 3083. 字符串及其反转中是否存在同一子字符串
算法·leetcode·职场和发展
茶猫_1 小时前
力扣面试题 39 - 三步问题 C语言解法
c语言·数据结构·算法·leetcode·职场和发展
初学者丶一起加油1 小时前
C语言基础:指针(数组指针与指针数组)
linux·c语言·开发语言·数据结构·c++·算法·visual studio
积兆科技2 小时前
从汽车企业案例看仓网规划的关键步骤(视频版)
人工智能·算法·汽车·制造