C++: red-black tree

Table of contents

1. Introduction to red-black trees

1. The concept of red-black tree

2. Properties of red-black trees

Thinking: How to ensure that the longest path does not exceed twice the shortest path?

3. Small exercises

4.Heigao

5. Comparison between red-black trees and AVL trees

4. Add new nodes to red

5. Insertion operation of red-black tree

Scenario 1: Uncle Hong: Uncle changes color and becomes a new node. (cur is red, p is red, g is black, u exists and is red)

 (1) Specific situation 1

 (2) Specific case 2: equivalent to repeating specific case 1

Situation 1: Uncle Hei's LL type: right unirotation, father and grandfather change color. (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/u exists and is black, but cur is the left of p

 (1) Specific case 1: cur is red, p is red, g is black, u does not exist, but cur is the left of p

(2) Specific case 2: cur is red, p is red, g is black, u exists and is black, but cur is the left of p

Case 3: cur is red, p is red, g is black, u does not exist/u exists and is black, but cur is the right side of p

 (1) Specific case 1: cur is red, p is red, g is black, u does not exist, but cur is the right side of p

6. Check the legality of red-black trees

Red-black tree total code

2. Complete example of inserting red-black tree

1.Example

(1) Uncle Hei LL type. Right single rotation, father and grandfather change color.

(2) Uncle Hong: Uncle changes color and becomes a new node.

(3) Uncle Hei RR type. Left single rotation, father and grandfather change color.

(3) Uncle Hei LR type. Double rotation of left and right, son and grandpa will change color

2. Inferences related to “Heigao”

3. Delete operation (not tested)


 

 

1. Introduction to red-black trees

a9ac02b872b2422395a320671b4184a4.png

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 a leaf, a red-black tree ensures that there is no path
The path will be twice as long as the other paths , so it is close to equilibrium . ( The longest path is no more than 2 times the shortest path, approximately balanced )
 
 
14d05351b42d48bcbf431a237eb932a1.png

 

2. Properties of red-black trees

1. Each node is either red or black
2. The root node is black 
3. If a node is red, then its two child nodes are black  (no consecutive red nodes)
4. For each node, the simple paths from the node to all its descendant leaf nodes 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 ) NULL node
9df6b6e9bccb40bcb7a4f88552023f60.png

Thinking: How to ensure that the longest path does not exceed twice the shortest path?

Why can a red-black tree guarantee that the number of nodes in its longest path will not exceed twice the number of nodes in the shortest path if it satisfies the above properties?
 
The longest path does not exceed 2 times the shortest path.
Shortest: all black.
Longest: one black and one red interval.
 

3. Small exercises

1840b60eb3b94062a0ae91a7ce3080d5.png

 86388f4e729a471c8102bae8d442efa2.png

730abf6765ac45bbb9669a71d2c46287.png 

4.Heigao

ea9bce2f43954e629549f0b5e624a70c.png

5. Comparison between red-black trees and AVL trees

AVL tree height of 1 million data
: about 20 lines.
Red-black tree height: about 40 lines, but the number of rotations is much less.
 

4. Add new nodes to red

The newly added node is red, which may destroy 3. ( There are no consecutive red nodes )
The newly added node is black, which must destroy 4. ( each path contains the same number of black nodes ). Rule 4 is difficult to maintain,
so the newly inserted nodes are It is dyed red because there is a black null leaf node underneath it, so it is not a leaf node and does not need to be dyed black.

5. Insertion operation of red-black tree

Rule: (staining = discoloration)

c44536917b954dd5b71a828fa1be8459.png

 The leaves are all black null nodes, which are not drawn below.

Scenario 1: Uncle Hong: Uncle changes color and becomes a new node. ( cur is red, p is red, g is black, u exists and is red)

Corresponding to the situation of Uncle Hong above , the uncle changes color and becomes a new node. That is: uncle p and u turn black, and grandpa g turns red.

