Learning C++ No.20 [Red-black tree combat]

introduction:

Beijing time: 2023/5/12/20:30, today is Friday, starting from me, although I just woke up, but in terms of the length of study today, it may be the longest day in a long time. At 6:40 in the morning, I came back from a morning run and sat on a stool. I sat for a long time until 13:40 in the afternoon. Then I slept for 10 minutes and went to a psychology class. After the psychology class, I went to have a meal and returned to the dormitory at 16:20. , I downloaded a software for my classmates. It may have been a long time since I downloaded it. It took me a long time to get it done. I finally entered the learning state at 17:10. Until around 19:00, I almost finished the task at hand, and then I took a shower and it was like 19:08 when I got out of the shower. I finally set the alarm clock and just woke up. At this point in time, firstly, my roommate was coming back from an elective class that I didn’t have, and secondly, I was going to give my blog to To sum up, in this blog, we will learn about the relevant knowledge about red-black trees. Of course, the most important thing is the implementation of red-black tree-related interfaces, which will help us understand how high-level data structures are balanced and controlled, so as to O(log N)achieve High-speed traversal efficiency, and finally widely used in various real-world scenarios

insert image description here

Knowledge about non-code implementation of red-black tree

What is a red-black tree

In the last blog, we learned about searching binary trees and found that searching binary trees is a data structure with very high search efficiency, but in some scenarios, the left and right subtrees may be unbalanced, which in turn causes the search tree to degenerate into a linked list , the search efficiency is greatly reduced, so it is not difficult to deduce that the purpose of our learning red-black tree today is to solve this problem, so that the left and right subtrees can maintain a relative balance, and then keep the search efficiency at the left and right, and understand that O(log N), From the perspective of maintaining balance alone, the AVL tree similar to the red-black tree is the best balance for the search tree, because it can already control the height difference between the left and right subtrees at plus or minus one, but it is also Due to this strict control requirement, the AVL tree needs to continuously control the balance through rotation when inserting data, resulting in low efficiency in some scenarios, so people have created a new one based on the AVL tree. The method of controlling balance is the red-black tree we are going to learn today, so the essence of the red-black tree is to solve the problem of frequent rotation of the AVL tree

How does red-black tree make up for the deficiency of AVL tree

To understand this problem, we should first take a look at the specific concept of red-black tree :
red-black tree is first a search binary tree, but at this time because it adds a color object to the node, red ( RED) or black ( BLACK), and then control the balance of the search binary tree, so the red-black tree is also a balanced search binary tree, specifically by restricting the coloring method of each node on any root-to-leaf path , To ensure that no path will be twice as long as other paths, and then control the height in the interval of [logN, 2logN], and finally reduce the number of rotations

After understanding the basic concept of red-black tree, let’s get to the point at this time, that is, the rules of red-black tree. Needless to say, the person who made this rule must be a master among the masters. Of course, if we want to realize it by ourselves later Part of the interface of the red-black tree must first abide by these rules, otherwise the search tree we implement is not called a red-black tree, so these rules must be very clear, as follows:

1. Each node is either red or black
2. The root node must be black
3. If a node is red, its two child nodes can only be black
4. For each node, from the node The simple path from the point to all its descendant leaf nodes contains the same number of black nodes
5. Each leaf node is black (the leaf node here refers to the empty node)

When we read these rules for the first time, people must be very confused, because you have never seen what is a red-black tree, so at this time we need a picture as a reference, as shown in the following figure:
Red-black tree picture
With This picture can greatly reduce the learning difficulty for us to understand the nature of red-black trees! So at this time, let's take a look at what a red-black tree looks like through the above picture and the description of the above-mentioned red-black tree rules! First of all 性质1, through the above picture, we can see everything at a glance. Secondly 性质2, through the above picture, we can also find that the 13 node at this time is used as the root node, which is indeed black. Then, similarly, 性质3through the illustration, it is found that red The child nodes of the node are all black, and then 性质4, count and find that there are 11 paths in the red-black tree in the above figure, and each path has 2 black nodes regardless of its length. Finally Yes 性质5, an empty node in a red-black tree is called a leaf node, and each empty node is black, which is the NIL node shown in the figure above

