Data Structure: Red-Black Tree Explanation (C++)

1 Introduction

  • This article aims tounderstand the basic concepts of red-black trees and color-changing rotation rules, so as to understand C++map The underlying principles of and set do not cover the deletion operation of red-black trees.
  • For basic rotation operations (single rotation and double rotation), this article will not go into detail. The detailed explanation is here:
    AVL tree rotation explanation .



2. Brief description of red-black trees

2.1 Concept

The red-black tree is a binary search tree, but on each nodea storage bit is added 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 the longest path does not exceed twice the shortest path , so it is close to equilibrium.


2.2 Properties

  1. Each node is either red or black.
  2. The root node is black. (In order to reduce the number of rotations, you will understand the rotation later)
  3. For a red node, its children can only be black. (That is, no consecutive red nodes can appear on a path)
  4. Each path must contain the same number of black nodes.

Through the restrictions of the above rules, the longest path of the red-black tree will not exceed twice the shortest path, that is, Maintained a high degree of relative balance.
Combine 3 and 4 to look at the following two paths:
The longest: black, red, black, red, black, red... a>
Shortest: black, black, black…………



3. Insertion of red-black tree

3.1 About the color of newly inserted nodes

For the newly inserted node, we set it to red,The reason is that each path of the red-black tree must contain the same number of black nodes (Property 4), the newly inserted red node does not It will definitely destroy the structure of the red-black tree. The newly inserted black node will definitely not comply with the property 4 and it will be difficult to adjust.
Insert image description here


3.2 Definition of nodes

//用枚举来定义颜色
enum Color
{
    
    
	RED,
	BLACK
};

//这里直接实现key_value模型
template<class K, class V>
struct RBTreeNode
{
    
    
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;  //涉及到旋转,多加父亲指针来简化操作
	pair<K, V> _kv;  //存储键值对
	Color _col; //颜色
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		,_col(RED)  //新节点颜色为红色
	{
    
    }
};

3.3 Insert new node

This is relatively simple, just insert according to the rules ofbinary search tree:

bool Insert(const pair<K, V>& kv)
{
    
    
	if (_root == nullptr)
	{
    
    
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
    
    
		if (kv.first > cur->_kv.first) //待插入节点在右子树
		{
    
    
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)  //待插入节点在左子树
		{
    
    
			parent = cur;
			cur = cur->_left;
		}
		else  //相同
		{
    
    
			return false;
		}
	}
	cur = new Node(kv);
	if (kv.first > parent->_kv.first) //新节点在父亲右子树
	{
    
    
		parent->_right = cur;
	}
	else  //新节点在父亲左子树
	{
    
    
		parent->_left = cur;
	}
	cur->_parent = parent;  //记得更新父亲指针
	
	/// 变色旋转维持红黑树结构(暂时省略)  //
	
	_root->_col = BLACK; //可能改变根部颜色,保持根部为黑色
	return true;
}

3.4 Determine whether adjustment is needed after insertion

In fact, after inserting the red-black tree, you only need to look at the colors of the current node and the parent, and the new node must be red.

  1. The father is black, which complies with the rules and does not require adjustment.
  2. The father is red. At this time, red continuous nodes appear and need to be adjusted.

3.5 Maintain the red-black tree structure after insertion (key point)

For the convenience of description, we make the following definition:

  1. cur represents the current node
  2. p represents cur’s parent node
  3. u represents uncle node
  4. g represents the grandfather (father of p and u) node
3.5.1cur, p, u are red, g is black

Insert image description here
Code:

while (parent && parent->_col == RED)  //父亲为红就调整,调整到根部要结束
{
    
    
	Node* granderfather = parent->_parent;  //祖父
	//需要对叔叔进行操作,需要判断叔叔是祖父的左还是右
	if (parent == granderfather->_left)  //父亲是祖父的左子树
	{
    
    
		Node* uncle = granderfather->_right;
		if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
		{
    
    
			uncle->_col = parent->_col = BLACK;
			granderfather->_col = RED; 
			//当前子树可能为部分,继续向上调整
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
     
			//先省略
		}
	}
	else  //父亲是祖父的右子树
	{
    
    
		Node* uncle = granderfather->_left;
		if (uncle && uncle->_col == RED)  //叔叔不空并且为红
		{
    
    
			parent->_col = uncle->_col = BLACK;
			granderfather->_col = RED;  
			//当前可能为部分子树,需要继续上调
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
    
			// 先省略
		}
	}
}

