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?
5. Comparison between red-black trees and AVL trees
5. Insertion operation of red-black tree
(2) Specific case 2: equivalent to repeating specific case 1
(1) Specific case 1: cur is red, p is red, g is black, u does not exist, but cur is the left of p
6. Check the legality of red-black trees
2. Complete example of inserting red-black tree
(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
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?
Shortest: all black.
Longest: one black and one red interval.
3. Small exercises
4.Heigao
5. Comparison between red-black trees and AVL trees
: 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)
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.
(1) Specific situation 1
If a, b, c, d, and e are all empty, then they are all black null leaf nodes.
(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
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 .
(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.
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.
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
(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
(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
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
// 检测是否有连续红色节点和各路径黑节点个数是否相同 的支线函数
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
(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.
(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.
(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 .
It's Uncle Hong again: Uncle's color changes, and he becomes a new knot.
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.
(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.
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.
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.
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 .
The rest of the situations are similar, so I won’t go into details.
2. Inferences related to “Heigao”
Internal nodes do not include leaf nodes, that is, NULL nodes.
3. Delete operation (not tested)