【C++庖丁解牛】高阶数据结构---红黑树详解(万字超详细全面介绍红黑树)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注网络安全)
img

正文

		{
			ppnode->_right = subL;
		}
		subL->_parent = ppnode;
	}
}

## 6.红黑树插入


红黑树的插入过程和二叉查找树插入过程基本类似,不同的地方在于,红黑树插入新节点后,需要进行调整,以满足红黑树的性质。性质1规定红黑树节点的颜色要么是红色要么是黑色,那么在插入新节点时,这个节点应该是红色还是黑色呢?


我都想不说答案,大家直接看性质5 **(从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。**),你插个黑的是不是必然会影响是不是就要调整了,是不是必然就是红的了,对的吧,红的出现两红连续了,可能才需要调整,对的吧,所以插入的节点应该是红色的。


插入我们可能感觉很迷茫,不知道如何下手,这玩意这可咋插入呀,一想到就头大就逃避,哈哈哈,凡复杂事情,其实按情况去划分,把每种情况都克服了,整体也就克服了。我们站在待插入节点的角度,去看我要往哪个父节点下插入,那么看父节点的话,那插入的几种情况我们就来看看哈,我们这里待插入的节点为N节点:


### 情况一:父节点为空


当父节点为空,也就是到根节点了,因为性质2所以将节点 N 直接变黑作为根节点,即可。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/20f85adc972f4f45b66082208984e1ec.png)


### 情况二:父节点是黑色


当父节点是黑色,这种情况下,性质4和性质5没有受到影响,不需要调整。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c950e97a30d449c7a2d41dacdd7a5dc7.png)


### 情况三:父节点是红色,叔叔也为红色。


待插入节点N的父节点为红色,叔叔也是红色的情况时,性质4就会被打破,此时需要进行调整。这种情况下,先将 父节点 和 叔叔节点 染成黑色,再将 爷爷节点 的颜色染成红色。此时经过爷爷的路径上的黑色节点数量不变(想想是不是),性质5仍然满足。但需要注意的是 G 被染成红色后,可能会和它的父节点形成连续的红色节点,此时需要递归向上调整。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dba056f71f014cc1a9789557fd147e77.png)  
 当然此情况下的这些情况都属于情况三哈。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c8cec54babe24ddfbf9377cb71cceb45.png)


### 情况四:父节点是红色,叔叔为黑色。


这种情况跟情况三的不同关键点就在于叔叔的节点颜色,情况三叔叔是红色,而情况四叔叔是黑色的哈。我们的角度从父节点的左右以及待插入节点是父节点的左右可以分成细拆成4种情况,我们来看下这四种情况的处理:  
 **(1)父节点为左子树,待插入节点为父节点的左子树**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/71ae2968d93e4b598e7301ad3ba08dfc.png)  
 **(2)父节点为左子树,待插入节点为父节点的右子树**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/30d6a963e75542628a4c860bcb1a958e.png)  
 **小记:当父节点为左子树时,待插入的节点都要往左靠(这个理解不,你看上图待插入N在父节点P的右边时,是不是先左旋了一下,都靠左边了,然后跟待插入节点在左子树时保持都靠左边啦)然后父节点和爷爷换色,然后右旋的。**


**(3)父节点为右子树,待插入节点为父节点的右子树**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/431e243fb29d4f2b93183cf3fb71d055.png)  
 **(4)父节点为右子树,待插入节点为父节点的左子树**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8860b23de812401c8f3a2831668b8528.png)  
 **小记:可以看到跟左子树的思路其实差不多,先把节点都往右靠,只是旋转方向不一样,右子树是左旋,左子树是右旋。**


### 插入小结


针对插入我们看到,划分情况来处理哈:


* 父节点为空的话,就直接变黑即可
* 父节点为黑的话,保持位置不变即可
* 父节点为红的话,就要观察叔叔节点的颜色
* 父节点为红,叔叔为红,直接父亲和叔叔变黑,爷爷变红,然后以爷爷为中心继续递归处理
* 父节点为红,叔叔为黑,就要父亲是左子树还是右子树以及待插入的是父亲的左子树还是右子树,父亲是左子树就要往左靠,所以待插入如果在父亲的右子树就要先左旋,然后父亲和爷爷换色,然后以爷爷为中心右旋哈,
* 父节点为红,叔叔为黑,父亲是右子树就要往右靠,所以待插入如果在父亲的左子树就要先右旋,然后父亲和爷爷换色,然后以爷爷为中心左旋哈。


