Advanced data structure - red-black tree

Table of contents

1. The concept of red-black tree

2. Properties of red-black trees

3. Red-black tree

6. Verification of red-black tree

7. Deletion of red-black trees

8. Comparison of red-black trees and AVL numbers

9. Application of red-black trees

10. Complete code

10.1 RBTree.h

10.2 test.cpp

1. The concept of red-black tree

A red-black tree is a binary search tree , but a storage bit is added to each node to represent the color of the node, which can be Red or Black. By restricting the coloring of each node on any path from the root to the leaf , the red-black tree ensures that no path will be twice as long as any other path ( the longest path does not exceed 2 times the shortest path ), so it is close to balanced .

 Both are binary search balanced trees, but the control of the AVL tree is much stricter than that of the red-black tree. If the absolute value of the balance factor of each node in the AVL tree does not exceed 1, it will lead to constant de-rotation and adjustment, and a relatively high cost will be incurred. The cost, and the red-black tree here is more like an approximate balance, and the conditions are not so harsh.

The following tree is balanced from the perspective of a red-black tree, but unbalanced from the perspective of an AVL tree and needs to be rotated and adjusted:

 However, from the perspective of search efficiency, the AVL tree is still better, because its balance standard is high, which makes it more balanced. With the same number of nodes, the height of the AVL tree will be lower. In addition to storing 1 million data, the AVL tree is about There are 20 layers (log100w), and the red-black tree can reach 40 layers at worst. Obviously the search efficiency of the AVL tree is high. But there is no difference between searching 20 times in the memory and searching 40 times, because the CPU is fast enough, so I will briefly mention it here.

2. Properties of red-black trees

1. Each node is either red or black.
2. The root node must be black .
3. If a node is red, then its two child nodes are black ( there are no consecutive red nodes )
4. For each node, there is a simple path from the node to all its descendant leaf nodes. , all contain the same number of black nodes ( each path contains the same number of black nodes )
5. Each leaf node is black (the leaf node here refers to the empty node-》NIL node)

According to these rules, how does the red-black tree ensure that the longest path does not exceed twice the shortest path?

First of all, we know from rule analysis that we assume that the number of black nodes in a path is N, then the longest path and the shortest path are as follows:

  • Shortest path: all black

  • The longest path: one black and one red interval

 The reason for the interval between one black and one red here is that the red-black tree does not allow continuous red nodes. In order to ensure the maximum number of nodes, only the interval between one black and one red can achieve the longest. In summary, when the number of black nodes is When the number is fixed to N, the number of shortest path nodes is N and the number of longest path nodes is 2N.

3. Red-black tree

Compared with the AVL tree, the implementation of the nodes here is still created as a KV model and a three-pronged chain structure . The only change is that red and black must be defined through enumeration, and the variable _col must be defined inside the node class. Indicates the node color , and finally remember to write the constructor .

enum Colour
{
	Red,
	Black,
};
//节点类
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)
	{}
};

Why are inserted nodes processed in red in the constructor?

  1. If it is processed as black, it will definitely lead to one more black node in the path where the newly inserted node is inserted, which no longer satisfies the property of having the same number of black nodes in each path, and will definitely destroy property 4, which is difficult to maintain at this time.
  2. If it is processed into red, the parent node may also be red. At this time, continuous red nodes appear, destroying property 3. However, we can adjust it upward at this time, but if the parent node is black, then there is no need to operate, and it does not violate of any nature.

Based on the pros and cons, inserting a black node will definitely destroy property 4 , while inserting a red node may destroy property 3 , so it is better to deal with it in red.

5. Insertion operation of red-black tree

The insertion operation of the red-black tree is mainly divided into these major steps:

  • 1. The tree is empty at the beginning, and new nodes are added directly;
  • 2. Start with a non-empty tree and find a suitable location for insertion;
  • 3. After finding the appropriate insertion position, perform a two-way link between the father and the child;
  • 4. Check whether the properties of the red-black tree cause damage after the new node is inserted.

Next, analyze them one by one:

  • 1. The tree is empty at the beginning, and new nodes are added directly:

Because the tree is empty, just create a newly inserted node and use it as the root_root, and then update the color_col to black.

  • 2. Start with a non-empty tree and find the appropriate location for insertion:

The idea here is the same as the idea of ​​finding a suitable insertion position in a binary search tree, and the following steps must be followed:

  1. Inserted value > node value, update to right subtree search
  2. Inserted value < node value, update to left subtree search
  3. Inserted value = node value, data redundancy insertion fails and returns false

