【C++】---红黑树详解

一、什么是红黑树?

1、概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。

2、性质

最长路径不超过最短路径的二倍
(路径:就是从 根节点 开始到 叶子节点 结束为止,叫做一条路径!)
在这里插入图片描述

3、四个规则

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 红色节点不能连续!
  4. 红黑树的每条路径上的黑色节点数量必须相同!

只要满足以上4个规则,就一定符合,红黑树最长路径不超过最短路径的二倍。

但是反过来就不一定:

在这里插入图片描述

二、红黑树的定义

1、红黑树 结点 定义

红黑树相比于AVL树多了一个颜色,因此我们需要一个成员变量来储存颜色,而AVL树是一种严格 控制高度平衡 的二叉搜索树,但是红黑树是一种近似平衡的二叉搜索树,所以不需要平衡因子。

所以说可以这样区分和理解:
AVL树 :它需要平衡因子!
红黑树:不需要平衡因子,但是红黑树需要一个成员变量来储存颜色!

但是目前有一个问题,因为构造函数的时候红黑树 需要设置颜色,那么我们将新插入的节点设置为红色还是黑色呢?
(这样可以回归到我们的4个规则,主要就是维持规则3和规则4)

(1)将新插入的结点 设置为黑色

假如将新节点初始化成黑色,不管父亲是黑色还是红色,一定会破坏规则(4),并且影响其他路径,影响范围广。

①当父亲是黑色时,破坏了规则(4)
在这里插入图片描述
②当父亲是红色时,也破坏了规则(4)

在这里插入图片描述
所以总之如果将新插入的节点设置为黑色,你无论怎么插入它一定会破坏规则4。

(2)将新插入的结点 设置为红色

假如将新插入节点置为红色,会有以下两种情况:

①当父亲是黑色时,没有破坏规则(3),也没有破坏规则(4)

在这里插入图片描述

②当父亲是红色时,破坏了规则(3),但是只需要改变一下颜色即可

虽然破坏了规则三,但是规则三这个规则可以很容易就维护了你规则四如果破坏了,你需要每条路径都要改变,每条路径都受到影响了,根本维护不过来。
在这里插入图片描述
因此节点要初始化成红色。

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

	pair<K, V> _kv;
	Colour _col ;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED) // 将新插入的节点 初始化为 红色!
	{

	}
};

2、红黑树的定义

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
 
    //构造函数
    RBTree()
		:_root(nullptr)
	{}
 
private:
	Node* _root;
};

三、红黑树插入

1、插入节点

插入节点分为2步:

(1) 先查找,如果树中已存在该节点,则插入失败

(2)树中不存在该节点,插入

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			// 注意:因为是红黑树,所以必须保证根节点是黑色的
			_root->_col = BLACK;
			return true;
		}
		
		
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if(kv.first> cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false; // 不允许重复 相同的直接false
				}
			}
			cur = new Node(kv);
			cur->_col = RED;// 默认:新插入的结点 为红色节点!
			
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

// 此时已经算是插入上了,如果cur的parent是黑色就此结束,但是红色就需要调整了!

2、控制颜色

所以在插入节点之后,为了满足红黑树的性质,还需要调整节点颜色:

(1)父亲为黑色

如果父亲是黑色,那么不需要调整,4个规则都满足,插入已完成

在这里插入图片描述

(2)父亲为红色

如果父亲是红色,违反了规则(3),需要调整颜色
在这里插入图片描述

四、红黑树颜色处理

首先需要明白一点就是:什么时候要对红黑树的颜色进行处理?

那肯定是新插入节点是红色,这是已经提前设置好的,肯定是违反了规则三,新插入节点

cur,他的父亲肯定是红色,所以才违反了规则三。那既然父亲是红色节点,那他必然有爷

爷,因为红色节点不能当作根节点,所以说cur的爷爷必然是存在的,并且一定是黑色节

点,所以说一个红黑树这三个节点已经确定了!

①cur是红色
②父亲(parent)是红色
③爷爷(grandfather)是黑色
只有叔叔不确定,叔叔就是爷爷的左子树或者右子树,所以说变量就是叔叔,他是什么颜色
或者存在不存在,我们就以叔叔(uncle)这个变量来进行分类讨论!

1、cur红、p红、g黑、u存在且为红

当cur为红,p为红,g为黑,u存在且为红时,为了满足红黑树的性质,处理方法:变色
将p和u变黑,g变红

(1)抽象图