**实现代码:**



bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)   //空树
	{
		_root = new Node(kv);    //新增节点为根节点
		_root->_col = BLACK;     //根节点为黑色,新节点默认为红,我们修改为黑色
		return true;
	}

	Node\* parent = nullptr;      //父亲
	Node\* cur = _root;           //根节点
	while (cur)        
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	//走到这里cur为空,这里就是要插入的位置
	cur = new Node(kv); // 红色的
	//连接
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	//链接父亲
	cur->_parent = parent;
	//如果插入以后它的parent是红色的,就需要处理
	while (parent && parent->_col == RED)
	{
		Node\* grandfather = parent->_parent;
		//如果父亲是爷爷的左孩子,那右孩子就是叔叔
		if (parent == grandfather->_left)
		{
			Node\* uncle = grandfather->_right;
			// 情况一:叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				//变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				//更新cur为grandfather,判断它的父亲是什么情况
				//1.如果不存在或者为黑,就需要继续处理,也不会进行循环
				//2.如果它的父亲存在且为红,重新循环进行调整
				cur = grandfather;
				parent = cur->_parent;
			}
			else   
			{
				// 情况二:叔叔不存在或者存在且为黑
				// 旋转+变色
				if (cur == parent->_left)
				{
					// g
					// p u
					// c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					// g
					// p u
					// c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			} 
		}
		else
		{
			Node\* uncle = grandfather->_left;
			// 情况一:叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				// 变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				// 情况二:叔叔不存在或者存在且为黑
				// 旋转+变色
				// g
				// u p
				// c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					// g
					// u p
					// c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
	}

	_root->_col = BLACK;

	return true;
}

## 7. 删除


相较于插入操作,红黑树的删除操作则要更为复杂一些。删除操作首先要确定待删除节点有几个孩子,如果有孩子,不能直接删除该节点。我们看上边插入的时候看的是叔叔节点的眼色,而删除是看的待删除节点的兄弟节点的颜色。还是一样我们站在待删除节点的角度,我们可以从如下两个方面去考虑:


* **节点的颜色,节点有红黑两种可能,那么删除黑节点就会造成节点失衡。**
* **节点的构造,有三种可能:其一是删除节点的孩子都为null,其二是删除节点的一个孩子节点为null,其三是删除节点的两个孩子节点都不为null。而如果两个节点都不为null,那么我们就需要找替代节点(前驱或者后继结点)来替代删除,而删除替代结点就会是情况一和情况二。**


删除的第三种情况大家应该知道吧,跟我们二叉排序树删除是不是要找一个前驱或者后继节点来替换,然后将前驱或者后继节点的值赋值给待删除的节点,然后删除前驱或者后继节点。而前驱或者后继结点的判断可以使用下图的方式,将节点Key落入X轴中(实质是中序排序)其后一个结点就是后继节点,前一个就是前驱节点,那么我们看一下后继节点替代删除的方案,比如删除-1,那么可以将0替换到-1的位置,然后删除0,这样就回到情况一, 再比如删除4,那么使用5来代替,然后删除5,这样就回到了情况二。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cdd6f4efb767406a92b80d7df035a02b.png)


由于前驱和后继至多只有一个孩子节点,这样我们就把原来要删除的节点有两个孩子的问题转化为只有一个孩子节点的问题,问题被简化了一些。这个大家理解的吧,不理解的话建议大家先去看一下二叉排序树的东西,比如二叉排序树的删除,再来这里看红黑树就好理解一点了。


那么针对红黑树的删除,我们这里可分为三种情形:


1. **删除的节点有一个孩子节点,删除节点只能是黑色,其子节点也必为红色,此时用删除节点的子节点接到父节点,且将子节点颜色涂黑,保证黑色数量。**


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6cd9d9ccb45a44f1b065499cafb49454.png)


2. **删除的节点没有孩子节点,如果是红色还好直接删除即可,如果是黑色就需要考虑平衡了**
3. 


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8a18272d8744445f924e02ebe2ba9e11.png)


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/748cfb9b7a6f45a6b591fea8888ad291.png)


3. **删除的节点有两个孩子节点,与二叉排序树一样,使用前驱或者后继节点作为替换的删除节点,场景转至为1或2处理。**


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ebf22ac9ac5f406ca99dee8392a8ac24.png)  
 接下来我们就仔细分析下情形一和情形二。


