Detailed explanation of the underlying structure of set/map - AVL tree and red-black tree

Table of contents

Preamble

One, AVL tree

1.1 What is an AVL tree?

1.2 Definition of AVL tree node

1.3 insert—insert (emphasis)

 1.4 Rotation (emphasis)

1.4.1 Right single spin

 1.4.2 Left single rotation

 1.4.3 Left and right double rotation

 1.4.4 Right-Left Birotation

 1.5 IsBalanc (balance judgment)

1.6 Inorder traversal

1.7 Testing

Second, the red-black tree

2.1 What is a red-black tree?

2.2 Properties of red-black trees

2.3 Definition of red-black tree node

2.4 insert

2.5 Insert adjustment

2.5.1 Case 1

2.5.2 Case 2

2.5.3 Case 3

2.6 IsBalance balance judgment

2.7 Inorder traversal and seeking height

2.7.1 Inorder traversal

2.7.2 Height

2.8 Testing

Three, AVL tree and red-black tree source code

3.1 AVL tree

3.2 Red-black tree

Summarize



Preamble

This article guides you to understand the underlying structure of set/map - AVL tree and red-black tree. If you are not familiar with set and map, it is recommended to learn the basic introduction and basic usage of set and map first.

One, AVL tree

1.1 What is an AVL tree?

Although the binary search tree can shorten the search efficiency, if the data is ordered or close to the order, the binary search tree will degenerate into a single branch tree, and searching for elements is equivalent to searching for elements in the sequence table, which is inefficient . Therefore, two Russian mathematicians GMAdelson-Velskii and EM Landis invented a method to solve the above problem in 1962: when a new node is inserted into the binary search tree, if the left and right subtrees of each node can be guaranteed If the absolute value of the height difference does not exceed 1 (the nodes in the tree need to be adjusted), the height of the tree can be reduced, thereby reducing the average search length .

The rules of the AVL tree are as follows

1. Its left and right subtrees are both AVL trees

2. The absolute value of the difference between the heights of the left and right subtrees (here we simply call it the balance factor) does not exceed 1 .

 If a binary search tree is height balanced, it is an AVL tree. If it has n nodes, its height can be maintained at logN, and the search time complexity is logN .

1.2 Definition of AVL tree node

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

The node we use is in the form of a three-fork chain, which is similar to the previous search for a binary tree, but there is an additional balance factor _bf

(balance factor = right subtree height - left subtree height)

1.3 insert—insert (emphasis)

First of all, the basic logic of inserting here is similar to searching a binary tree

1. If the tree is empty, directly create a new node to the root

2. The tree is not empty, find an empty position through the rules of binary tree search, and insert it

 

But there is one more step here, and it is also the most important

That is to adjust the balance factor

The rules for updating the balance factor are as follows:

1. cur is on the right side of the parent node, the balance factor of the parent node++

2. cur is on the left of the parent node, the balance factor of the parent node --

Then we need to update the balance factor upwards due to the change of the balance factor. The update rules are as follows :

a. bf == -1 || bf == 1 : It means that the balance factor of this node has become 1/-1, so the balance factor of this node must be 0, and it needs to be updated upwards at this time

b. bf == -2 || bf == 2: Indicates that the left and right subtrees of the node have broken balance and need to be rotated and adjusted

c. bf == 0: It means that the height of the node is exactly equal after insertion, no need to update upwards

 1.4 Rotation (emphasis)

The essence of the AVL tree is to maintain the balance of the left and right subtrees through rotation , so as to prevent the height of the tree from being crooked. In the above insertion, after updating the balance factor, the situation b. bf == -2 || bf == 2 needs to be performed Rotate to keep the left and right subtrees in balance. First let's understand the reason and purpose of rotation

Reason for rotation: When the balance factor is greater than 1 or less than -1, the left and right subtrees are unbalanced

The purpose of rotation: to reduce the height of the tree, maintain the balance of the left and right subtrees, while maintaining the nature of the search binary tree