Knowledge about red-black tree code implementation

Creation of red-black tree nodes

I won't say much about the importance of nodes in the data structure. That's it, the reason why there are various types of data structures is essentially because the nodes of each data structure are different. A node is equal to Determines the type of a data structure, that is to say, through the object in a node, we can know that the node is provided for that data structure type, that is, each data type has a unique node, so the node structure of the red-black tree is shown in the following code:
insert image description here
other nonsense, the first is the classic implementation method of the red-black tree three-point chain, so at this time, on the basis of the left and right child pointers of the binary tree, a The pointer of the parent node, followed by which representspair<K,V> _kv,Color _color; a template object with two data types. Two data types of any type can be constructed by means of templates. The purpose used in the red-black tree and the search binary tree are pairThe same, the essence is the key type and key/value type . For details, you can check in the previous blog. Secondly, _colorthe object is an enumeration type. Of course, you can also define it through #define. The specific enumeration structure is shown in the following code:

enum Color
{
    
    
	RED,
	BLACK
};

Finally, it is also the most important knowledge point, that is, the understanding _colorof the default initialization of objects in the constructor RED. What does it mean? If you haven't realized the seriousness of this problem, it can only mean that you don't know much about searching binary trees. It means that when we insert nodes, that is, when we apply for space on the heap area, At this time, you will default the color of each node created on the heap to red. The simple understanding is that if you insert a node, you do not restrict and change the corresponding color through the rules of the red-black tree. , then the search binary tree you built at this time is not a red-black tree, but a red-to-purple red-red tree . I am too full, so I put the picture below for you, as shown below:

insert image description here
But at this time, you will find a problem, that is, why must the default structure be red? Can't it be black? Hahaha, if you have this question, then I can only say, annoying, I have to type a lot of words, hahaha, no kidding, if you have this question, it can only show that you are a curious baby , and has higher wisdom than ordinary people. If you want to explain this problem, it will naturally involve the rules of red-black trees. Specifically, don’t worry about the rules. Let’s go slowly and review red-black trees 3 . 4 Two rules, rule 3 says that the child node of a red node must be black, rule 4 says that the number of black nodes in any path from the root node to the leaf node is the same, read it twice carefully , It is found that if rule 3 is violated, the consequences at this time are local (a certain subtree), while rule 4 is violated, then the consequences at this time are global (all paths). What does it mean? The simple understanding is that if rule 4 is violated, that is, when a node is inserted by default, a black node is inserted, which means that there is one more black node on a certain path, but the remaining paths in the red-black tree The black nodes have not changed, so the problem is very serious. It can also be understood that according to this rule, black nodes are randomly inserted in any path, and we know the corresponding path, but it is difficult for us to put the black nodes of each path All under control, the code implementation may be possible, but it is more complicated, more difficult, and has more scenarios, so we cannot violate rule 4 by default, but violate rule 3, so when inserting a node by default, it inserts red, and It cannot be black, so in the constructor, we use red.

Red-black tree insertion interface implementation

Continuing the above-mentioned understanding of the default construction of red by the constructor in the red-black tree node _color, at this time we officially enter the topic of this blog, and of course it is also the most painful time for me. When a data is constructed by the node constructor, this It will be inserted into the red-black tree at that time, similar to a lamb waiting to be slaughtered. After the sheep is processed (plucking, steaming), it is waiting to be served on the table and finally divided And eclipse, in the same way, at this time we need to insert the red node where it should be, that is, the appropriate place, but if you want to complete this task, you will first encounter two situations : 1.该红色结点成为了某一个黑色结点的孩子结点 2.该红色结点成为了某一个红色结点的孩子结点, Of course, if you are a fairy, you can imagine the third situation, if not, then it is the two situations I mentioned above! Hahaha!