如下a、b、c、d、e都是子树:
在这里插入图片描述
(1)假如g是根节点,根据红黑树性质,根节点必须是黑色,那就把g再变黑就好了
在这里插入图片描述
(2)假如g不是根节点,g是子树,那么g还有parent节点。

①如果g的parent是黑色,满足红黑树性质,那么停止调整。

在这里插入图片描述
②如果g的parent是红色,就破坏了规则(3),那么还需要继续向上调整。
在这里插入图片描述
调整方法为:把g当作cur,继续向上调整,直到p不存在,或p存在且为黑停止。

(2)具象图

①g是根节点,直接把p和u变黑,g变红
在这里插入图片描述
②g不是根节点,g是子树,把p和u变黑,g变红之后,还要继续向上调整,直到p不存在,或p存在且为黑停止
在这里插入图片描述

2、cur红、p红、g黑、u不存在 或者(u存在且为黑)

这种情况下,g、p、cur形成直线,先看cur为红,p为红,g为黑,u为黑的情况

这是由情况一cur红,p红,g黑,u存在且为红处理以后变换而来,比如以下情况:

在这里插入图片描述
在这种情况下,cur原来的颜色一定是黑色,只不过在处理的过程当中,将cur的颜色变成了红色,所以cur不是新增节点,而是新增节点的g节点。

他这种(cur红、p红、g黑、u不存在 或者(u存在且为黑) )一定是有2层,x>=2。
解释如下:
在这里插入图片描述

(1)单旋

①抽象图:

如下a、b、c、d、e都是子树,由于要旋转,所以要分为两种情况:当p是g的左子树,cur是p的左子树时,g右单旋,p变黑,g变红:
在这里插入图片描述
当p是g的右子树,cur是p的右子树时,g左单旋,p变黑,g变红:

在这里插入图片描述

②具象图:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(2)双旋

双旋也就是 在反方向!

这种情况下g、p、cur形成折线,先看cur为红,p为红,g为黑,u为黑的情况:

①抽象图:

当p是g的左子树,cur是p的右子树时,处理方法:p左单旋,g右单旋,cur变黑,g变红:
在这里插入图片描述
当p是g的右子树,cur是p的左子树时,处理方法:p右单旋,就变成了情况二(单纯的一边高)
在这里插入图片描述

②具象图:

当p是g的左子树,cur是p的右子树时,将p左单旋,g右单旋,cur变黑,g变红:
在这里插入图片描述
当p是g的右子树,cur是p的左子树,p右单旋,g左单旋,p变黑,g变红
在这里插入图片描述
再看cur为红,p为红,g为黑,u不存在的情况,u不存在的情况更为简单:

当p是g的左子树,cur是p的右子树时, 将p左单旋,g右单旋,cur变黑,g变红

在这里插入图片描述
当p是g的右子树,cur是p的左子树,p右单旋就变成了情况二:
在这里插入图片描述