Rotation is mainly divided into the following four situations

 Next, we will explain the occurrence conditions of the following rotations and the specific implementation of the rotation one by one.

1.4.1 Right single spin

 As shown in the figure, when a new node is inserted to the left side of the higher left subtree, it will cause the left and right imbalance of the parent, and right single rotation adjustment is required at this time

First of all, we know according to the nature of the binary search tree: parent>subL, subL<subLR<parent

The rotation steps are as follows:

1. parent becomes the right side of subL

2. subLR becomes the left side of parent

After the above rotation , the height of the tree is reduced, the balance of the left and right subtrees is maintained, and the nature of the binary search tree is maintained.

The code is implemented as follows (with more detailed comments):

 1.4.2 Left single rotation

 As shown in the figure, when a new node is inserted into the right side of the higher right subtree , that is, a new node is inserted at the position of c, it will cause the left and right subtrees of the parent to become unbalanced, and left single-rotation adjustment is required at this time

First of all, we know according to the nature of the search binary tree: parent<subR, parent<subRL<subR

Rotation steps:

1. parent becomes the left tree of subR

2. subRL becomes the right subtree of parent

After the above rotation , the height of the tree is reduced, the balance of the left and right subtrees is maintained, and the nature of the binary search tree is maintained.

The code is as follows (because the left and right are similar, we will not comment in detail here):

 1.4.3 Left and right double rotation

 As shown in the figure above, when a new node is inserted into the right side of a higher subtree, that is, inserted in b or c, it will cause the left and right subtrees of the parent to be out of balance. At this time, left and right double rotation is required

 Specific steps are as follows:

1. Use subL as the parent to perform left single rotation

2. Take the parent as the parent for right single rotation

3. Adjust the balance factor

Attention should be paid to the step of adjusting the balance factor:

a. If the balance factor of subLR is -1 after the new node is inserted, then the balance factor of the last parent is 1.

b. If the balance factor of subLR is 1 after the new node is inserted, then the balance factor of subL is -1.

c. If the balance factor of subLR is 0 after the new node is inserted, no special adjustment is required

code show as below:

 1.4.4 Right-Left Birotation

  As shown in the figure above, when a new node is inserted to the left of a higher subtree, that is, inserted at b or c, it will cause the left and right subtrees of the parent to be unbalanced, and right-left double rotation is required at this time

 Specific steps are as follows:

1. Use subR as the parent for right single rotation

2. Take the parent as the parent to perform left single rotation

3. Adjust the balance factor

Attention should be paid to the step of adjusting the balance factor:

a. If the balance factor of subRL is -1 after the new node is inserted, then the balance factor of subR is 1.

b. If the balance factor of subRL is 1 after the new node is inserted, then the balance factor of the last parent is -1.

c. If the balance factor of subRL is 0 after the new node is inserted, no special adjustment is required

code show as below:

 1.5 IsBalanc (balance judgment)

According to the nature of the AVL tree, we only need to judge whether the height difference between the left and right subtrees is greater than 1. Here we are taking an in-order traversal

    //检查树的高度是否平衡
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
    //树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}
	//检查树的高度是否平衡
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return abs(Rheight - Lheight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);

	}

1.6 Inorder traversal

//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
//中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

1.7 Testing

After the general framework is completed, let's do a simple test

void Test_AVLTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
			{
			int x = 0;
			}*/

		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

// 10:35继续
void Test_AVLTree2()
{
	srand(time(0));
	const size_t N = 500000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.TreeHeight() << endl;
}

 Ok, the test passed

Second, the red-black tree

After talking about the AVL tree, we are going to talk about the absolutely classic red-black tree

2.1 What is a red-black tree?