As shown in the figure:
insert image description here
At this time, as shown in the figure above, it can be clearly seen that if a red node is linked behind the red node (left figure), the entire red-black tree is generally normal at this time, only the inserted There is a problem with the small subtree of the red node, because rule 3 is violated, the child node of the red node can only be black, and if a red node is linked after the black node (right picture), At this time, the whole red-black tree is normal and conforms to all the rules, so when inserting data, we only need to control the red nodes that are finally inserted after the red nodes. At this time, the whole red-black tree we It’s under control , how to control it, don’t worry, the control rules are very simple, but as a blogger, I need to draw pictures, so it’s very uncomfortable, painful for two seconds

In the same way, the following code: According to the characteristics of the search tree (insert the large value in the right subtree, and insert the small value in the left subtree), find a suitable empty position to insert data

insert image description here

Get the above knowledge, now enter the focus of this blog, of course, is the key to the realization of the red-black tree, that is, the secret of how the red-black tree controls the balance and height , it may still be a secret for you, but when Get this blog done, then everything will be no secret, the red-black tree is a naked beauty for you, you are full of unremitting, so next, we will divide into three scenes to get it done when we put a red knot The point is inserted into the child node of the red node, which is a violation of rule 3, as follows:

Simple comparison, leading to the scene

A:
insert image description here
B:
insert image description here
At this time, by comparing the above A and B , it can be found that in this scenario, the red insertion node must be the child node inserted in the red node, so it can be judged that when a problem occurs (two Red nodes are continuous), at this time, the parent node of the child node must be red, and the grandpa node must be black, because before inserting the red node, the tree is a red-black tree, which satisfies the requirements of the red-black tree. All the rules, so I understand that as long as it is a scene with continuous red nodes, the colors of the father node and grandpa node have been fixed, so if you want to distinguish and process different scenes, then you need to Depending on unclethe (uncle) node, it can also be confirmed from the figure that the two different scenarios are indeed due to the fact that unclethe nodes different, one exists and is red, and the other does not exist. After understanding this, we officially enter The division of different scenarios is as follows:

scene one: uncle结点存在且为红色

insert image description here
Note : a/b/c/d/e represent a subtree with a black node of n, and the tree may be a complete tree or a subtree. If it is a complete tree, then you need to Set the root node to black. If it is a subtree, then if the root node of the subtree is red, then you need to continue to judge the color of the parent node of the root node. If it is red, continue to change the color (premise Uncle exists and is red), if it is black, it breaks, indicating that the insertion is successful, so in the above scenario, if the parent node of the grandpa node is also red, then it needs to continue to change color upwards (provided that the uncle exists and is red), the corresponding cycle will not end until it is black, otherwise it needs to be judged through iteration. The following figure is a scene of secondary update and color change:

insert image description here
General: No matter from the above picture or the point of attention, we can find at this time that when inserting a red node and causing two red nodes to be continuous, the solution is to make the parent node and uncle node red, pparent The node becomes black, the essence is to use the red of pparent to replace the black of parent, so that the black nodes of each path remain equal, which meets the requirements of red-black tree

Scene two: uncle结点不存或者存在但为黑色(同侧)