3.5.2cur, p is red, g is black, u is empty/if u exists, it is black

Here is the rotation interface that will be used later:

void RotateL(Node* parent)  //左单旋,rotate->旋转
{
    
    
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;  //这个有可能为空
	Node* ppnode = parent->_parent;  //原来父亲的父亲

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

	SubR->_left = parent;
	parent->_parent = SubR;

	if (ppnode == nullptr)  //旋转的是整颗树
	{
    
    
		_root = SubR;
		SubR->_parent = nullptr;
	}
	else  //旋转的是部分
	{
    
    
		if (ppnode->_left == parent) //是左子树
		{
    
    
			ppnode->_left = SubR;
		}
		else  //是右子树
		{
    
    
			ppnode->_right = SubR;
		}
		SubR->_parent = ppnode;
	}
}

void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多
{
    
    
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;  //这个有可能为空
	Node* ppnode = parent->_parent;

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

	SubL->_right = parent;
	parent->_parent = SubL;

	if (ppnode == nullptr)  //旋转的是整颗树
	{
    
    
		_root = SubL;
		SubL->_parent = nullptr;
	}
	else  //旋转部分
	{
    
    
		if (ppnode->_left == parent)  //是左子树
		{
    
    
			ppnode->_left = SubL;
		}
		else  //右子树
		{
    
    
			ppnode->_right = SubL;
		}
		SubL->_parent = ppnode;
	}
}

The situation involving rotation is more complicated and will be discussed separately:

(1)p is the left child of g, cur is the left child of p
Insert image description here


(2)p is the left child of g, cur is the right child of p

Insert image description here


(3)p is the right child of g, cur is the right child of p

Insert image description here


(4)p is the right child of g, cur is the left child of p

Insert image description here

Integrate (1, 2, 3, 4) to get the following adjustment code:

//到这里插入新节点的工作完成,下面进行结构调整:
while (parent && parent->_col == RED)  //父亲为红就调整,调整到根部要结束
{
    
    
	Node* granderfather = parent->_parent;  //祖父
	if (parent == granderfather->_left)  //父亲是祖父的左子树,p为g的左孩子
	{
    
    
		Node* uncle = granderfather->_right;
		if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
		{
    
    
			uncle->_col = parent->_col = BLACK;
			granderfather->_col = RED; 
			//当前子树可能为部分,继续向上调整
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
     
			//     g
			//   p   u
			// c
			if (cur == parent->_left)  //当前为父亲的左子树,cur为p的左孩子
			{
    
    
				RotateR(granderfather);
				granderfather->_col = RED;
				parent->_col = BLACK;
			}
			else   //当前为父亲的右子树,cur为p的右孩子
			{
    
    
				//    g
				//  p   u
				//    c
				//左右双旋
				RotateL(parent);
				RotateR(granderfather);
				granderfather->_col = RED;
				cur->_col = BLACK;
			}
			break;  //这两种情况调整完可以结束
		}
	}
	else  //父亲是祖父的右子树,p为g的右孩子
	{
    
    
		Node* uncle = granderfather->_left;
		if (uncle && uncle->_col == RED)  //叔叔不空并且为红
		{
    
    
			parent->_col = uncle->_col = BLACK;
			granderfather->_col = RED;  
			//当前可能为部分子树,需要继续上调
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
    
			if (cur == parent->_right)  //当前为父亲的右,cur为p的右孩子
			{
    
    
				//    g
				//  u   p
				//        c
				//左旋
				RotateL(granderfather);
				parent->_col = BLACK;
				granderfather->_col = RED;
			}
			else  //当前为父亲的左,cur为p的左孩子
			{
    
    
				//   g
				// u   p
				//   c
				//右左双旋
				RotateR(parent);
				RotateL(granderfather);
				cur->_col = BLACK;
				granderfather->_col = RED;	
			}
			break;  //这两种情况调整完可以结束
		}
	}
}
_root->_col = BLACK; //保持根部为黑色