Red Black Tree (Red Black Tree) is a self-balancing binary search tree. It is a data structure used in computer science . The typical use is to implement associative arrays . Red-black trees were invented by Rudolf Bayer in 1972 , when they were called balanced binary B-trees (symmetric binary B-trees). Later, in 1978, it was modified by Leo J. Guibas and Robert Sedgewick into today's "red-black tree". The red-black tree is a specialized AVL tree ( balanced binary tree ), which maintains the balance of the binary search tree through specific operations during insertion and deletion operations, thereby obtaining high search performance. Although it is complex, its worst-case running time is also very good, and it is efficient in practice: it can do lookup, insertion and deletion in O(log n) time, where n is the number of nodes in the tree number of elements. 

Specifically, it 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 constraining the way each node is colored on any path from the root to a leaf, a red-black tree ensures that no path is twice as long as any other path, and thus is nearly balanced .

2.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, its two child nodes are black (emphasis)
4. For each node, from the The simple path from a node to all its descendant leaf nodes contains the same number of black nodes (emphasis)
5. Each leaf node is black (the leaf node here refers to an empty node)

So how does the red-black tree guarantee according to the above properties that the longest path is not more than twice the shortest path

 The figure above is a simple red-black tree without red nodes, and the number of black nodes in each path is the same. Suppose we add red nodes in the leftmost path, but because of rule three, red nodes cannot be added consecutively, so at most Add three, that is to say, under the condition of ensuring that it is a red-black tree, the shortest path can only reach twice the original path, so it is guaranteed that when the node tree is N, all the nodes in the red-black tree The path lengths are all in the range of [logN, 2logN], so as to achieve a close balance

2.3 Definition of red-black tree node

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

Compared with the AVL tree, the node of the red-black tree has one less balance factor, and one more enumeration type Colour to represent the color. Note that the new node is inserted here, and the red is inserted by default, so as to avoid breaking rule 4 and facilitate adjustment

2.4 insert

First of all, the basic logic of inserting here is similar to searching a binary tree

1. If the tree is empty, directly create a new node to the root

2. The tree is not empty, find an empty position through the rules of binary tree search, and insert it

3. Make adjustments according to the situation

 

It needs to be adjusted after the insertion here, and the adjustment is as follows:

1. If the parent node of the inserted node is black, the rules are not broken, just pat your ass and leave

2. If the parent node of the inserted node is red, the rules are broken, and color change or rotation adjustment is required

2.5 Insert adjustment

Note: cur is a newly inserted node, the parent node of cur is called parent, and the grandparent node of cur is called grandfather, and uncle needs to be marked

The adjustment can be roughly divided into three situations (the following situations are all based on the example of cur being inserted into the left tree of the grandfather, and the right tree can be reversed):

Case 1: cur is red, parent is red, uncle is red

Case 2: cur is red, parent is red, uncle is black/uncle does not exist, cur is the left child of parent

Case 3: cur is red, parent is red, uncle is black/uncle does not exist, cur is the right child of parent

2.5.1 Case 1

Processing method: Just change the color, turn p and u black, and g red, note that the tree here may be a subtree, or it may be a complete tree, if it is a complete tree, then turn g black, if not then Continue to adjust upwards, because the redness of g may affect the above

2.5.2 Case 2

 Processing method: rotation + color change, first rotate right with g as the parent node (if it is a right tree, perform left rotation), then p turns black, and g turns red.

2.5.3 Case 3

Processing method: double rotation + color change

1. cur belongs to the left subtree of g: first, the parent is used as the root node for left-hand rotation, which converts to case 2, then right-hand rotation is performed with g as the root node, and finally the color is changed, cur turns black, and g turns red

2. cur belongs to the right subtree of g: it first rotates to the right and then to the left, and finally changes color. I won’t repeat the details, the details are similar to the above

All the code adjusted is as follows:

