Hand torn red-black tree | Color change + rotation Do you really understand? [Super-intentional and super-detailed graphic explanation | An article to learn Red_Black_Tree]


say up front

We have not updated the data structure series for a long time. Half a year ago, the blogger relearned the data structure of the red-black tree in depth, and has always wanted to update it for everyone. Recently, there has been no time. Today, the red-black tree is here!

For this blog, the blogger has made a lot of preparations and tried a lot of drawing software, just to let everyone understand! I hope everyone will not be stingy with one button and three consecutive! !

foreword

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


What is a red-black tree?

Before learning the red-black tree, we must first understand and be familiar with the AVL tree:

4D hand torn AVL tree | Can you really know how to rotate hundreds of lines? [Super-intentional and super-detailed graphic explanation | An article to learn AVL] https://blog.csdn.net/Yu_Cblog/article/details/127698306?spm=1001.2014.3001.5501 Red-black tree is a binary search tree, but Add a storage bit on 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 the other path, so it is close to balance.

Therefore, the red-black tree maintains balance not directly by checking [no path will be twice as long as other paths], but by limiting the coloring of nodes on the path from the root to the leaf! Instead of directly controlling balance like AVL, it controls balance indirectly .

Properties of red-black trees

  • Each node is either red or black
  • the root node is black
  • If a node is red, its two child nodes are black
  • For each node, the simple path from the node to all its descendant leaf nodes contains the same number of black nodes
  • Each leaf node is black (leaf nodes here refer to empty nodes (NIL nodes))

Red-black tree node structure

Similarly, the red-black tree is also constructed using triple chains

enum Colour {
	RED,BLACK
};
template<class K,class V>
struct __Red_Black_TreeNode {
	__Red_Black_TreeNode<K, V>* _left;
	__Red_Black_TreeNode<K, V>* _right;
	__Red_Black_TreeNode<K, V>* _parent;
	pair<K, V>_kv;
	Colour _col;
	__Red_Black_TreeNode(const pair<K, V>& kv)
		:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv) {}
};

Insertion of red-black tree (emphasis)

First insert the node, the previous part is the same as the search tree and AVL, just find the position to insert:

	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 = new Node(kv);
		cur->_col = RED;//一开始尽量先变红
		if (parent->_kv.first < kv.first) {
			parent->_right = cur;
		}
		else {
			parent->_left = cur;
		}
		cur->_parent = parent;

        //判断是否需要调整颜色或旋转
		while (...) {
			//...
		}
        //根据规则,最后的根节点一定是黑色的
		_root->_col = BLACK;//最后无论根是红是黑 -- 都处理成黑
		return true;
	}

In the insertion part, we must keep in mind the two rules of the red-black tree, which is the most fundamental basis for us to insert nodes!

  • Rule 3: If a node is red, its children are black
  • Rule 4: For each node, the simple path from the node to all its descendant leaf nodes contains the same number of black nodes

Inserted new nodes are red by default

Now we want to insert a new node

We can divide into two cases:

  • The parent of the inserted node is black
  • The parent of the inserted node is red 

If the parent of the inserted node is black, we will violate rule 4 by inserting a black node. Inserting a red node is not a violation of the rule, so the node we should insert should be set to red.

If the parent of the inserted node is red, we would violate rule 4 by inserting a black node, and rule 3 by inserting a red node. What color node should we insert at this time?

The answer is that we should insert red nodes, and then do the subsequent color change work.

Reason: If we insert a black node, we violate rule 4, which is equivalent to the whole tree breaking the rule. And we violated rule 3, we can solve the problem by locally adjusting the color or rotating, so we choose to set the new node to red first, and then do the color change (+rotation) process.

Therefore, insert a new node in the red-black tree, no matter what the situation, first set it to red!

Color change and rotation

When we insert a red node, we have to check whether the red-black tree complies with the rules.

If the parent of the inserted node is black, it does not violate the rules of the red-black tree, and we don't need to deal with it.

Let's focus on the following: the case where the parent of the inserted node is red.