If g is a subtree, continue to treat g as cur and continue to adjust upward; if g is a root , then "g becomes a new node", that is, treat g as a new node and return to the above process to determine the new node. Conditions: The new node is the root - dyed black, the new node is not the root - dyed red. Then dye g black.

739ea3be9bc5453aa4a68161fd7cf768.png

 (1) Specific situation 1

If a, b, c, d, and e are all empty, then they are all black null leaf nodes.

5a15f32658ce46c0b022c0c5c07293a5.png

 (2) Specific case 2: equivalent to repeating specific case 1

Cur is a new node, change p and u to black, change g to red, regard g as cur, and continue to adjust upward. At this time, the g/cur node is regarded as a new node, and its father pp and uncle uu are changed to black, and gg is changed to red. Then if it is a subtree, it can continue to be adjusted upward like this; if it is a root

bbf686d8f25d4d64b8cbc6272cc65c57.png

 

d050c0eaa77a44fc95733c6b6da44bb5.png 

 

Situation 1: Uncle Hei's LL type: right unirotation, father and grandfather change color. ( cur is red, p is red, g is black, u exists and is red)

Corresponding to the situation of Uncle Black above , the uncle changes color and becomes a new node. That is: uncle p and u turn black, and grandpa g turns red.

If g is a subtree, continue to treat g as cur and continue to adjust upward; if g is a root , then "g becomes a new node", that is, treat g as a new node and return to the above process to determine the new node. Conditions: The new node is the root - dyed black, the new node is not the root - dyed red. Then dye g black.

Case 2: cur is red, p is red, g is black, u does not exist/u exists and is black , but cur is the left of p

 (1) Specific case 1: cur is red, p is red, g is black, u does not exist , but cur is the left of p

After p and g are rotated to the right, change p to black and g to red. Because the root node p of this local subtree is black, it has nothing to do with the color of the number above, so there is no need to continue to adjust upward .

861d6bf169bd46e0ae51d31ed33e6f49.png

(2) Specific case 2: cur is red, p is red, g is black, u exists and is black , but cur is the left of p

Through the above analysis, if u exists and is black, then cur must originally be black. As the grandfather of the subtree below, the situation changes. It was originally black and now turns red.

The operation is a right single rotation of p and g: the right of p is given to the left of g, then g is given to the right of p, and p is used as the root node. Finally, p and g change color, p turns black, and g turns red.

If p is the left child of g and cur is the left child of p, a right single rotation will be performed; on the contrary, if p is the right child of g and cur is the right child of p, a left single rotation will be performed.

 

9da98774cf0d4ea391740f221babef23.png

A red node is inserted into the subtree at a certain position in xymn, causing cur to change from black to red.
Subtrees such as xymnabd e must be red-black tree subtrees containing the same number of black nodes.

① to ② are the changes in case 1: cur (y position) is red, p is red, g is black, u exists and is red, ② to ③ are the changes where  cur is red, p is red, g is black, u If it exists and is black  , the operation is to rotate p, g to the right, g turns red, and p turns black.

83d0ca2326e34a80ba54767a4bf89c5a.png

Case 3: cur is red, p is red, g is black, u does not exist/u exists and is black , but cur is the right side of p

The difference between case two and case three

Case 2: p is the left of g, cur is the left of p - single rotation
Case 3: p is the left of g, cur is the right of p - double rotation

 

p is the left child of g , and cur is the right child of p , then perform a left single rotation on p ; on the contrary,
p is the right child of g , cur is the left child of p , then perform a right single rotation on p
It is converted into case 2
From ① to ②, p and cur perform left-handed rotation, from ② to ③, exchange cur and p, and finally convert to situation 2.
 

c0b2353bd2a248768c28b9f904531b96.png

 (1) Specific case 1: cur is red, p is red, g is black, u does not exist , but cur is the right side of p

Double rotation + color change

 577ac4f97a954ddf88366250ba4d9e17.png

 

 

  (2) Specific case 2: cur is red, p is red, g is black, u exists and is black , but cur is the right side of p

 258f09e128414879a81f0b54452cfb6b.png

 

 