//调整

		while (parent && parent->_col == RED)
		{
			//cur在g的左子树上
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的左子树,右单旋+变色
					if (cur == parent->_left)
					{
						_RotateR(grandfather);//右单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的右子树,左单旋+右单旋+变色
					else
					{
						_RotateL(parent);//左单旋
						_RotateR(grandfather);//右单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
			//cur在g的右子树
			else
			{
				Node* uncle = grandfather->_left;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的右子树,左单旋+变色
					if (cur == parent->_right)
					{
						_RotateL(grandfather);//左单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的左子树,右单旋+左单旋+变色
					else
					{
						_RotateR(parent);//右单旋
						_RotateL(grandfather);//左单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
		}

2.6 IsBalance balance judgment

We also judge the balance of the red-black tree according to the rules:

1. Each node is either red or black
2. The root node is black 
3. If a node is red, its two child nodes are black (emphasis)
4. For each node, from the The simple path from a node to all its descendant leaf nodes contains the same number of black nodes (emphasis)
5. Each leaf node is black (the leaf node here refers to an empty node)

 The inspection method is as follows: one-to-one correspondence

1. No need to check

2. Check if the root node is black

3. Rule three means that there can be no continuous red, check the parent of each red node, if the parent is also red, it violates the rule

4. Use pass-by-value to pass parameters, record the number of black nodes on the path of the node, and then compare it with the number of black nodes on the first path. If they are not equal, it violates the rules

5. No need to check

 code show as below

//检查是否平衡/是否符合红黑树规则
	bool IsBalance()
	{
		int FPathNumber = -1;
		return _check(_root,0,FPathNumber);
		
	}
    //检查
	bool _check(Node* root,int BlackNumber,int& FPathNumber)
	{
		//检查规则二,检查根节点
		if (_root->_col == RED)
		{
			cout << "根节点不为黑,违反规则二" << endl;
			return false;
		}

		if (root == nullptr)
		{
			//检查路径黑色节点是否相等
			if (FPathNumber == -1)
			{
				FPathNumber = BlackNumber;
			}

			if (FPathNumber != BlackNumber)
			{
				cout << "路径的黑色节点数量不同,违反规则四" << endl;
				return false;
			}
			return true;
		}

2.7 Inorder traversal and seeking height

2.7.1 Inorder traversal

    //中序遍历
	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.7.2 Height

    //树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
    //树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}

2.8 Testing

The test code is as follows

void Test_RBTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
		//cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.TreeHeight() << endl;
}

 Test completed without error

Three, AVL tree and red-black tree source code

3.1 AVL tree

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <time.h>
using namespace std;
//avl树的实现
//结点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//连接完成,更新平衡因子
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			//因子为1或-1,继续向上调整
			if (parent->_bf == -1 || parent->_bf == 1)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			//因子为-2或者2,说明左右子树已经打破平衡,需要进行调整
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//进行旋转调整
				//左旋
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					_RotateL(parent);
				}
				//右旋
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					_RotateR(parent);
				}
				//左右双旋
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					_RotateLR(parent);
				}
				//右左双旋
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					_RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				break;
			}
			//因子为0,则左右子树插入后正好平衡,无需调整
			else if (parent->_bf == 0)
			{
				break;
			}
			//其他值,插入错误
			else
			{
				assert(false);
			}
		}

		return true;


	}
	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//检查树的高度是否平衡
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
private:
	//树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}
	//检查树的高度是否平衡
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return abs(Rheight - Lheight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);

	}



	//中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

	//右单旋
	void _RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* ppnode = parent->_parent;

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

		parent->_left = subLR;
		//判断subLR不为空
		if (subLR)
			subLR->_parent = parent;

		//判断parent是否为根节点
		if (parent==_root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = ppnode;
			if (ppnode->_right == parent)
			{
				ppnode->_right = subL;
			}
			else
			{
				ppnode->_left = subL;
			}
		}
		parent->_bf = subL->_bf = 0;
	}

	//左单旋
	void _RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		Node* ppnode = parent->_parent;//记录parent的父节点后续根据ppnode调整subR的父节点

		parent->_right = subRL;
		if (subRL)//判断subRL是否不为空,为空则不能非法访问
			subRL->_parent = parent;

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

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

		parent->_bf = subR->_bf = 0;
	}

	//左右双旋
	void _RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//记录subLR的平衡因子后续需要根据这个平衡因子调整subL parent的平衡因子
		int bf = subLR->_bf;
		//左旋
		_RotateL(parent->_left);
		//右旋
		_RotateR(parent);

		if (bf == -1)
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			subL->_bf = -1;
			parent->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 0)
		{
			subL->_bf = 0;
			parent->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(subLR);
		}
	}

	//右左双旋
	void _RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;

		//右旋
		_RotateR(parent->_right);
		//左旋
		_RotateL(parent);

		//调整平衡因子
		if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(bf);
		}
	}