When the loop ends, it means that the appropriate location for insertion has been found, and the next step of linking can be carried out.

  • 3. After finding the appropriate insertion position, perform a two-way link between the father and the child:

Note that the node here is composed of a three-way chain, so the final link between the backend child and the father is a two-way link. The specific operation is as follows:

  1. Inserted value > parent's value, link the inserted value to the right of the parent
  2. Inserted value < parent's value, link the inserted value to the left of the parent
  3. Because it is a three-pronged link, remember to have a two-way link after inserting it (child links to father)

Reaching this point means that the node has been inserted. Next, we need to adjust the color of the red-black tree.

  • 4. Check whether the properties of the red-black tree cause damage after the new node is inserted:

Not all situations require adjustment. When the parent of the inserted node is black (the default color of the new node is red), then there is no need to adjust because it does not destroy any properties of the red-black tree.

Only when the parent of the inserted node is red (the default color of the new node is also red), adjustment is needed, because at this time the inserted node and the parent are both red nodes, but the red-black tree does not allow consecutive red nodes. At this point it is time to make adjustments.

Note that since the parent p of the inserted node cur is red, then according to the properties of the red-black tree (the root node is black), its father's father g, that is, the grandfather must exist and must be black, then its father's brother node u (May not exist) That is, the uncle of the newly inserted node cur. Therefore, we agree: cur is the current node, p is the parent node, g is the grandparent node, and u is the uncle node .

The adjustment method here mainly depends on the color of the uncle node. Different uncle nodes will lead to three different situations that need to be adjusted:

  • Case 1: cur is red, p is red, g is black, u exists and is red
  • Case 2: cur is red, p is red, g is black, u does not exist
  • Case 3: cur is red, p is red, g is black, u exists and is black

Let’s discuss them separately:

  • Case 1: cur is red, p is red, g is black, u exists and is red

In order to avoid the appearance of continuous red nodes, we can turn the parent node p black, but in order to ensure that the number of black nodes in each path is the same, we need to turn the grandparent node g red (without affecting the number of black nodes in other paths) , and then turn uncle node u black.

The adjustment is not over yet. At this time, the grandfather node g is red. But what if this tree is a complete tree? That is, g is the root node, so you only need to turn node g into black.

If this tree is a subtree of a tree, then the grandfather node g is used as the newly inserted node cur and continues to be adjusted upward (continue to determine the father, uncle...) until the adjustment is completed.

Supplement: Case 1 does not care about the left-right relationship, it only changes color but does not rotate, so it does not matter whether p or u is to the left or right of g, and it does not matter whether cur is to the left or right of p.

Next, analyze situation 2:

  • Case 2: cur is red, p is red, g is black, u does not exist

 If node u does not exist, then cur must be a newly inserted node, because if cur is not a newly inserted node, then cur and p must have a node whose color is black, which does not satisfy property 4: the number of black nodes in each path is the same.

At this time, it is a very classic right-single rotation structure (the new node is inserted to the left side of the higher left subtree). We can perform a right-single rotation on it first, and then update the color . Specific steps are as follows:

  1. Let grandfather g become the right subtree of father p.
  2. The parent p serves as the root node.
  3. Update the parent node p to black.
  4. Update grandfather g to red.

  • Replenish:

If p is the right child of g and cur is the right child of p, perform a left single rotation on p. Example:

If the relationship between the three generations of ancestors and grandchildren is a polyline (the three nodes of cur, parent, and grandfather are one discount), then we need to perform a double rotation operation first, and then adjust the color . After the color adjustment, the root of the rotated subtree It's black, so there's no need to go any further up there. Example:

 To sum up:

  1. p is the left side of g, and cur is the left side of p, then a right single rotation is performed + p turns black and g turns red;
  2. p is the right side of g, cur is the right side of p, then the left monorotation + p turns black and g turns red;
  3. p is the left side of g, and cur is the right side of p, then the left and right double rotation + cur turns black and g turns red;
  4. p is the right side of g, and cur is the left side of p, then perform a right-left double rotation + cur turns black and g turns red.

Let’s enter situation three

  • Case 3: cur is red, p is red, g is black, u exists and is black

This situation never exists alone . It can never be the real insertion of a new node cur. Then there will also be situations where p is red, g is black, and u exists and is black. If it exists, it can only mean that the node or structure was previously inserted. There is a problem with the function because it does not conform to the properties of a red-black tree before insertion ( the number of black nodes in each path is the same ).