3、红黑树 结点 的插入代码

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			// 注意:因为是红黑树,所以必须保证根节点是黑色的
			_root->_col = BLACK;
			return true;
		}
		
		
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if(kv.first> cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			cur = new Node(kv);
			cur->_col = RED;// 默认:新插入的结点 为红色节点!
			
			if (kv.first < parent->_kv.first)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			// 此时已经算是插入上了,如果cur的parent是黑色就此结束,但是红色就需要调整了!

			while (parent && parent->_col == RED)
				// 这里的 parent && parent->_col == RED 中的 第1个parent就是来判断g是不是root
			{
				Node* grandfather = parent->_parent;

				// 1、变量uncle在grandfather的右边
				if (parent == grandfather->_left)
				{
					//(1)uncle存在&&为红   -------------// 变色 
					Node* uncle = grandfather->_right;

					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						//还要判断 此时的grandfather是不是root
						// ①g == root只需要让 g再次变黑即可!
						// ②g != root  把g赋值给cur 继续往上更新!
						cur = grandfather;
						parent = cur->_parent;

					}
					// (2)uncle不存在 || uncle存在 && 为黑色    ----// 变色 + 旋转
					else 
					{
						/*parent->_col = BLACK;
						grandfather->_col = RED;*/

						// 旋转:单旋 、双旋
						if (cur == parent->_left)
						{
							// (1)单旋
							//     g
							//   p   u
							// c

							 先变色 再旋转!

							//parent->_col = BLACK;
							//grandfather->_col = RED;
							//RS_rotation(grandfather);
							

							// 先旋转 再变色!
							RS_rotation(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else 
						{
							// (1)双旋
							//       g
							//   p      u
							//     c

							 先变色 再旋转!
							//cur->_col = BLACK;
							//grandfather->_col = RED;
							//LS_rotation(parent);
							//RS_rotation(grandfather);

							// 先旋转 再变色!
							LS_rotation(parent);
							RS_rotation(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;

						}

						break;// 因为else这种情况 变色:没有破坏规则  旋转:维持了红黑树的性质 所以完成之后可以直接 退出循环! 
					}
				}
				else
					// 2、变量uncle在grandfather的左边
				{
					Node* uncle = grandfather->_left;

					// (1)
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						cur = grandfather;
						parent = cur->_parent;
					}
					else // (2)
					{

						if (cur == parent->_right)
						{

							
							//parent->_col = BLACK;
							//grandfather->_col = RED;
							//LS_rotation(grandfather);
							
							// 先旋转 再变色!
							LS_rotation(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else
						{

							
							//parent->_col = BLACK;
							//grandfather->_col = RED;
							//RS_rotation(parent);
							//LS_rotation(grandfather);


							// 先旋转 再变色!
							RS_rotation(parent);
							LS_rotation(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;// 因为else这种情况 变色:没有破坏规则  旋转:维持了红黑树的性质 所以完成之后可以直接 退出循环! 
					}
					
				}
			}
			_root->_col = BLACK;
			return true;
		
	}

红黑树 先旋转再变色 or 先变色再旋转 都一样!!!

五、红黑树查找

	//查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)//key比当前节点小,就向右查找
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)//key比当前节点大,就向左查找
			{
				cur = cur->_left;
			}
			else//找到了
			{
				return cur;
			}
		}
		return nullptr;//空树,直接返回
	}

六、检查红黑树是否平衡

检查是否平衡还是要用到递归子函数

(1)需要判断是否满足红黑树的4条性质

(2)计算 最左 / 最右 路径上的黑色节点个数,把它当做参考值:refNum!
递归路径时,需要计算每个路径上的黑色节点个数(blackNum)是否和最左路径上的节点个数相等

public:
bool Is_balance()// 主要就是 判断 这个树 符不符合 那4个规则!
	{
		if (_root->_col == RED)
		{
			return false;// 不符合 规则2 :根节点必须是黑色!
		}
		
		// 创建 一个 refNum 当做参照物 用来检验是否和blackNum相等 从而得出结论:blackNum思路正确 && 整个树符合规则4、
		// refNum 可以是 最左侧/最右侧!

		// int refNum = 1;  因为根节点是黑色 为啥初始化的时候 不直接给1?而是前置++
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				// refNum++;   因为根节点是黑色 所以要用前置++
				++refNum;
			}
			cur = cur->_left;
		}


		return _Check(_root, 0,refNum);
	}
private:
// 创建一个 blackNum 用来标记 每条路径下 黑色节点的数目!(递归形参!)
	bool _Check(Node* root,int blackNum,const int refNum)
	{
		if (root == nullptr)
		{
			if (refNum != blackNum)
			{
				cout << "存在黑色结点不相等的 路径" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout <<root->_kv.first <<  "存在连续的红色结点!" << endl;
			return false;// 不符合规则3: 不能存在连续的红色节点!
		}


		if (root->_col == BLACK)
		{
			blackNum++;
		}

		// 递归遍历整个树!
		return _Check(root->_left, blackNum, refNum) &&
			   _Check(root->_right, blackNum, refNum);
	}

七、红黑树的遍历

	//遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
 
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
 
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

八、红黑树的验证

void TestRBTree1()
{
	int a[] = { 4,2,6,1,3,5,15,7,16,14,8,3,1,10,6,4,7,14,13 };

	RBTree<int, int> t1;
	for (auto e : a)
	{
		if (e == 10)
		{
			int i = 0;
		}
		t1.Insert({ e,e });

		cout << "Insert:" << e << "->" << t1.Is_balance() << endl;
	}

	t1.InOrder();

	cout << t1.Is_balance() << endl;
}

在这里插入图片描述

九、对Insert插入 总结、红黑树的注意事项:

(1)插入 总结

三种情况:
(1)uncle存在且为red
(2)uncle存在且为black
(3)uncle不存在

实际上,情况二 和 情况三 可以合并 为同一种情况!

在这里插入图片描述

(2)注意事项

1、对参考值 refNum 的初始化 refNum=0 、refNum必须是前置++
在这里插入图片描述

2、为啥要将 refNum 和 blackNum 判断是否相等 放在 if(root是否等于nullptr)里面?

在这里插入图片描述

3、双旋的注意事项:
在这里插入图片描述


好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小能软糖_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值