红黑树:
红黑树是一种二叉搜索树,但是每个结点上增加一个存储位表示结点的颜色,可以是Red或者BLACK。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其它路径长出2倍,因而是接近平衡的。
假设最短路径是h,最长是2h,其他路径长度为[h,2h]这个范围内。
如图所示,这是一棵红黑树。
(一)
红黑树的性质:
1.每个结点要不是黑色就是红色。
2.根结点是黑色的。
3.如果一个结点是红色的,那么它的两个孩子结点必须是黑色的(没有连续的红色结点)。
4.每条路径都包含相同数量的黑色结点。
5.每个叶子结点都是黑色的(这里的叶子结点指的是空结点)。这里避免路径数错了的误区。
咱们可以从红黑树的性质分析出:红黑树的最短路径为全黑路径,最长路径为一黑一红。
由图可知,全黑的结点路径长度为2,一黑一红的路径长度为4。如果我们在右边那条路径再新增一个结点不管是黑色还是红色的都已经违反规则了,首先,新增黑色结点违反每条路径黑色结点数量相同,新增红色结点违反不能由连续的红色结点。但是如果必须在这里增加一个结点,有且只能增加红色结点,因为我们可以通过后续的 颜色的变化和旋转 来维持红黑树的规则和二叉搜索树的平衡。
记住:新增结点从来都是红色结点,黑色结点不能通过新增得到,而是在颜色的变化中得到黑色结点!如果直接在某一条路径中新增黑色结点,那么就要牵扯到所有路径,因此就是牵一发而动全身的这种感觉,没办法去解决这种问题。
这种树就不是红黑树。
13(黑)->8(红)->NIL(黑) (1)
13(黑)->8(红)->11(黑)->NIL(黑) (2)
13(黑)->17(红)->15(黑)->NIL(黑) (3)
13(黑)->17(红)->25(黑)->22(红)->NIL(黑) (4)
13(黑)->17(红)->25(黑)->27(红)->NIL(黑) (5)
第一条路径和其他路径不一样,它只有1黑。忽略掉NIL,不要看空结点,我只是写在这看的更清楚。
和AVL树相比的话,AVL树的高度是logN,而红黑树的高度是接近2longN,因为红黑树的最短路径是logN,最长路径不超过最短路径2倍也就是2logN。
(二)
这里我将所有红黑树的类型归类为一个抽象类型,这种类型可以转化为任何一种情况。
a,b,c,d,e可以是空树,也可以不是空树,要不要颜色的变化或者旋转需要看叔叔的颜色情况。
情况1:u存在且为红,a,b,c,d,e不为空。
p/u变黑,g变红,如果
u存在且为红,a,b,c,d,e为空。
改变:p和u变黑,g变红,如果g不是是根,继续往上调整,让cur指向g的位置,p和g接着往上指到自己对应的位置,如果g是根,就把g变黑,调整完成。
这个图就是刚才抽象图的一个实例并且满足情况1。
情况2:u不存在或者存在为黑
u不存在:
这是情况2的最简单的一种情况。
p是g的左孩子,cur是p的左孩子,进行右旋。或者p是g的右孩子,cur是p的右孩子,进行左旋
这里是右旋的情况
u存在:
这是抽象图的实例化并满足情况2——u存在且为黑, 这个是在颜色的变化中才存在的情况,刚开始满足情况1。
u不存在同时p是g的左孩子,cur是p的右孩子。这里是新的情况,就和AVL树中有些相似,图中的情况需要进行左右双旋+变色。p是g的右孩子,cur是p的左孩子就是对称的,需要右左双旋。
这是是抽象图的实例化,插入新增结点后,先进行变色,新增结点的叔叔是红色,满足情况1,进行变色,因为g不是根,让cur指向g所在的结点,接着让p结点和g结点指向其最新的位置,然后发现满足情况2且存在叔叔结点并且cur在p的右边要进行双旋转,如图所示,旋转完后就调整完了。
(三)
代码展示:
bool Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
KeyOfT kot;
// KeyOfT kot;
while (cur)
{
if (kot(cur->_data) >kot(data))
{
parent = cur;
cur = cur->_left;
}
else if (kot(cur->_data)) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else {
return false;
}
}
cur = new Node(data);
if (kot(parent->_data) >kot(data))
{
parent->_left = cur;
}
else {
parent->_right = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* Grandfather = parent->_parent;
if (parent == Grandfather->_left)
{
Node* uncle = Grandfather->_right;
//情况一 叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
Grandfather->_col = RED;
cur = Grandfather;
parent = cur->_parent;
}
else
{
//情况二 叔叔不存在或者存在且为黑
if (cur == parent->_left)
{
RotateR(Grandfather);
parent->_col = BLACK;
Grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(Grandfather);
cur->_col = BLACK;
Grandfather->_col = RED;
}
break;
}
}
else
{
// Node* Grandfather = parent->_parent;
Node* uncle = Grandfather->_left;
// if (parent && parent->_col == RED)
//{
if (uncle && uncle->_col == RED)
{
//情况一 叔叔存在且为红
parent->_col = uncle->_col = BLACK;
Grandfather->_col = RED;
/* if (_root == Grandfather)
{
Grandfather->_col = BLACK;
}
*/
cur = Grandfather;
// parent = Grandfather->_parent;
parent = cur->_parent;
}
else
{
//情况二 叔叔不存在或者存在为黑
if (cur == parent->_right)
{
RotateL(Grandfather);
Grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent);
RotateL(Grandfather);
cur->_col = BLACK;
Grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
这里的左旋函数RotateL(),左右双旋函数RotateLR()参考我上一期的AVL树的博客。里面都有详细的代码。
如果我们想看看我们的红黑树插入,变色,旋转完成后是否满足它的规则.那么就看看这里我写的判断是否平衡的代码。
bool _IsBalance(Node* root,int blacknum, int refBlacknum)
{
//不能有连续的红色结点
//根结点必须是黑色的
//每条路径的黑色结点的个数相同
if (root == nullptr)
{
if (blacknum != refBlacknum)
{
return false;
}
cout << "黑色结点的数量" << blacknum << endl;
return true;
}
if (root->_col == BLACK)
{
blacknum++;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_data <<"存在连续的红色结点" << endl;
return false;
}
return _IsBalance(root->_left, blacknum, refBlacknum) && _IsBalance(root->_right, blacknum, refBlacknum);
}
bool IsBalance()
{
Node* cur = _root;
int refBlacknum = 0;
while (cur)
{
if (cur->_col == BLACK)
refBlacknum++;
cur = cur->_left;
}
return _IsBalance(_root,0,refBlacknum);
}
这里我用的递归来判断是否满足平衡。这里有一点需要注意,如果在类外面调用函数,不能传递类里面私有成员变量的参数,需要套一层函数调用,在类里面的函数用私有成员变量传参。拿代码中的例子来说,用main()函数调用IsBalance()函数不能传递类里面的私有成员变量_root,我没有展示出来,只能在类里面再写一层函数_IsBalance(),在IsBalance()来调用_IsBalance(Node*root)并且传参给它私有成员变量_root。
制作不易,希望多多支持