1.uncle结点不存在:
insert image description here
As shown in the figure above, it can be found that when the uncle node does not exist, inserting the node at this time will cause the left subtree or the right subtree of the red-black tree to be significantly higher than the other subtree, which does not meet the longest path and does not exceed the shortest Twice the path, as shown in the figure above, the shortest path is 1, and the longest path is 3, so at this time, in violation of rule 3, the child node of the red node can only be black, and indirectly touched the red and black The bottom line principle of the tree is that the longest path is not more than twice the shortest path , so the solution at this time is to use 旋转+变色the specific rotation method as implemented in the AVL tree, which is divided into four types (left single rotation, right single rotation, Left and right double rotation, right and left double rotation) will be introduced one by one later, and then the color change. It is worth noting that the color change method at this time is different from the previous color change method with uncle nodes existing and red. The specific color change method needs to be based on The scene can be determined, but the only certainty is that after the rotation is completed, the color of the grandpa node must turn red, and the colors of the other nodes will not change unless they are used as the root node, as shown in the figure above As shown, the reason why the parent node turns black is because after the single rotation, it acts as the root node, so it will turn black, as shown in the following scene three, you can see that it does not need to become every time Black, becoming black is not determined by the identity of the node, but by the position of the node

2.uncle结点存在但为黑色
insert image description here
In the same way, the processing method of uncle nodes existing but being black is the same as that of uncle nodes not existing. , the 旋转+变色specific discoloration situation, let us get the third double-rotation scene, and we can draw a conclusion!

Scene three: uncle结点不存或者存在但为黑色(不同侧)

insert image description here
As can be seen from the figure above, it is not necessary to rotate every time the red node and the red node are on different sides. The key still depends on the situation of the uncle node. If it exists and is red, then let the parent and uncle directly in the same way The node turns black and the pparent turns red. Only as shown in the figure below, when the red node and the red node are on different sides, and the uncle node exists but is black, the double-rotation scene will occur. As can be seen in the figure above
insert image description here
, When the double-rotation scenario is satisfied, there is no color change during the rotation process. As long as the rotation is completed, the color change can be performed at this time. The grandpa node becomes red (fixed), and the cur node becomes red . becomes black (the double-rotation scene is fixed, but the rotation scene is not fixed) , so this is why in the above-mentioned single-rotation scene, we say that only the color change of the grandpa node is inherent in the rotation scene, while the colors of other nodes The change needs to depend on the position, the essence is because, if it is a single rotation, then the parent node is the root node at this time, and if it is a double rotation, the cur node is the root node at this time

General: In the scene where the uncle exists but is black or does not exist, the solution when the red node and the red node exist continuously at this time is to rotate, whether it is single-rotation or double-rotation, depending on the position of cur in the red father Whether the nodes are on the same side or different sides, one single rotation on the same side will complete the balance, otherwise double rotation, and understand that the color change of the single rotation is that the grandpa node turns red (solid) + the father node turns black (root), and the double rotation The color change of the spin is that the grandpa node turns red (solid) + the current node turns black (root)

Insert interface specific code implementation

Understand all the above knowledge, then you can say that the rules of the red-black tree and the specific insertion scenarios are a matter of course, now just let you see how the specific code is implemented, then the red-black tree at this time Plug in the interface, and you are all done, and the degree of completion is at least 90%. The specific code is as follows:

insert image description here

Red-black tree rotation process and detailed code explanation

insert image description here

How to detect whether the search tree is a red-black tree

After getting the above knowledge, all the knowledge about the red-black tree, whether it is scene analysis or code implementation, we are done. The last step is to check whether the search tree we implemented is a red-black tree. The easiest way It is to judge whether the red-black tree we implemented is a search tree according to the rules of the red-black tree, just like changing the college entrance examination paper. The way to judge whether a question is correct is to compare it with the standard answer, so the basic detection idea is as follows:

1. Determine whether the root node is black
2. Determine whether there are continuous red nodes
3. Determine whether the number of black nodes in each path is the same

code show as below:

insert image description here

Red-black tree basic implementation complete code (loop)

Remove base interface implementations other than interfaces:

#include<iostream>
#include<map>
#include<iostream>
#include<string>
#include<vector>
#include<set>
#include<cassert>
#include<time.h>

using namespace std;

enum Color
{
    
    
	RED,
	BLACK
};

template<class K,class V>
class RBTreeNode	
{
    
    
public:
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;//直接使用pair结构体就行,但是要记得把模板参数传过去初始化数据类型
	Color _color;