Now that situation three has appeared, it must be reasonable. It is a special situation that is based on situation one and continues to be adjusted upwards. Specifically, we will draw a picture to demonstrate:

 At this time, it is an obvious situation 3. cur is red, pp is red, gg is black, and u exists and is black. This proves that situation 3 is evolved through the upward adjustment of situation 1 . And this new node must be inserted or evolved from any left or right subtree of p and x, which will cause the subsequent cur to change from black to red.

At this time, it is a very classic right-single rotation structure (cur is on the left side of the higher left subtree). We can perform a right-single rotation on it first, and then update the color . Specific steps are as follows:

  1. Let the right subtree of p become the left subtree of g;
  2. Let p become the root node position;
  3. The right subtree of p points to g;
  4. Update the color of p to black;
  5. Update the color of g to red.

 Replenish:

If p is the right child of g and cur is the right child of p, perform left single rotation + color adjustment, example:

If the relationship between the three generations of ancestors and grandchildren is discounted (the three nodes of cur, parent, and grandfather are a polyline), then we need to perform a double rotation operation first, and then adjust the color. After the color adjustment, the root of the rotated subtree It's black, so there's no need to go any further up there. Example:

To sum up:

  • p is the left side of g, cur is the left side of p, then perform right single rotation + p turns black and g turns red;
  • p is the right side of g, and cur is the right side of p, then perform left-handed rotation + p becomes black and g becomes red;
  • p is the left of g, cur is the right of p, then perform left and right double rotation + cur becomes black and g becomes red;
  • p is the right side of g and cur is the left side of p, then perform a right-left double rotation + cur becomes black and g becomes red.

After case 2 and case 3 are rotated + changed, this subtree does not violate the red-black tree rules compared to before the insertion, and the number of black nodes remains unchanged, which will not affect the upper layer, and the processing is over.

code show as below:

bool Insert(const pair<K, V>& kv)
{
	//1、一开始为空树,直接new新节点
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = Black;//新插入的节点处理成黑色
		return true;
	}
	//2、寻找插入的合适位置
	Node* cur = _root;
	Node* parent = nullptr;
	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;//插入的值 = 节点的值,数据冗余插入失败,返回false
		}
	}
	//3、找到了插入的位置,进行父亲与插入节点的链接
	cur = new Node(kv);
	cur->_col = Red;//插入的节点处理成红色
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;//插入的值 > 父亲的值,链接在父亲的右边
	}
	else
	{
		parent->_left = cur;//插入的值 < 父亲的值,链接在父亲的左边
	}
	cur->_parent = parent;//三叉链,要双向链接
 
//4、检测新节点插入后,红黑树的性质是否造到破坏
	while (parent && parent->_col == Red)//存在连续的红色节点
	{
		Node* grandfather = parent->_parent;
		assert(grandfather);
		//先确保叔叔的位置
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;
			//情况一:cur为红,p为红,g为黑,u存在且为红
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
			else
			{
				if (cur == parent->_left)//p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红
				{
					//		  g
					//     p
					// cur
					RotateR(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else//p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红
				{
					//		  g
					//	 p
					//	     cur
					RotateLR(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;
			}
		}
		else//grandfather->_right == parent
		{
			Node* uncle = grandfather->_left;
			//情况一:cur为红,p为红,g为黑,u存在且为红
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
			else
			{
				if (cur == parent->_right)//p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红
				{
					//	g
					//	   p
					//	     cur
					RotateL(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else//p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红
				{
					//   g
					//	      p
					//	cur
					RotateRL(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;
			}
		}
	}
	_root->_col = Black;//暴力处理把根变成黑色
	return true;
}
//1、左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* ppNode = parent->_parent;//提前保持parent的父亲
	//1、建立parent和subRL之间的关系
	parent->_right = subRL;
	if (subRL)//防止subRL为空
	{
		subRL->_parent = parent;
	}
	//2、建立subR和parent之间的关系
	subR->_left = parent;
	parent->_parent = subR;
	//3、建立ppNode和subR之间的关系
	if (parent == _root)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}
		subR->_parent = ppNode;//三叉链双向链接关系
	}
}
//2、右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* ppNode = parent->_parent;
	//1、建立parent和subLR之间的关系
	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	//2、建立subL和parent之间的关系
	subL->_right = parent;
	parent->_parent = subL;
	//3、建立ppNode和subL的关系
	if (parent == _root)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;//三叉链双向关系
	}
}
//3、左右双旋
void RotateLR(Node* parent)
{
	RotateL(parent->_left);
	RotateR(parent);
}
//4、右左双旋
void RotateRL(Node* parent)
{
	RotateR(parent->_right);
	RotateL(parent);
}

6. Verification of red-black tree

The verification of red-black trees is mainly divided into two major steps:

  • 1. Check whether it satisfies the binary search tree (whether the in-order traversal is an ordered sequence)
  • 2. Check whether it meets the properties of a red-black tree

Next, they will be demonstrated separately:

  • 1. Check whether it satisfies the binary search tree (whether the in-order traversal is an ordered sequence):

Here you only need to recursively write an in-order traversal and determine whether the result of the test case is an ordered sequence to determine the binary search tree:

//验证是否为一颗搜索二叉树
void InOrder()
{
	_InOrder(_root);//调用中序遍历子树
	cout << endl;
}
//中序遍历的子树
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_kv.first << " ";
	_InOrder(root->_right);
}
  • 2. Check whether it meets the properties of a red-black tree:

