In-depth understanding of the properties of red-black trees and the code implementation of node definition and Insert (detailed explanation with pictures and texts)

red black tree

concept

A red-black tree is a binary search tree, but a storage bit is added to each node to indicate the color of the node, which can be Red or Black. By restricting the coloring of each node on any one path from the root to the leaf, the red-black tree ensures that no path is twice as long as the other path , and thus is close to balanced (not strictly balanced).

nature

Properties 3 and 4 are the most important, and property 5 can be ignored

  1. Each node is either red or black.
  2. The root node is black.
  3. There are no consecutive red nodes, and if a node is red, its two children are black . But a node is black, its children can be black.
  4. For each node, the simple path from the node to all its descendant leaf nodes contains the same number of black nodes .
  5. Each NIL node/leaf node/empty node is black , where the leaf node== null node ==NIL node.

Satisfying the above properties, the red-black tree can guarantee that the number of nodes in the longest path will not exceed twice the number of nodes in the shortest path.

Shortest path : In extreme cases, it is the path of all black, or the number of black nodes that a path passes through.

Longest path : one black and one red path.

Discuss the concept that the red-black tree is close to balance

Worst case: The case of extremely unbalanced left and right is the worst, that is, a subtree is black and red 2 log 2 N 2log_2N2log2N , the other subtree is all blacklog 2 N log_2Nlog2N

The optimal situation: the left and right balance is the best, that is, when it is all black or each path is black and red and the tree is close to a full binary tree.

Code

node definition

In the definition of a node, the default color of the node is red.

Reason: If every new node is black, it must violate rule 4 (the rule that the number of black nodes on all paths is the same); and every time a new node is red, the parent node of the node is not necessarily red, It may be black, so it may violate rule 3 (no consecutive red nodes), so the default color is red.

enum Color { RED, BLACK };

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv, Color color = RED)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _color(color)
	{}

	RBTreeNode<K, V>* _left;  // 节点的左孩子
	RBTreeNode<K, V>* _right; // 节点的右孩子
	RBTreeNode<K, V>* _parent;// 节点的双亲
	KV _kv;				// 节点的值
	Color _color;		// 节点的默认颜色给成红色的
};

insert

Insert new nodes according to the binary search tree method, check whether the nature of the red-black tree is damaged after the new node is inserted, and exit directly if the rules are met, otherwise, the node color needs to be updated, and the red-black tree is rotated and colored to meet the requirements Rules for red-black trees.

Convention: cur is the current node, p is the parent node, g is the grandparent node, u is the uncle node

//父节点
parent = cur->_parent;
//祖父节点
grandparent = parent->_parent;
//叔叔节点
if(grandparent->left == parent)
    uncle = grandparent->right;
else
    uncle = grandparent->left;

3 cases of discoloration

Case 1: p is red, g is black, cur is red, u exists and is red. [p is red, g is black, cur is red ==> the while loop judges that the parent exists and the parent color is red. First, if the parent is red, it means that the grandparent must exist and be black (rule 2+rule 3); secondly, the color of the newly added node is red by default]

Solution:

  • Change p, u to black, and g to red.
  • Then update g and cur, give cur the original g, and give g the father of cur, and continue to adjust upwards.

1 p red g black u red cur, p, g in a straight line
1 p red g black u red cur, p, g in the broken line

Case 2: cur, p, and g are on a straight line (2.1 p is the left child of g, cur is the left child of p or 2.2 p is the right child of g, cur is the right child of p), p is red, and g is black, cur is red, u does not exist or u exists and is black. [p is red, g is black, cur is red ==> the while loop judges that the parent exists and the parent color is red. First, if the parent is red, it means that the grandparent must exist and be black (rule 2+rule 3); secondly, we default the color of the newly added node to be red. u does not exist or u exists and is black ==> judge inside the while loop]