private:
	Node* _root;
};

3.2 Red-black tree

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <time.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:
	RBTree()
		:_root(nullptr)
	{}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	//插入
	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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//调整

		while (parent && parent->_col == RED)
		{
			//cur在g的左子树上
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的左子树,右单旋+变色
					if (cur == parent->_left)
					{
						_RotateR(grandfather);//右单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的右子树,左单旋+右单旋+变色
					else
					{
						_RotateL(parent);//左单旋
						_RotateR(grandfather);//右单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
			//cur在g的右子树
			else
			{
				Node* uncle = grandfather->_left;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的右子树,左单旋+变色
					if (cur == parent->_right)
					{
						_RotateL(grandfather);//左单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的左子树,右单旋+左单旋+变色
					else
					{
						_RotateR(parent);//右单旋
						_RotateL(grandfather);//左单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
		}
		return true;
	}

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//检查是否平衡/是否符合红黑树规则
	bool IsBalance()
	{
		//检查根节点
		int FPathNumber = -1;
		return _check(_root,0,FPathNumber);	
	}

	//树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
private:
	//析构函数
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	//树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}

	//检查
	bool _check(Node* root,int BlackNumber,int& FPathNumber)
	{
		//检查规则二,检查根节点
		if (_root->_col == RED)
		{
			cout << "根节点不为黑,违反规则二" << endl;
			return false;
		}

		if (root == nullptr)
		{
			//检查路径黑色节点是否相等
			if (FPathNumber == -1)
			{
				FPathNumber = BlackNumber;
			}

			if (FPathNumber != BlackNumber)
			{
				cout << "路径的黑色节点数量不同,违反规则四" << endl;
				return false;
			}
			return true;
		}

		//规则四:求每条路径黑色节点数量,如果root为黑色节点,BlackNumber++
		if (root->_col == BLACK)
		{
			BlackNumber++;
		}

		//判断规则三,是否有连续红色节点
		Node* parent = root->_parent;
		if (root->_col == RED && parent && parent->_col == RED)
		{
			cout << "出现连续红色节点,违反规则三" << endl;
			return false;
		}

		return _check(root->_left,BlackNumber,FPathNumber)
			&& _check(root->_right, BlackNumber, FPathNumber);
	}

	//中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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





	//左单旋
	void _RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		Node* ppnode = parent->_parent;//记录parent的父节点后续根据ppnode调整subR的父节点

		parent->_right = subRL;
		if (subRL)//判断subRL是否不为空,为空则不能非法访问
			subRL->_parent = parent;

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

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

	//右单旋
	void _RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* ppnode = parent->_parent;

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

		parent->_left = subLR;
		//判断subLR不为空
		if (subLR)
			subLR->_parent = parent;

		//判断parent是否为根节点
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = ppnode;
			if (ppnode->_right == parent)
			{
				ppnode->_right = subL;
			}
			else
			{
				ppnode->_left = subL;
			}
		}
	}
private:
	Node* _root;
};

Summarize

This article mainly leads you to understand the underlying data structure AVL tree and red-black tree of map and set.

The essence of the AVL tree is mainly the balance factor, and the height of the tree is reduced by rotation to make the tree balanced.

The essence of the red-black tree is that in most cases, the number of rotations can be reduced by changing the color, thereby improving efficiency.

Guess you like

Origin blog.csdn.net/zcxmjw/article/details/130640520