Here you only need to determine whether the five rules of red-black trees are met. The specific operations are as follows:

  • 1. Whether the root node is black;
  • 2. Whether the number of black nodes in any path is the same (whether each recursive path is the same as the determined one);
  • 3. Recursively detect whether property three is violated and continuous red nodes appear.
bool IsBalanceTree()
{
	Node* pRoot = _root;
	// 空树也是红黑树
	if (pRoot == nullptr)
		return true;
	// 检测根节点是否满足情况
	if (pRoot->_col != Black)
	{
		cout << "违反红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}
	// 获取任意一条路径中黑色节点的个数-->拿最左路径作为比较基准值
	size_t blackCount = 0;
	Node* pCur = pRoot;
	while (pCur)
	{
		if (pCur->_col == Black)
			blackCount++;
		pCur = pCur->_left;
	}
	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
	//走到null之后,判断k和black是否相等
	if (pRoot == nullptr)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}
	// 统计黑色节点的个数
	if (pRoot->_col == Black)
		k++;
	// 检测当前节点与其双亲是否都为红色
	Node* pParent = pRoot->_parent;
	if (pParent && pParent->_col == Red && pRoot->_col == Red)
	{
		cout << "违反性质三:没有连在一起的红色节点,而这里出现了" << endl;
		return false;
	}
	return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
}

7. Deletion of red-black trees

The deletion of red-black trees is the same as that of AVL trees, so I won’t do too much demonstration. For details, please refer to "Introduction to Algorithms" or "STL Source Code Analysis". Boss blog post: Red-black tree insertion and deletion operations

8. Comparison of red-black trees and AVL numbers

Both red-black trees and AVL trees are efficient balanced binary trees. The time complexity of adding, deleting, modifying, and checking is O(logN). Red-black trees do not pursue absolute balance. They only need to ensure that the longest path does not exceed twice the shortest path. Relatively speaking, the number of insertions and rotations is reduced, so the performance is better than the AVL tree in structures where additions and deletions are often performed, and the implementation of red-black trees is relatively simple, so there are more red-black trees in actual use.

9. Application of red-black trees

  •  1、C++ STL库 -- map/set、mutil_map/mutil_set
  • 2. Java library
  • 3. Linux kernel
  • 4. Some other libraries 

10. Complete code

10.1 RBTree.h

