一.概念
红黑树是一棵二叉搜索树,每个节点增加一个存储位,来存储节点的颜色。节点颜色只有红色和黑色两种。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,最长路径不超过最短路径的二倍 ,也就是说,一棵红黑树的高度为h,最短路径为h/2,其他路径长度都在它俩之间。那么整棵树进而达到接近平衡的状态。所以它控制平衡没有AVL树那么严格。

1.红黑树的规则

2.为何最长路径不超过最短路径的二倍?


也就是说,规则第二,三点限定最长。规则第四点限定最短。
3.红黑树的开销

N位于每条路径全是黑(H = h),以及每条路径全是红黑交替(H = 2h)两个极端以内,而通过等比数列求和即可求出上面N的范围。
二.红黑树的实现
1.结构
2.插入
2-1.插入的过程
1.插入一个值,必须按照二叉搜索树的规则插入,插入后还要判断整棵树是否符合红黑树四规则。
2.对于非空树,插入黑色节点必然违反规则四,而插入红色节点有可能违反规则三,更何况规则四是很难维护的,所以新插入节点为红色节点。而对于空树,新插入节点必须为黑节点,否则违反了规则二。
3.非空树新插入的节点必为红色 ,如果该节点的父节点为黑色,则符合规则三,插入结束。
4.非空树新插入的节点必为红色,如果该节点的父节点为红色,违反规则三,需进一步分析,调整。

这几个亲属节点中,唯uncle节点颜色不定,其余几个均为固定颜色。所以要根据u节点,分情况处理(关键看叔叔):
2-2情况1.纯变色(u不为空,且为红)

p,u为红,g为黑,直接来个换家操作,g变红,p,u变黑,c不变,还是红。
首先得明晰,无论怎么讲,只要在非空树新插入的节点必为红色 ,该节点的父节点为红色 这个大前提下,去做处理,p节点必然要被处理成黑色,因为不能有父子双红。p变黑以后,如果不做其他操作,那么该条支路的黑节点数就会增加1,就违背了规则四,于是就把g变成红,增加的黑节点数就又恢复了。既然g变红,u节点那一支也不能出现父子双红,所以u也变红。
总结:

抽象化

子树a,b,d,e,f的具象化
Ⅰ.a,b,c,d,e,f子树全为空,c为新插入节点
此时d,e,f子树bh = 0


Ⅱ.c不是新插入节点,为之前的g节点(黑色)
此时d,e,f子树bh = 1
新插入节点的插入位置在a,b的4个子节点里选一个。

Ⅲ.c不是新插入节点,为之前的g节点(黑色)
但d,e,f子树的bh = 2

无论子树d,e,f的bh被设计的有多大,子树变的多复杂,万变不离其宗,在处理时都向上,c移到当下子树g节点处,反复将上面子树的父亲,叔叔变黑,爷爷变红,直到整棵树满足红黑树规则。
代码实现
//父亲也为红,出现父子连续双红,需要处理
while (Parent && Parent->_col == RED)
{
Node* grandfather = Parent->_parent;
//叔叔存在
if (grandfather->_left == Parent)
{
//g
//p u 叔叔在右
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{//叔叔存在且为红。变色
Parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//向上调整
cur = grandfather;
cur->_parent = Parent;
}
}
else
{//叔叔在左
Node* uncle = grandfather->_left;
}
}
//parent为空,也就是cur走到根节点的位置,并且根节点为红色。
//直接给_root改色即可,反正把它改成黑色,每个支路上黑节点总数都加1,还是相同的。
//即便根本来就是黑色,再让它成黑色也没问题。
_root->_col = BLACK;
return true;
2-3情况2.单旋+变色(u为空或为黑)
①.g的_parent(18)为黑

所以需要先对gpc这棵子树进行单旋,然后g和p再变色:

②.g的_parent(18)为红