6. Check the legality of red-black trees

1. Check the child when a red node is encountered, but it is recommended to check the father when a red node is encountered, which is easier to implement. (It is difficult to check the child when you encounter a red node, because the child may be empty or not) 2.
How to check the black node of each path? (There are 11 paths in the figure below, and one path is empty)
Preorder recursion Traverse the tree and find the number of black nodes in each path

8ae36be9c8ce435b84548268da323d69.png

// 检测是否有连续红色节点和各路径黑节点个数是否相同 的支线函数
    bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{//k用来记录每条路径中黑色节点的个数, blackCount是主要函数传进来的“任意一个路径的黑色
//节点个数”作基准值,只要有k和基准值比较不同就违反了性质四:每条路径中黑色节点的个数必须相同
		//走到null之后,k就是整个路径的黑色节点个数,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}

		// 统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;

		// 顺便检测当前节点与其双亲是否都为红色
	if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
		{
			cout << "违反性质三:存在连在一起的红色节点" << endl;
			return false;
		}

		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}
	bool IsBalanceTree()    //检查红黑树合法性的主要函数
	{
		// 检查红黑树几条规则

		Node* pRoot = _root;
		// 1.只要是空树就是红黑树
		if (nullptr == pRoot)
			return true;

		// 2.检测根节点是否满足情况
		if (BLACK != pRoot->_col)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}

		// 获取任意一条路径中黑色节点的个数 -- 比较基准值
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_col)
				blackCount++;

			pCur = pCur->_left;
		}

		// 检测是否有连续红色节点和各路径黑节点个数是否相同
		size_t k = 0;    //k用来记录路径中黑色节点的个数
		return _IsValidRBTree(pRoot, k, blackCount);
	}

 

 

 

Red-black tree total code

 

#pragma once

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)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
};

template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		// 1、搜索树的规则插入
		// 2、看是否违反平衡规则,如果违反就需要处理:旋转
		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 = new Node(kv);
		cur->_col = RED;  
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		// 存在连续红色节点
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);

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

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else // 叔叔不存在 或者 叔叔存在且为黑
				{
					if (cur == parent->_left) // 单旋
					{
						//     g
						//   p
						// c
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋
					{
						//     g
						//   p
						//     c 
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else //(grandfater->_right == parent)
			{
				Node* uncle = grandfater->_left;
				// 情况一:
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						// g
						//   p
						//     c 
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else // 双旋
					{
						// g
						//   p
						// c
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

	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;
	}

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

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

			subR->_parent = ppNode;
		}
	}

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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}

	}

	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;
	}


	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

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

	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}

		// 统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;

		// 检测当前节点与其双亲是否都为红色
		if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
		{
			cout << "违反性质三:存在连在一起的红色节点" << endl;
			return false;
		}

		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}

public:

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	void Height()
	{
		cout << "最长路径:" << _maxHeight(_root) << endl;
		cout << "最短路径:" << _minHeight(_root) << endl;
	}


	bool IsBalanceTree()    //检查红黑树合法性的主要函数
	{
		// 检查红黑树几条规则

		Node* pRoot = _root;
		// 1.只要是空树就是红黑树
		if (nullptr == pRoot)
			return true;

		// 2.检测根节点是否满足情况
		if (BLACK != pRoot->_col)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}

		// 获取任意一条路径中黑色节点的个数 -- 比较基准值
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_col)
				blackCount++;

			pCur = pCur->_left;
		}

		// 检测是否有连续红色节点和各路径黑节点个数是否相同
		size_t k = 0;    //k用来记录路径中黑色节点的个数
		return _IsValidRBTree(pRoot, k, blackCount);
	}

private:
	Node* _root = nullptr;
};

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();
}

2. Complete example of inserting red-black tree

1.Example

c44536917b954dd5b71a828fa1be8459.png

(1) Uncle Hei LL type. Right single rotation, father and grandfather change color.