#pragma once
#include<iostream>
#include<queue>
#include<vector>
#include<assert.h>
using namespace std;
enum Colour
{
	Red,
	Black,
};
//节点类
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)
	{}
};
//红黑树的类
template <class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
	//1、一开始为空树,直接new新节点
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = Black;//新插入的节点处理成黑色
			return true;
		}
	//2、寻找插入的合适位置
		Node* cur = _root;
		Node* parent = nullptr;
		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;//插入的值 = 节点的值,数据冗余插入失败,返回false
			}
		}
	//3、找到了插入的位置,进行父亲与插入节点的链接
		cur = new Node(kv);
		cur->_col = Red;//插入的节点处理成红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;//插入的值 > 父亲的值,链接在父亲的右边
		}
		else
		{
			parent->_left = cur;//插入的值 < 父亲的值,链接在父亲的左边
		}
		cur->_parent = parent;//三叉链,要双向链接

	//4、检测新节点插入后,红黑树的性质是否造到破坏
		while (parent && parent->_col == Red)//存在连续的红色节点
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			//先确保叔叔的位置
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一:cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
				else
				{
					if (cur == parent->_left)//p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红
					{
						//		  g
						//     p
						// cur
						RotateR(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else//p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红
					{
						//		  g
						//	 p
						//	     cur
						RotateLR(grandfather); 
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
			else//grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//情况一:cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
				else
				{
					if (cur == parent->_right)//p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红
					{
						//	g
						//	   p
						//	     cur
						RotateL(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else//p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红
					{
						//   g
						//	      p
						//	cur
						RotateRL(grandfather); 
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
		}
		_root->_col = Black;//暴力处理把根变成黑色
		return true;
	}
//验证是否为一颗搜索二叉树
	void InOrder()
	{
		_InOrder(_root);//调用中序遍历子树
		cout << endl;
	}
//验证是否为红黑树
	bool IsBalanceTree()
	{
		Node* pRoot = _root;
		// 空树也是红黑树
		if (pRoot == nullptr)
			return true;
		// 检测根节点是否满足情况
		if (pRoot->_col != Black)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}
		// 获取任意一条路径中黑色节点的个数-->拿最左路径作为比较基准值
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (pCur->_col == Black )
				blackCount++;
			pCur = pCur->_left;
		}
		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}

//求一棵树的高度
	void Height()
	{
		cout << "最长路径:" << _maxHeight(_root) << endl;
		cout << "最短路径:" << _minHeight(_root) << endl;
	}
private:
	//1、左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;//提前保持parent的父亲
		//1、建立parent和subRL之间的关系
		parent->_right = subRL;
		if (subRL)//防止subRL为空
		{
			subRL->_parent = parent;
		}
		//2、建立subR和parent之间的关系
		subR->_left = parent;
		parent->_parent = subR;
		//3、建立ppNode和subR之间的关系
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;//三叉链双向链接关系
		}
	}
	//2、右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;
		//1、建立parent和subLR之间的关系
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//2、建立subL和parent之间的关系
		subL->_right = parent;
		parent->_parent = subL;
		//3、建立ppNode和subL的关系
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;//三叉链双向关系
		}
	}
	//3、左右双旋
	void RotateLR(Node* parent)
	{
		RotateL(parent->_left);
		RotateR(parent);
	}

	//4、右左双旋
	void RotateRL(Node* parent)
	{
		RotateR(parent->_right);
		RotateL(parent);
	}
//中序遍历的子树
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
//求一棵树的最长路径的子树
	int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _maxHeight(root->_left);
		int rh = _maxHeight(root->_right);
		return lh > rh ? lh + 1 : rh + 1;
	}
//求一棵树的最短路径的子树
	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _minHeight(root->_left);
		int rh = _minHeight(root->_right);
		return lh < rh ? lh + 1 : rh + 1;
	}
//求是否满足红黑树性质的子树
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (pRoot == nullptr)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
		// 统计黑色节点的个数
		if (pRoot->_col == Black)
			k++;
		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && pParent->_col == Red && pRoot->_col == Red)
		{
			cout << "违反性质三:没有连在一起的红色节点,而这里出现了" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
	}
public:
		//层序遍历(非必须)
	vector<vector<int>> levelOrder() {
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;
		queue<Node*> q;
		int levelSize = 1;
		q.push(_root);
		while (!q.empty())
		{
			// levelSize控制一层一层出
			vector<int> levelV;
			while (levelSize--)
			{
				Node* front = q.front();
				q.pop();
				levelV.push_back(front->_kv.first);
				if (front->_left)
					q.push(front->_left);

				if (front->_right)
					q.push(front->_right);
			}
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;
			// 上一层出完,下一层就都进队列
			levelSize = q.size();
		}
		return vv;
	}

private:
	Node* _root = nullptr;
};

10.2 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"
void TestRBTree1()
{
	//int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	int a[] = { 30, 29, 28, 27, 26, 25, 24, 11, 8, 7, 6, 5, 4, 3, 2, 1 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.levelOrder();
	t.InOrder();
	t.Height();
}

void TestRBTree2()
{
	const size_t N = 1024 * 1024;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());
		v.push_back(i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}

	//t.levelOrder();
	//cout << endl;
	cout << "Ƿƽ? " << t.IsBalanceTree() << endl;
	t.Height();

	//t.InOrder();
}
int main()
{
	//TestRBTree1();
	TestRBTree2();
	return 0;
}

Guess you like

Origin blog.csdn.net/m0_49687898/article/details/131343995