The nodes to be concerned about in the classification of red-black tree processing: father, grandfather and uncle (uncle is the father's brother node)

We can handle all the conditions of the red-black tree by grasping the father, grandfather and uncle. Among them, the color of the uncle is the most critical!

The following are three cases of red-black tree adjustment:

It is agreed that cur is the current node, p is the father node, g is the grandfather node, and u is the uncle node.

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

Handled as follows:

Tips: We can find that this situation does not need to look at the left and right. The cur is on the left or right of p, and the processing method is actually the same.

Case 2: cur is red, p is red, g is black, u does not exist/u is black (and cur, p, g are on the same line)

In case 1, when we blacken the father, we can pull the uncle into the water together and make the uncle black, so that we can ensure that the number of black nodes on the path remains consistent.

But now, the uncle does not exist or the uncle is already black, at this time it can only be rotated.

 

Explanation: There are two cases of u

  1. If the u node 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
  2. If the u node exists, it must be black, then the original color of the cur node must be black. Now the reason why it is red is because the cur subtree changes the color of the cur node from black to black during the adjustment process. red.

p is the left child of g, cur is the left child of p, then perform a right single rotation;

On the contrary, if p is the right child of g, and cur is the right child of p, perform left single rotation

p, g change color - p turns black, g turns red 

Because cur, p, and g are on the same straight line, only single rotation is required in case 2! The following situation requires double spin!

Case 3: cur is red, p is red, g is black, u does not exist/u is black (and cur, p, g are not on the same straight line)

p is the left child of g, cur is the right child of p, then do a left single rotation for p; on the contrary, if
p is the right child of g, cur is the left child of p, then do a right single rotation for p to
convert to the case 2 

Red-black tree deletion, etc.

At this point, some partners may ask, why talk about red-black trees and not delete those interfaces?

Because, school recruitment, company interview, and future work will basically not examine the deletion interface of AVL tree and red-black tree, we only need to master the insertion interface.

AVL tree and red-black tree are all for comprehension learning. We don’t need to tear all its codes by hand. This will cost a lot of time and have little meaning. When we learn red-black tree, we need to understand it in depth Structure, learning a plug-in interface, we can already do this very well.

Check if the red-black tree is legal

Idea: Find the number of black nodes in the leftmost downward path as the reference value, and check whether the number of black nodes in each path is equal to the reference value.

    bool prev_check(Node* root, int blackNum,int bench_mark) {
		if (root == nullptr) {
			if (blackNum != bench_mark)return false;
			return true;
		}
		if (root->_col == BLACK) {
			blackNum++;
		}
		if (root->_col == RED && root->_parent->_col == RED) {
			//存在连续的红节点,return false
			return false;
		}
		return prev_check(root->_left, blackNum, bench_mark) &&
			prev_check(root->_right, blackNum, bench_mark);
	}
	bool is_balance() {
		if (_root == nullptr)return true;
		if (_root->_col == RED)return false;
		//黑色节点数量基准值
		int bench_mark = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_col == BLACK)++bench_mark;
			cur = cur->_left;
		}
		return prev_check(this->_root, 0, bench_mark);
	}

RBTree.h

#pragma once
//#include<map>
#include<iostream>
#include<assert.h>
using namespace std;


enum Colour {
	RED,BLACK
};
template<class K,class V>
struct __Red_Black_TreeNode {
	__Red_Black_TreeNode<K, V>* _left;
	__Red_Black_TreeNode<K, V>* _right;
	__Red_Black_TreeNode<K, V>* _parent;
	pair<K, V>_kv;
	Colour _col;
	__Red_Black_TreeNode(const pair<K, V>& kv)
		:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv) {}
};