### 7.1 删除节点仅存在一个孩子


删除节点存在一个孩子,那么此时,不需要考虑节点的颜色,因为该节点必然为黑色 (因为性质4,红下必有俩黑或者俩null节点,而我们这里的情况是只有一个孩子,所以必为黑),且孩子节点必然为红(如果为黑,那么D节点就会有一方黑色节点比另一方多一,这个理解么?因为我们说过红黑树的任一黑节点为根的树也必是一颗红黑树)。那么情况一的所有情况如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c84ee1cafbfe469793cc31b96cf4950e.png)  
 这种情况处理较为简单,只需要将节点P的指针指向DL或者DR,然后将DL或者DR的颜色变更为黑色, 就保证了红黑树的平衡,如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c5cdd241117049b6acb3b50915fe2f84.png)  
 上述情况需要注意当D是根节点时,删除操作之后,孩子节点会成为新的根节点


### 7.2删除节点不存在孩子


删除节点不存在孩子,那么此时就需要考虑节点的颜色。


* **删除节点为红色**  
 如果为红色,因为性质4那么父节点肯定为黑色,那么直接删除即可哈,P节点断掉指向节点D的指针指向就好了。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/34e48dd536614a5b9a6b02ba6517c1b7.png)


* **删除节点为黑色**  
 我们考虑黑色,这种情况较为复杂,因为黑色节点被删除之后,红黑树会失去平衡,此时需要调整平衡。那么可能的情况有如下几种(如果D为根节点,删除操作后,红黑树变为空树即可,下面以非根节点的情况为例来分析)
* + **父节点为红色,兄弟节点不存在孩子**  
	 父节点为红色,兄弟节点不存在孩子(兄弟节点必然存在,且为黑色),这种情况稍微好处理,因为将父节点变为黑色,兄弟节点变为红色,即可保证红黑树平衡。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6e7f77788b3c456a91e3e50e258840a2.png)


* + **父节点为红色,兄弟节点存在孩子**  
	 父节点为红色,兄弟节点存在孩子,此时无法像情况 3.3.2.1 那么处理,因为兄弟节点存在的孩子必然为红色(理解么?因为我们说了要删除的节点D没有孩子,父亲是红,兄弟就必是黑节点,黑节点下如果还有黑节点,是不是父节点为根的子树就不是红黑树了,对不对),那么此时就需要进行旋转。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cbf0adfc972f4256bb4d5122bb005368.png)


* + 父节点为黑色  
	 当兄弟结点不存在孩子节点,此时无法通过旋转来实现平衡,即P节点无法继续调整,那么就B设置为 红色,保证P这个子树平衡,然后通过P节点,向上递归维持平衡。


比如P的父亲是红色的话:


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ce2617721b9c428dbc87b183b0bf7097.png)


### 删除小结


(1)删除节点之后,看看这个节点是不是黑色的叶子节点,如果不是,简单处理就可以达到平衡了;


(2)先看N是不是根节点,是的话啥都不用管;不是的话看兄弟什么颜色:


* 兄弟是红色:进行旋转涂色,去到兄弟为黑色那里处理
* 兄弟是黑色,看看兄弟子节点是不是全部都是黑。
* 全黑的话,看父节点什么颜色进行对应处理;
* 不全黑,看兄在的位置,兄在左的话,看兄的左子是不是红色,进行对应处理;兄在右的话,看兄的右子是不是红色,进行对应处理。


## 8.红黑树的验证


验证其是否平衡且满足红黑树性质  
 那如何判断它是否满足是一棵红黑树呢?


**其实就是去检查那几条规则(性质):**


首先结点颜色要么是黑色,要么是红色,这没什么好检查的。 然后根结点必须是黑色,这个可以检查一下,如果根结点不是黑色(是红色)直接就不符合了



bool IsBalance()
{
	if (_root && _root->_col == RED)
		return false;

	return Check(_root);
}

**然后如果出现连续的红色结点,那也不符合。** 那怎么检查有没有出现连续红色结点呢? 我们可以去遍历这棵树,然后遇到红色结点判断它的孩子是不是红色结点,如果存在红色结点它的孩子也是红色,那就不符合。 这样确实可以,但是不太好,因为孩子的话要检查两个,而且结点的孩子有还有可能不存在,为空,那还得再加一个判断。 所以我们可以这样做:遍历遇到红色结点我们去看它的父亲,如果它的父亲也为红色那就不行。 而判断父亲的话,只有根结点没有父亲,但是根结点是黑色的也不会去检查它。 所以这样写会好一点