Corresponding to the above situation of Uncle Hei's LL type , right-handed rotation, father and grandfather change color.

5 is the newly inserted node - son. At this time, parent 10 is the left L of parent 20, and son 5 is the left L of parent 10, so it is the black uncle LL type . Parent 10 and parent 20 rotate right, and the right of 10 is given to 20. On the left, give 20 to the right of 10, and 10 goes to grandpa's position. Change the color again, change the color of father 10 and grandpa 20.

12c94ec71cb64165b4f255025396be80.png

(2) Uncle Hong: Uncle changes color and becomes a new node.

30 is the newly inserted node - son. Let father 10 turn red, uncle 5 and father 20 turn black, father 10 become the new node. If father 10 is the root node, go back to the above conditions to judge the new node: the new node is Roots - dyed black, new nodes that are not roots - dyed red. Here, 10 is the root, so dye 10 black.

8bb5c9982c61479bb533eb167a3bd544.png

(3) Uncle Hei RR type. Left single rotation, father and grandfather change color.

Corresponding to the above situation of Uncle Hei's RR type , left single rotation, father and grandfather change color.

40 is the newly inserted node - the son. At this time, the father 30 is the right R of the father 20, the son 40 is the right R of the father 30, and the uncle is a null black node, so it is the black uncle RR type. The father 30 and the father 20 are left single rotation . , give the left of 30 to the right of 20, give 20 to the left of 30, and 30 will go to grandpa's place. Then change the color, change the color of father 30 and grandpa 20, 20 to red, 30 to black. Because the root node 30 of this local subtree is black, it has nothing to do with the color of the number above, so there is no need to continue to adjust upward .
8ebdb63baf304b9c8b03988b6208f598.png

It's Uncle Hong again: Uncle's color changes, and he becomes a new knot.


11563b00b83a44888c27148ad047c192.png
After turning "ye into a new node", you still need to continue to make judgments and adjustments according to the definition of red-black trees.
d216a07de4f9480099ee57aa8bc078b3.png
6c52b9c912d2450594adb9dbf4c6e275.png
05c62e075df7474bb8b9220118563288.png

(3) Uncle Hei LR type. Double rotation of left and right, son and grandpa will change color

Corresponding to the above situation of Uncle Hei's LR type , left and right double rotation, son and grandpa change color

23 is the newly inserted node - the son. At this time, the father 22 is the left L of the grandfather 25, the son 23 is the right R of the father 22, and the uncle is a null black node, so it is the black uncle LR type , performing left and right double rotation.


127a7cd0d6e5489ab7b59bc819f0a804.png

Father 22 and son 23 first make a left-hand rotation: give the left side of 23 to the right side of father 22, give the left side of 22 from father to son 23, and give the left side of son 23 to grandpa 25.
8f2317ea600445408825cc8c73dff725.png

Son 23 and grandpa 25 make a right spin: give the right of 23 to the left of 25, give 25 to the right of son 23, and give the right of son 23 to 20.


4321f75696dd43b0b54834ddc063c117.png

Change the color again, change the color of son 23 and grandpa 25, grandpa 25 will turn red, and son 23 will turn black. Because the root node 23 of this local subtree is black, it has nothing to do with the color of the number above, so there is no need to continue to adjust upward . ​​​​​​​
72c67ca1a6284eeb9e28113b5a858e67.png


The rest of the situations are similar, so I won’t go into details.
37ab56f67cd641e39012696e94f10cdc.png

2. Inferences related to “Heigao”

699ec707b87c4206aac0594da2c8a5ae.png
Internal nodes do not include leaf nodes, that is, NULL nodes.
d8d1a372bd1e479d9ddf47dd76d55b1c.png
81539f94e5cf4ebbbe875f0f80664e14.png

3. Delete operation (not tested)

ec448053b7eb4132a1dc7bd0e5c2ea03.png

 

 

 

 

 

 

 

 

 

 

 

 

Guess you like

Origin blog.csdn.net/zhang_si_hang/article/details/126453555