4. Some simple test interfaces

void InOrder()   //中序遍历,验证是否为二叉搜索树
{
    
    
	_InOrder(_root);
	cout << endl;
}

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

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

// 根节点->当前节点这条路径的黑色节点的数量
bool Check(Node* root, int blacknum, const int refVal)  
{
    
    
	if (root == nullptr)  //到根部看看当前路径黑色节点和标准值是否一致
	{
    
    
		//cout << balcknum << endl;
		if (blacknum != refVal)
		{
    
    
			cout << "存在黑色节点数量不相等的路径" << endl;
			return false;
		}

		return true;
	}

	/检查子比较复杂,可以反过来去检查红节点父是否为黑色
	if (root->_col == RED && root->_parent->_col == RED)  
	{
    
    
		cout << "有连续的红色节点" << endl;

		return false;
	}

	if (root->_col == BLACK)
	{
    
    
		++blacknum;  //为黑节点加一
	}

	return Check(root->_left, blacknum, refVal)
		&& Check(root->_right, blacknum, refVal);
}

bool IsBalance()
{
    
    
	if (_root == nullptr)
		return true;

	if (_root->_col == RED)
		return false;

	//参考值,即先算出一条路径的黑色节点数
	int refVal = 0;
	Node* cur = _root;
	while (cur)
	{
    
    
		if (cur->_col == BLACK)
		{
    
    
			++refVal;
		}

		cur = cur->_left;
	}

	int blacknum = 0;
	return Check(_root, blacknum, refVal);
}



5. Complete code

#pragma once
#include <iostream>
#include <utility>
using namespace std;

//用枚举来定义颜色
enum Color
{
    
    
	RED,
	BLACK
};

//这里直接实现key_value模型
template<class K, class V>
struct RBTreeNode
{
    
    
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;  //涉及到旋转,多加父亲指针来简化操作
	pair<K, V> _kv;  //存储键值对
	Color _col; //颜色
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		,_col(RED)  //新节点颜色为红色
	{
    
    }
};

template<class K, class V>
class RBTree
{
    
    
public:
	typedef RBTreeNode<K, V> Node;