bool Check(Node\* cur)
{
	if (cur == nullptr)
		return true;

	if (cur->_col == RED && cur->_parent->_col == RED)
	{
		cout << cur->_kv.first << "存在连续的红色节点" << endl;
		return false;
	}
	
	return Check(cur->_left)
		&& Check(cur->_right);
}

bool IsBalance()
{
	if (_root && _root->_col == RED)
		return false;

	return Check(_root);
}

**然后还剩一个,我们要检查每条路径黑色结点数量是否相等,存在不相等的情况就不符合。 那这个要如何检查呢? ,我们可以先求出一条路径的黑色结点数量,把它作为基准值,然后再递归求出每条路径的黑色结点数量和它比较,如果存在不相等的情况,就不符合。**



bool Check(Node\* cur, int blackNum, int refBlackNum)
{
	if (cur == nullptr)
	{
		//走到空就是一条路径走完了,比较一下是否相等
		if (refBlackNum != blackNum)
		{
			cout << "黑色节点的数量不相等" << endl;
			return false;
		}

		//cout << blackNum << endl;
		return true;
	}

	if (cur->_col == RED && cur->_parent->_col == RED)
	{
		cout << cur->_kv.first << "存在连续的红色节点" << endl;
		return false;
	}

	if (cur->_col == BLACK)
		++blackNum;
	
	return Check(cur->_left, blackNum, refBlackNum)
		&& Check(cur->_right, blackNum, refBlackNum);
}

bool IsBalance()
{
	if (_root && _root->_col == RED)
		return false;
	
	//先求出一条路径黑色结点数量
	int refBlackNum = 0;
	Node\* cur = _root;
	while (cur)
	{
		if(cur->_col == BLACK)
			refBlackNum++;

		cur = cur->_left;
	}
	//检查是否出现连续红色结点及所有路径黑色结点数量是否相等
	return Check(_root, 0, refBlackNum);
}

## 9. 红黑树的查找


那红黑树的查找就也和搜索二叉树的查找一样,之前讲过,这里就不再说了。


## 9. 红黑树与AVL树的比较


红黑树和AVL树都是高效的自平衡搜索二叉树,增删改查的时间复杂度都是O( 
 
 
 
 
 l 
 
 
 o 
 
 
 
 g 
 
 
 2 
 
 
 
 N 
 
 
 
 log\_2 N 
 
 
 log2​N)。 红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,所以AVL树的插入和删除操作比红黑树更耗时。 因为AVL树在插入和删除节点后,会进行更多的旋转操作以维持一个较为严格的平衡,所以插入和删除操作的时间复杂度更高。 相对而言,红黑树对平衡的控制比较宽松,降低了插入删除时需要旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。 在实际应用中,红黑树的使用更广泛。许多编程语言和库都使用红黑树作为基础数据结构,例如C++ STL中的std::map和std::set就是基于 红黑树实现的。


## 10.map底层为什么用红黑树实现


### 红黑树:


**红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。  
 通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,  
 因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。**


**性质:**


1. 每个节点非红即黑
2. 根节点是黑的;
3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
4. 如果一个节点是红色的,则它的子节点必须是黑色的。
5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;


### .平衡二叉树(AVL树):


**红黑树是在AVL树的基础上提出来的。**


平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。


AVL树中所有结点为根的树的左右子树高度之差的绝对值不超过1。


将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。


## 11. 源码


### 11.1 RBTree.h



#pragma once

enum Colour
{
RED,
BLACK,
};

template
struct RBTreeNode
{
RBTreeNode* _parent;
RBTreeNode* _left;
RBTreeNode* _right;
T _data;
Colour _col;

RBTreeNode(const T& data)
:_parent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _data(data)
, _col(RED)
{}

};

