文章目录
前言
在map和set容器中,底层都是使用红黑树来实现的。为了能够模拟实现map和set,因此先来解决红黑树的问题。
提示:以下是本篇文章正文内容
一、红黑树有什么性质?
1.红黑树中每个结点不是红色就是黑色。
2.根节点是黑色的。
3.如果一个节点是红色的,则它的两个孩子结点是黑色的
4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点
这些性质保证了红黑树最长路径中节点个数不会超过最短路径节点的二倍。
二、插入的原理即步骤
1.新增结点为红色
红黑树要求新增结点颜色必须为红色,若为黑色则就可能打破其第4条性质。
2.寻找叔叔结点
1.叔叔为红色
如图所示,若要在22的结点下插入21,此时其叔叔结点为27。
则此时 cur=21,parent=22,grandfather=25,uncle=27
且叔叔为红色,则只需将父亲和叔叔变黑,将祖父变红。
最后让cur=grandfather,继续向上更新。
2.叔叔不存在为nullptr
此时cur=21,parent=22,grandfather=25,uncle=nullptr
则就需要去旋转+变色来处理。
首先根据 cur,parent,grandfather三者的相互顺序来判断双旋还是单旋。
然后旋转完毕后改变颜色。
若为单旋,则将parent变为黑,grandfather变为红。
若为双旋,则将cur变为黑,grandfather变为红。
3.叔叔存在且为黑
此时cur=22,parent=17,grandfather=13,uncle=8
和第二种情况一样,需要旋转+变色
在此不进行过多赘述。
三、代码实现
1.叔叔为红
if (uncle->_col == RED)
{
uncle->_col = BLACK;
parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
此时只需变色,然后继续向上寻找即可。
2.叔叔为空或者叔叔为黑
if (uncle == nullptr || uncle->_col == BLACK)
{
// 旋转+变色
if (grandfather->_right == parent && parent->_right == cur)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else if (grandfather->_left == parent && parent->_left == cur)
{
RotateR(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else if (grandfather->_left == parent&&parent->_right == cur)
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
else if (grandfather->_right == parent&&parent->_left == cur)
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
else
assert(false);
break;
}
首先通过cur,parent,grandfather三者之间的关系,判断左单旋or右单旋or左右双旋or右左双旋。
然后旋转完毕改颜色即可,注意双旋单旋的情况不同。
四、验证是否为红黑树
1.代码实现
bool CheckNum(Node* root, int n, int num)
{
if (root == nullptr)
{
if (n != num)
{
cout << num<<' '<<n << endl;
return false;
}
else
return true;
}
if (root->_col == BLACK)
n++;
return CheckNum(root->_right,n,num)&&CheckNum(root->_left, n, num);
}
bool CheckColor(Node* root)
{
if (root == nullptr)
return true;
if (root->_col == RED&&root->_parent != nullptr&&root->_parent->_col == RED)
{
cout << root->_data << ":该节点出现连续的红节点" << endl;
return false;
}
int num = 0;
Node* p = root;
while (p != nullptr)
{
if (p->_col == BLACK)
num++;
p = p->_left;
}
return CheckColor(root->_left) && CheckColor(root->_right) && CheckNum(root,0,num);
}
bool IsBalance()
{
if (_root->_col == RED)
return false;
return CheckColor(_root);
}
首先验证这棵树是否有连续的红节点。
然后判断这棵树是否每条路径的黑节点数量一样。
2.测试
1.测试代码
const int N = 100000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand());
}
jjw::RBTree<int> t;
for (auto e : v)
{
t.Insert(e);
}
cout << t.IsBalance() << endl;
2.测试结果
输出1,表示结果正确。
总结
以上就是红黑树的插入的实现过程,完整版代码存放在Git–ee上:红黑树的插入
本人小白一枚,有错误之处还望各位大佬能够指正。
接下来回顾一下map和set的模拟实现,然后继续往后学习!