	bool Insert(const pair<K, V>& kv)
	{
    
    
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
    
    
			if (kv.first > cur->_kv.first) //待插入节点在右子树
			{
    
    
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)  //待插入节点在左子树
			{
    
    
				parent = cur;
				cur = cur->_left;
			}
			else  //相同
			{
    
    
				return false;
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first) //在右子树
		{
    
    
			parent->_right = cur;
		}
		else
		{
    
    
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)  //父亲为红就调整,调整到根部要结束
		{
    
    
			Node* granderfather = parent->_parent;  //祖父
			if (parent == granderfather->_left)  //父亲是祖父的左子树
			{
    
    
				Node* uncle = granderfather->_right;
				if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
				{
    
    
					uncle->_col = parent->_col = BLACK;
					granderfather->_col = RED; 
					//当前子树可能为部分,继续向上调整
					cur = granderfather;
					parent = cur->_parent;
				}
				else  //叔叔为空或为黑色
				{
    
     
					//     g
					//   p   u
					// c
					if (cur == parent->_left)  //当前为父亲的左子树
					{
    
    
						RotateR(granderfather);
						granderfather->_col = RED;
						parent->_col = BLACK;
					}
					else   //当前为父亲的右子树
					{
    
    
						//    g
						//  p   u
						//    c
						//左右双旋
						RotateL(parent);
						RotateR(granderfather);
						granderfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
			else  //父亲是祖父的右子树
			{
    
    
				Node* uncle = granderfather->_left;
				if (uncle && uncle->_col == RED)  //叔叔不空并且为红
				{
    
    
					parent->_col = uncle->_col = BLACK;
					granderfather->_col = RED;  
					//当前可能为部分子树,需要继续上调
					cur = granderfather;
					parent = cur->_parent;
				}
				else  //叔叔为空或为黑色
				{
    
    
					if (cur == parent->_right)  //当前为父亲的右
					{
    
    
						//    g
						//  u   p
						//        c
						//左旋
						RotateL(granderfather);
						parent->_col = BLACK;
						granderfather->_col = RED;
					}
					else  //当前为父亲的左
					{
    
    

						//   g
						// u   p
						//   c
						//右左双旋
						RotateR(parent);
						RotateL(granderfather);
						cur->_col = BLACK;
						granderfather->_col = RED;	
					}
					break;
				}
			}
		}
		_root->_col = BLACK; //保持根部为黑色
		return true;
	}


/// //
/// /
/// 	测试代码
	 
	void InOrder()   //中序遍历,验证是否为二叉搜索树
	{
    
    
		_InOrder(_root);
		cout << endl;
	}

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

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

	// 根节点->当前节点这条路径的黑色节点的数量
	bool Check(Node* root, int blacknum, const int refVal)  
	{
    
    
		if (root == nullptr)  //到根部看看当前路径黑色节点和标准值是否一致
		{
    
    
			//cout << balcknum << endl;
			if (blacknum != refVal)
			{
    
    
				cout << "存在黑色节点数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		/检查子比较复杂,可以反过来去检查红节点父是否为黑色
		if (root->_col == RED && root->_parent->_col == RED)  
		{
    
    
			cout << "有连续的红色节点" << endl;

			return false;
		}

		if (root->_col == BLACK)
		{
    
    
			++blacknum;  //为黑节点加一
		}

		return Check(root->_left, blacknum, refVal)
			&& Check(root->_right, blacknum, refVal);
	}

	bool IsBalance()
	{
    
    
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
			return false;

		//参考值,即先算出一条路径的黑色节点数
		int refVal = 0;
		Node* cur = _root;
		while (cur)
		{
    
    
			if (cur->_col == BLACK)
			{
    
    
				++refVal;
			}

			cur = cur->_left;
		}

		int blacknum = 0;
		return Check(_root, blacknum, refVal);
	}


	int Height()
	{
    
    
		return _Height(_root);
	}

	int _Height(Node* root)  //求高度的
	{
    
    
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}


	Node* Find(K key)
	{
    
    
		return _Find(key, _root);
	}

	Node* _Find(K key, Node* root)
	{
    
    
		if (root == nullptr)
			return nullptr;
		if (key > root->_kv.first) //在右子树
		{
    
    
			return _Find(key, root->_right);
		}
		else if (key < root->_kv.first) //在左子树
		{
    
    
			return _Find(key, root->_left);
		}
		else  //找到了
		{
    
    
			return root;
		}
	}

private:
	Node* _root = nullptr;

	void RotateL(Node* parent)  //左单旋,rotate->旋转
	{
    
    
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;  //这个有可能为空
		Node* ppnode = parent->_parent;  //原来父亲的父亲

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

		SubR->_left = parent;
		parent->_parent = SubR;

		if (ppnode == nullptr)  //旋转的是整颗树
		{
    
    
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else  //旋转的是部分
		{
    
    
			if (ppnode->_left == parent) //是左子树
			{
    
    
				ppnode->_left = SubR;
			}
			else  //是右子树
			{
    
    
				ppnode->_right = SubR;
			}
			SubR->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多
	{
    
    
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;  //这个有可能为空
		Node* ppnode = parent->_parent;

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

		SubL->_right = parent;
		parent->_parent = SubL;

		if (ppnode == nullptr)  //旋转的是整颗树
		{
    
    
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else  //旋转部分
		{
    
    
			if (ppnode->_left == parent)  //是左子树
			{
    
    
				ppnode->_left = SubL;
			}
			else  //右子树
			{
    
    
				ppnode->_right = SubL;
			}
			SubL->_parent = ppnode;
		}
	}
};

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/134450719