template
class RBTree
{
typedef RBTreeNode Node;
public:
bool Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//走到这里cur为空,就是key应该插入的位置
cur = new Node(data);
//链接
if (data < parent->_data)
parent->_left = cur;
if (data > parent->_data)
parent->_right = cur;

//链接父亲指针
cur->_parent = parent;

//如果插入之后它的parent是红的,就需要进行调整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
//如果父亲是祖父的左孩子,那右孩子就是叔叔
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//这里处理的情况是叔叔存在且为红,变色+向上继续处理
if (uncle && uncle->_col == RED)
{
//将p,u改为黑,g改为红
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;

//更新cur为grandfather,判断它的父亲是什么情况:
//1.如果不存在或者为黑,就需要继续处理了,也不会进行循环了
//2.如果它的父亲存在且为红,重新循环进行调整
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在/叔叔存在且为黑的情况
{
// g
// p u
// c
if (cur == parent->_left)//左左——右单旋+变色
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//左右——左右双旋+变色
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//这里调整完就可以break了,因为颜色都变的合适了,而相关的链接关系旋转会帮我们处理
break;
}
}
//如果父亲是祖父的右孩子,那左孩子就是叔叔
else //parent = grandfather->_right
{
Node* uncle = grandfather->_left;
//这里处理的情况是叔叔存在且为红
if (uncle && uncle->_col == RED)
{
//将p,u改为黑,g改为红
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;

//更新cur为grandfather,判断它的父亲是什么情况:
//1.如果不存在或者为黑,就需要继续处理了,也不会进行循环了
//2.如果它的父亲存在且为红,重新循环进行调整
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)//右右——左单旋+变色
{
// g
// u p
// c
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//右左——右左双旋+变色
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
//上面处理过程中有可能会把根变成红色,这里统一处理一下,把根置成黑
_root->_col = BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBlance()
{
if (_root && _root->_col == RED)
{
cout << “根结点是红色” << endl;
return false;
}

//先求出一条路径黑色结点数量
int mark = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++mark;
cur = cur->_left;
}

//检查是否出现连续红色结点及所有路径黑色结点数量是否相等
return _Check(_root, 0, mark);
}
int TreeHeight()
{
return _TreeHeight(_root);
}
private:
int _TreeHeight(Node* root)
{
if (root == nullptr)
return 0;
int RightH = _TreeHeight(root->_left);
int leftH = _TreeHeight(root->_right);
return RightH > leftH ? RightH + 1 : leftH + 1;
}
bool _Check(Node* root, int blackNum, int mark)
{
if (root == nullptr)
{
//走到空就是一条路径走完了,比较一下是否相等
if (blackNum != mark)
{
cout << “存在黑色结点数量不相等” << endl;
return false;
}
return true;
}

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

if (root->_col == RED && root->_parent->_col == RED)
{
cout << “出现连续红色结点” << endl;
return false;
}

return _Check(root->_left, blackNum, mark)
&& _Check(root->_left, blackNum, mark);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
//左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;

//旋转并更新_parent指针
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;

//先保存一下parent->_parent,因为下面会改它
Node* pparent = parent->_parent;

//旋转并更新_parent指针
subR->_left = parent;
parent->_parent = subR;

//若pparent为空则证明旋转的是一整棵树,因为根结点的_parent为空
if (pparent == nullptr)
{
//subR是新的根
_root = subR;
_root->_parent = nullptr;
}
//若pparent不为空,则证明旋转的是子树,parent上面还有结点
else
{
//让pparent指向子树旋转之后新的根
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
//同时也让新的根指向pparent
subR->_parent = pparent;
}
}

//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;

//旋转并更新_parent指针
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;

//先保存一下parent->_parent,因为下面会改它
Node* pparent = parent->_parent;

//旋转并更新_parent指针
subL->_right = parent;
parent->_parent = subL;

//若parent等于_root则证明旋转的是一整棵树(这也是一种判断方法)
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
//让pparent指向子树旋转之后新的根
if (parent == pparent->_left)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
//同时也让新的根指向pparent
subL->_parent = pparent;
}
}

private:
Node* _root = nullptr;
};


### 11.2 Test.cpp



#define _CRT_SECURE_NO_WARNINGS
#include
using namespace std;
#include <time.h>
#include “RBTree.h”
void RBTest1()
{
//int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int arr[] = { 95,47,32,29,7,7,2,50,74,30 };

RBTree t1;
for (auto e : arr)
{
t1.Insert(e);
}
t1.InOrder();

cout << t1.IsBlance() << endl;

}

void RBTest2()
{
srand((unsigned int)time(nullptr));
const int N = 100000;
RBTree t;
for (int i = 0; i < N; ++i)
{
int x = rand() + i;
t.Insert(x);
}
cout << t.TreeHeight() << endl;

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ert/311903982dea1d8a5d2c98fc271b5b41.jpeg)

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
[外链图片转存中…(img-4YAkxdc6-1713367655052)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值