上面操作还未完,假如g->_parent为红,旋转+变色后g和g->_parent出现父子双红。还需要继续调整,把g当作c,向上更新。如果g->_parent还是红,就接着向上调;如果g->_parent是黑,就结束;如果g是整棵树根节点,还是红色,就把它变成黑。
总结:
子树抽象化:
u为空,或者u存在且为黑。
u为空:
此时c为新增节点

u存在且为黑:
c经向上调整后移到之前子树的g节点处,并且该节点变成红色。

核心就是g先单旋,再改变p,g的颜色
子树具象化:

2-4情况3.双旋+变色(u为空或为黑)
u为空:
双旋后,c做了当前子树根节点,且p,g变成了子节点,需要将c变黑,g变红,才能符合规则。
u存在且为黑:

由于旋转这几种情况,在旋转+变色以后,子树的根节点都会变成黑色,所以无需再向上调整。而叔叔存在且为红的情况,旋转+变色后会让子树根节点变成红色,此时上面一棵子树parent也为红,连续双红需要向上调整。
2.5.插入的完整代码实现及其思维导图

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)
{//cur和待插入节点的值进行比较,待插入值大于cur,cur往右子树走,小于则往左子树走。
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;
}
if (Parent->_kv.first > kv.first)
{
Parent->_left = cur;
}
//链接父节点
cur->_parent = Parent;
//父亲也为红,出现父子连续双红,需要处理
while (Parent && Parent->_col == RED)
{
Node* grandfather = Parent->_parent;
if (grandfather->_left == Parent)
{//叔叔在右
//g
//p u
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{//叔叔存在且为红。变色
Parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//向上调整
cur = grandfather;
cur->_parent = Parent;
}
else
{//uncle存在且为黑或叔叔不存在。 旋转+变色
//c在p左子树,g右单旋+g,p变色
//g
// p u
//c
if (cur == Parent->_left)
{
RotateR(grandfather);
Parent->_col = BLACK;
grandfather->_col = RED;
}
else
{//c在p右子树,g-p-c双旋+变色
//g
// p u
// c
RotateL(Parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;//此处作为子树根节点的是Parent,它是黑色的,所以不用管上面是红是黑,全部符合规则,不需要再向上调整。
}
}
else
{//叔叔在左
Node* uncle = grandfather->_left;
//g
//u p
//叔叔存在且为红
if (uncle && uncle->_col == RED)
{
uncle->_col = Parent->_col = BLACK;
grandfather->_col = RED;
//向上调整
if (grandfather->_col == RED)
{
cur = grandfather;
Parent = cur->_parent;
}
}
else
{//叔叔不存在或存在但为黑
//g
// u p
// c
//单旋
if (Parent->_right = cur)
{
RotateL(grandfather);
Parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//g
// u p
// c
//双旋
RotateR(Parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
//parent为空,也就是cur走到根节点的位置,并且根节点为红色。
//直接给_root改色即可,反正把它改成黑色,每个支路上黑节点总数都加1,还是相同的。
//即便根本来就是黑色,再让它成黑色也没问题。
_root->_col = BLACK;
return true;
}
3.红黑树验证

注:
1.代码中,blackNum作为一个形参出现,每一次递归遍历时,它的值都隶属于当下,因此递归返回时,每一个blackNum对应的是当下的每一个节点。
2.构造参考值,此处以最左侧路径的黑节点数量作参考值:

3.前序遍历⾛到空时,意味着⼀条路径⾛完了,此时进入if语句,blackNum与参考值refNum进行比较。

4.前序遍历的return用&&的妙处在于,&&前假如是false,就可直接截断,无需浪费精力去管其后面的东西。

递归展开图:

完整代码:
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
// 前序遍历⾛到空时,意味着⼀条路径⾛完了
//cout << blackNum << endl;
if (refNum != blackNum)
{
cout << "存在⿊⾊结点的数量不相等的路径" << endl;
return false;
}
return true;
}
// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红⾊结点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
// 参考值
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return Check(_root, 0, refNum);
}