	RBTreeNode(const pair<K,V>& kv)//注意:pair是一个结构体,所以如果想要使用这个结构体就一定也要给模板参数,不然不能确定pair两个数据的类型
		:_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(RED)
	{
    
    }
};
//明白红黑树相当于AVL树的本质就是因为它旋转的更少

template<class K,class V>//注意:此时模板之间是以类对应的那个分号来分割开
class RBTree
{
    
    
	typedef RBTreeNode<K,V> Node;
public:
	RBTree()
		:_root(nullptr)
	{
    
    }
	~RBTree()
	{
    
    
		_Destory(_root);
		_root = nullptr;//好习惯
	}
	Node* Find(const K& key)
	{
    
    
		Node* cur = _root;
		while (cur != nullptr)
		{
    
    
			if (cur->_kv.first > key)
			{
    
    
				cur = cur->_left;
			}
			else if (cur->_kv.first < key)
			{
    
    
				cur = cur->_right;
			}
			else
			{
    
    
				return cur;
			}
		}

		return nullptr;
	}
	bool Insert(const pair<K, V>& kv)
	{
    
    
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			_root->_color = BLACK;//满足规则,_root结点插入,该结点对应的颜色一定是黑色
			return true;
		}
		// 并且明白,当不是root根结点时,新增一个结点应该给红色还是黑色
		// 如果新增黑色,那么面临的第一个问题就是,由于每一条路径的黑色结点数要相同,此时每一条路径都需要新增一个黑色结点
		// 而如果是新增红色,那么此时面临的问题就是,红色结点的孩子结点还是红色

		//所以默认规定,此时每次插入默认都是插入红色结点,因为这样造成的错误比较容易解决

		Node* cur = _root;
		Node* parent = nullptr;