template<class K, class V>
struct RBTree {
	typedef __Red_Black_TreeNode<K, V>Node;
private:
	Node* _root = nullptr;
private:
	//左单旋
	void rotate_left(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL) {
			subRL->_parent = parent;
		}
		Node* ppNode = parent->_parent;//记录一下原先parent的parent
		subR->_left = parent;
		parent->_parent = subR;
		if (_root == parent) {
			_root = subR;
			subR->_parent = nullptr;
		}
		else {
			//如果ppNode==nullpt,是不会进来这里的
			if (ppNode->_left == parent) {
				ppNode->_left = subR;
			}
			else {
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
	}
	//右单旋
	void rotate_right(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 (_root == parent) {
			_root = subL;
			subL->_parent = nullptr;
		}
		else {
			if (ppNode->_left == parent) {
				ppNode->_left = subL;
			}
			else {
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
	}
	void _inorder(Node* root) {
		if (root == nullptr)return;
		_inorder(root->_left);
		cout << root->_kv.first << endl;
		_inorder(root->_right);
	}
	bool prev_check(Node* root, int blackNum,int bench_mark) {
		if (root == nullptr) {
			if (blackNum != bench_mark)return false;
			return true;
		}
		if (root->_col == BLACK) {
			blackNum++;
		}
		if (root->_col == RED && root->_parent->_col == RED) {
			//存在连续的红节点,return false
			return false;
		}
		return prev_check(root->_left, blackNum, bench_mark) &&
			prev_check(root->_right, blackNum, bench_mark);
	}
public:
	bool is_balance() {
		if (_root == nullptr)return true;
		if (_root->_col == RED)return false;
		//黑色节点数量基准值
		int bench_mark = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_col == BLACK)++bench_mark;
			cur = cur->_left;
		}
		return prev_check(this->_root, 0, bench_mark);
	}
public:
	void inorder() {
		this->_inorder(this->_root);
		cout << "\n";
	}
	//前面插入的过程和搜索树一样的
	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 = 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* grandparent = parent->_parent;
			assert(grandparent && grandparent->_col == BLACK);
			//关键看叔叔
			//判断一下左右
			if (parent == grandparent->_left) {
				Node* uncle = grandparent -> _right;
				//情况1(不看方向)
				if (uncle && uncle->_col == RED) {
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
					//继续向上处理
					cur = grandparent;
					parent = cur->_parent;
				}
				//情况2+3
				//uncle不存在/存在且为黑
				else {
					//情况2
					//   g
					//  p  u
					// c
					//右单旋+变色
					if (cur == parent->_left) {
						rotate_right(grandparent);
						parent->_col = BLACK;//父亲变黑
						grandparent->_col = RED;//祖父变红
					}
					//情况3
					//   g
					//  p  u
					//   c
					//左右双旋+变色
					else {
						rotate_left(parent);
						rotate_right(grandparent);
						//看着图写就行了
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
			else {
				Node* uncle = grandparent->_left;
				//情况1(不看方向)
				if (uncle && uncle->_col == RED) {
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
					//继续向上处理
					cur = grandparent;
					parent = cur->_parent;
				}
				else {
					//情况2
					//   g
					//  u  p
					//      c 
					//左单旋+变色
					if (cur == parent->_right) {
						rotate_left(grandparent);
						parent->_col = BLACK;//父亲变黑
						grandparent->_col = RED;//祖父变红
					}
					//情况3
					//   g
					//  u  p
					//    c
					//右左双旋+变色
					else {
						rotate_right(parent);
						rotate_left(grandparent);
						//看着图写就行了
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;//最后无论根是红是黑 -- 都处理成黑
		return true;
	}
};


void test1() {
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int>t1;
	for (auto e : a) {
		t1.insert(make_pair(e, e));
	}
	t1.inorder();
	cout << "is_balance():" << t1.is_balance() << endl;
}
void test2() {
	size_t N = 10000;
	srand((unsigned)time(nullptr));
	RBTree<int, int>t1;
	for (size_t i = 0; i < N; ++i) {
		int x = rand();
		t1.insert(make_pair(x, i));
	}
	cout << "is_balance():" << t1.is_balance() << endl;
}

Summarize

Seeing this, everyone should have a deeper understanding of the implementation of the red-black tree, the key point is its rotation. The blogger of this blog has spent a lot of thought on drawing pictures, and also invested a lot of time in drawing pictures. The next issue will bring you the encapsulation of map/set. I hope everyone can support a lot, one-click three links, like, follow, collect and comment, and then leave!

(Please indicate the author and source when reprinting. Do not use for commercial purposes without permission.)
For more articles, please visit my homepage

@backpack https://blog.csdn.net/Yu_Cblog?type=blog

Guess you like

Origin blog.csdn.net/Yu_Cblog/article/details/128210260