Solution:

  • 2.1 p is the left child of g, and cur is the left child of p, then perform a right single rotation on the g node, then p becomes black, and g becomes red;
  • 2.2 p is the right child of g, and cur is the right child of p, then perform a left single rotation on the g node, then p becomes black, and g becomes red.
  • Then update g and cur, give cur the original g, and give g the father of cur, and continue to adjust upwards.

2 p red g black u black or there is no cur, p, g in a straight line

Case 3: cur, p, and g are on the broken line (3.1 p is the right child of g, cur is the left child of p or 3.2 p is the left child of g, cur is the right child of p), p is red, and g is black , cur is red, u does not exist or u exists and is black. [p is red, g is black, cur is red ==> the while loop judges that the parent exists and the parent color is red. First, if the parent is red, it means that the grandparent must exist and be black (rule 2+rule 3); secondly, we default the color of the newly added node to be red. u does not exist or u exists and is black ==> judge inside the while loop]

3 p red g black u black or there is no cur, p, g in the broken line

Solution:

  • 3.1 p is the left child of g, and cur is the right child of p, then perform a left single rotation on the p node, and then perform a right single rotation on the g node, cur becomes black, and g becomes red.
  • 3.2 p is the right child of g, and cur is the left child of p, then perform a right single rotation on the p node, and then perform a left single rotation on the g node, cur becomes black, and g becomes red.
  • Then update g and cur, give cur the original g, and give g the father of cur, and continue to adjust upwards.
bool Insert(const std::pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_color = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		// 1、找到合适的插入点
		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;
			}
		}
		// 2、开始插入以及更新节点颜色
		cur = new Node(kv);
		cur->_color = RED;//新插入的节点给红色
		//比父节点的值大,就插在右边
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//比父节点的值小,就插在左边
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		// 3、插入成功,更新节点颜色
		// 3.1 若父亲颜色为黑色,结束退出	
		// 3.2 若父亲颜色为红色,开始调整颜色
		while(parent && RED == parent->_color)
			//这个循环条件保证了grandparent一定存在,因为parent是红色,而根节点必须为黑色
			// 因为parent存在,且不是黑色节点,则parent一定不是根,则其一定有双亲
		{
			Node* grandparent = parent->_parent;
			//情况1和x.1
			if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				// 情况1: p为红,g为黑,cur为红,u存在且为红
				if (uncle && uncle->_color == RED)
				{
					//将p、u改为黑,g改为红
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandparent->_color = RED;
					//更新cur节点
					cur = grandparent;
					parent = cur->_parent;
				}
				//情况2、情况3
				else
				{
					//情况2.1
					if (cur == parent->_left)
					{
						RotateR(grandparent);

						parent->_color = BLACK;
						grandparent->_color = RED;
					}
					//情况3.1
					else
					{
						RotateL(parent);
						RotateR(grandparent);

						cur->_color = BLACK;
						grandparent->_color = RED;
					}
					break;
				}
			}
			情况1和x.2
			else
			{
				Node* uncle = grandparent->_left;
				// 情况1: p为红,g为黑,cur为红,u存在且为红
				if (uncle && uncle->_color == RED)
				{
					//将p、u改为黑,g改为红
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandparent->_color = RED;
					//更新cur节点
					cur = grandparent;
					parent = cur->_parent;
				}
				//情况2、情况3
				else
				{
					//情况2.2
					if (cur == parent->_right)
					{
						RotateL(grandparent);

						parent->_color = BLACK;
						grandparent->_color = RED;
					}
					//情况3.2
					else
					{
						RotateR(parent);
						RotateL(grandparent);

						cur->_color = BLACK;
						grandparent->_color = RED;
					}
					break;
				}
			}			
		}
		_root->_color = BLACK;//在修改grandparent的颜色时,有可能改掉了根的颜色,再循环外要改为黑色
		
		return true;
	}

performance

In terms of time complexity alone, the AVL tree is better than the red-black tree. However, the AVL tree requires a lot of performance to adjust the balance through rotation, while the red-black tree does not require strict balance, which reduces the performance consumption caused by rotation.

Guess you like

Origin blog.csdn.net/m0_61780496/article/details/129639147