		while (cur != nullptr)
		{
    
    
			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 = new Node(kv);//此时默认初始化的时候,创建的就是一个红色结点
		//cur->_color = RED;
		if (parent->_kv.first > kv.first)
		{
    
    
			parent->_left = cur;
		}
		else
		{
    
    
			parent->_right = cur;
		}
		cur->_parent = parent;

		//此时代码来到这里,红色结点就被我们成功的插入到某个结点下了(可能是红色的,也可能是黑色的,具体看情况)
		//如果是红色,那么此时就违反了红黑树的规则,此时需要通过判断进行控制住
		//具体原理,看同文件的那幅图处理就行
		//看图(去找一下就行了)

		while (parent != nullptr && parent->_color == RED)//因为变色可能会一直向上,直到遇到root结点,所以需要判断到root结点,也就是parent=nullptr的时候
		{
    
    
			//根据原理:1.将父结点和叔叔结点变成黑色,爷爷结点变成黑色(前提是uncle不为空)
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)//叔叔结点需要判断左右
			{
    
    
				Node* uncle = pparent->_right;
				//此时就找到叔叔结点了,根据原理,此时就需要进行判断
				if (uncle != nullptr && uncle->_color == RED)
				{
    
    
					//(uncle存在且为红)满足该条件,就走自己的变色规则(parent和uncle变红,pparent变黑)就行
					parent->_color = BLACK;
					uncle->_color = BLACK;
					pparent->_color = RED;
					//搞定完之后,再根据原理,需要判断爷爷结点的父结点是红色还是黑色(迭代循环走走)
					cur = pparent;
					parent = cur->_parent;
					//parent = pparent->_parent;//这种写法虽然更快,但是没有真正按照迭代的原理来,每一步都要按照原理来最好,容易看懂
				}
				//注意:此时上述代码判断的只是uncle结点的一种情况,此时uncle还可能有另外两种情况
				//1.不存在,为空
				//2.存在,但是为黑色
				//从图中可以得出结论,如果满足这两种情况,那么此时就是旋转+变色,如果是单旋的话,就让parent变黑,pparent变红,如果是双旋的话,就让cur变黑,pparent变红
				//并且注意:单旋和双旋是由cur的位置决定,同侧单旋,反之双旋
				else
				{
    
    
					//情况2+3(也就是单旋或者双旋场景)+变色(旋转不同,变色不同)
					//        g
					//      p   u
					//    c 或 c       
					if (parent->_left == cur)
					{
    
    
						//满足该条件就是一个单旋
						RotateR(pparent);
						parent->_color = BLACK;
						pparent->_color = RED;
					}
					else
					{
    
    
						//双旋
						RotateL(parent);
						RotateR(pparent);
						cur->_color = BLACK;//双旋会导致cur去做根,所以cur变黑,单旋由于parent做根,所以parent变黑
						pparent->_color = RED;
						//上面两步就是双旋变色的关键,下面这个变色可有可无
						parent->_color = RED;//这个只是为了保持红色而已,本质上没有变,最终让代码还可以进入循环进行判断,防止有的场景问题
					}

					break;//单旋或者双旋完,此时该子树的颜色就正常了,就可以退出该循环了
				}
			}
			else
			{
    
    
				//        g
                //      u  p
                //        c c
				Node* uncle = pparent->_left;
				if (uncle != nullptr && uncle->_color == RED)//同理
				{
    
    
					//符合变色规则,就开始变色
					parent->_color = BLACK;
					uncle->_color = BLACK;
					pparent->_color = RED;
					cur = pparent;
					parent = cur->_parent;
					//parent = pparent->_parent;//最好不要这样写,因为这样写,会导致cur的位置没有改变,迭代不了cur,只迭代了parent
				}
				else
				{
    
    //两个场景是类似的,可以当作一个场景看
					if (parent->_right == cur)
					{
    
    
						RotateL(pparent);
						parent->_color = BLACK;
						pparent->_color = RED;
					}
					else
					{
    
    
						RotateR(parent);
						RotateL(pparent);
						cur->_color = BLACK;
						pparent->_color = RED;
					}

					break;
				}
			}
		}
		//代码来到这里,表示出循环了,但是root结点可能会因为迭代被置成红色,所以此时需要来一步保底置黑
		_root->_color = BLACK;//此时就可以不需要判断,某个根结点的父结点是否为空,因为如果爷爷结点的父结点为黑色了,这个循环就会被终止,这步就是多余的,但是如果是真的走到了root结点,root结点被置红了,那么循环终止的这个代码就尤为重要

		return true;
	}

private:
	void RotateL(Node* parent)
	{
    
    
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		parent->_right = subRL;
		if (subRL != nullptr)
		{
    
    
			subRL->_parent = parent;
		}
		subR->_left = parent;
		parent->_parent = subR;
		if (pparent == nullptr)
		{
    
    
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
    
    
			if (pparent->_left == parent)
			{
    
    
				pparent->_left = subR;
			}
			else
			{
    
    
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
	}

	void RotateR(Node* parent)
	{
    
    
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;

		parent->_left = subLR;
		if (subLR != nullptr)
		{
    
    
			subLR->_parent = parent;
		}
		subL->_right = parent;
		parent->_parent = subL;
		if (pparent == nullptr)
		{
    
    
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
    
    
			if (pparent->_left == parent)
			{
    
    
				pparent->_left = subL;
			}
			else
			{
    
    
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
public:
	void _Destory(Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}

		_Destory(root->_left);
		_Destory(root->_right);
		delete root;
	}
	void InOrder()//中序打印AVL树
	{
    
    
		_InOrder(_root);
		cout << endl;
	}
	void _InOrder(Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}
		else
		{
    
    
			_InOrder(root->_left);//这边递归不要传参,你真的是人才啊
			cout << root->_kv.first << " ";
			_InOrder(root->_right);
		}
	}
	bool IsBalance()
	{
    
    
		return _IsBalance(_root);
	}
	bool _IsBalance(Node* root)
	{
    
    
		//1.检查根结点
		if (root != nullptr && root->_color == RED)//根结点等于黑色不能判断该树是一个红黑树,但是如果根结点是红色,那么这棵树肯定不是红黑树
		{
    
    
			cout << "_root结点不是黑色" << endl;
			return false;
		}
		//2.检查是否有连续的红色结点
		//原理就是找打红色结点,然后判断该结点的孩子结点或者父亲结点是否也是红色,是则违规,不是则该树可能是红黑树
		int reference = 0;
		Node* cur = root;
		while (cur != nullptr)
		{
    
    
			if (cur->_color == BLACK)
			{
    
    
				++reference;
			}
			cur = cur->_left;//表示我们找的基础值就是最左路径黑色结点的个数
		}
		return _Check(_root, 0, reference);
	}//3.计算黑色结点的数量

	bool _Check(Node* root,int black_num,int reference)//此时注意:这里没有使用引用,因为我们要的就是形参,让各个递归函数间的形参不会互相干扰导致累加,这样就可以获取到最终黑色结点的个数了
	{
    
          //第三个参数表示的是某一条路径的黑色结点个数
		if (root == nullptr)
		{
    
    
			//cout << black_num << endl;//此时表示的就是访问到叶子结点了,注意:此时不是平时返回,而是直接打印此时的black_num这个形参,并且由于递归传递的是形参,一个递归函数中的形参不会改变另一个递归函数的形参,所以最终这个递归函数访问到的就是黑色结点的个数
			if (reference != black_num)
			{
    
    
				cout << "某条路径的黑色结点数量不同" << endl;
				return false;
			}
			return true; //并且明白,只有在访问到到叶子结点为空的时候,才能看到个数,否则这个个数就消失了(具体可以画递归展开图)
		}
		if (root->_color == RED && root->_parent->_color == RED)//如果该结点是红色的,那么此时就去判断它的孩子结点是否为红色,但是此时这种方法不好,因为孩子结点可能不存在,所以我们就去判断它的父亲结点就行了
		{
    
    
			cout << "存在连续的红色结点" << endl;
			return false;
		}
		if (root->_color == BLACK)
		{
    
    
			black_num++;
		}

		return _Check(root->_left, black_num,reference) && _Check(root->_right, black_num, reference);//递归遍历去寻找连续的红色结点
	}//注意,每一层递归都有不同的栈帧,所以此时这个black_num是不会被继承的,假如此时是1,那么递归的下一个函数的black_num还是0
	
	int Helight()
	{
    
    
		return _Height(_root);
	}
	int _Height(Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return 0;
		}
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

private:
	Node* _root;
};


void testRedBlackTree1()
{
    
    
	RBTree<int, int> t1;
	int arr[] = {
    
     16, 3, 7, 11, 9, 26, 18, 14, 15, 4, 2, 6, 1, 3, 5, 15, 7, 16,14 };
	for (auto e : arr)
	{
    
    
		t1.Insert(make_pair(e, e));
	}//同理,不能说明什么问题,需要按照红黑树的规则去测试此时的这棵树是否符合

	t1.InOrder();

	t1.IsBalance(); 
}
void testRedBlackTree2()
{
    
    
	srand(time(0));
	const size_t N = 100000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
    
    
		size_t x = rand();
		t.Insert(make_pair(x, x));
	}
	//t.InOrder();
	cout << t.Helight() << endl;
	cout << t.IsBalance() << endl;
}
int main()
{
    
    
	//testRedBlackTree1();
	testRedBlackTree2();
}

insert image description here

Summary: Whether it is an AVL tree or a red-black tree, or the elementary data structure learned before, the code is not important, the important thing is always the idea, and understand that the type of data structure we implement ourselves has not been tested. Before, it was unreliable!

Guess you like

Origin blog.csdn.net/weixin_74004489/article/details/130649927