✨前言✨
📘 博客主页:to Keep博客主页
🙆欢迎关注,👍点赞,📝留言评论
⏳首发时间:2024年5月7日
📨 博主码云地址:博主码云地址
📕参考书籍:《C++ Primer》《C++编程规范》
📢编程练习:牛客网+力扣网
**由于博主目前也是处于一个学习的状态,如有讲的不对的地方,请一定联系我予以改正!!!
1 红黑树的性质
1️⃣一个节点不是红节点就是黑节点
2️⃣根节点一定是黑节点
3️⃣不存在连续的红节点
4️⃣对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5️⃣每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
通过以上几点性质,我们可以知道红黑树的核心思想就是最长路径最多为最短路径的两倍,并且此时在最极端的情况下,节点全部为黑色的路径是最短的,最长的就是一黑一红交错着的!与AVL树差不多,这里我们直接给出红黑树节点的定义:
//利用枚举,来表示两种颜色
enum Colour
{
RED,
BLACK
};
template <class K,class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
pair<K, V> _kv;
RBTreeNode(pair<K,V> kv)
:_left(nullptr),
_right(nullptr),
_parent(nullptr),
_col(RED),
_kv(kv)
{}
};
2 红黑树的插入
首先,对于新插入进来的节点是红色节点还是黑色节点!这个我们必须想清楚了,显然,要插入的新节点必须得是红色节点,如果是黑色节点,那么我们就必须在其他路径上也要新增黑色节点,这样才能保证从当前节点到叶子结点的黑色节点个数是一样的!而插入红色节点,就不需要我们考虑其他路径上黑节点是否需要新增的问题了!第二个,如何变色的呢?在红黑树中,新增的节点,其父亲节点,祖父节点的颜色是确定的,如果父亲节点是黑色的,那就不用调整颜色了。但是如果父亲的颜色是红色的,那就需要调整了,而且叔叔节点的颜色是不确定的,所以我们变色的规则就要分开来讨论了!
2.1 叔叔节点存在,且为红色
如下简图所示:
此时我们只需要改变颜色就可以了,将parent和uncle的颜色改为黑色节点,grandfather节点的颜色改为红色,在让cur等于grandfather,继续往上进行更新,如果grandfather是根节点,那就需要把根节点的颜色改为黑色!
2.2 叔叔节点不存在,或者存在为黑色节点
此时我们可以看到叔叔节点不存在,或者叔叔节点的颜色是黑色,此时变色解决不了问题了,那么这个时候就需要我们进行旋转加变色就可以解决问题了!这里的旋转就是我们之前AVL树那一节所学过的!只不过里面没有平衡因子的改变了,只需要旋转就行了!图中只是描述了叔叔在右边的情况,当然还有叔叔在左边的情况,左边的情况是与右边的情况是相反的!
3 具体实现
有了上述的思路,我们可以很快的实现红黑树了
bool Insert(pair<K,V> kv)
{
//1 寻找要插入的位置
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
if (cur)
cur->_parent = parent;
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else {
parent->_left = cur;
}
cur->_parent = parent;
//开始改变颜色
while (parent && parent->_col==RED)
{
Node* grandfather = parent->_parent;
//parent节点在祖父节点的左边
if (parent==grandfather->_left)
{
//第一种情况,需要变色处理
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
uncle->_col = BLACK;
parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
//第二种情况 uncle节点为空或者是黑色节点
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//由于根节点变成了黑色,那就不用再往上判断了
break;
}
}
else
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = BLACK;
parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
//第二种情况 uncle节点为空或者是黑色节点
if (cur == parent->_left)
{
RotateR(parent);
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//由于根节点变成了黑色,那就不用再往上判断了
break;
}
}
}
//将根节点的颜色变成黑色就行了
_root->_col = BLACK;
return true;
}
4 红黑树的验证
1️⃣验证是否符合中序遍历是否有序
2️⃣验证是否满足红黑树的性质
注意,不可以用最长路径不超过最短路径的两倍这一思想来验证,如果你这个红黑树不满足所有性质条件,并且也满足了以上两个条件,那么这样一棵树,并不是红黑树!所以我们验证的思想就是取其中一个路径,遍历一遍,比较其他路径中黑色节点数是否与这条路径上的数量相等!判断子节点与父节点是否为联系的红色节点!具体的实现代码如下所示:
bool IsRBTree()
{
if (_root == nullptr)
return true;
Node* tmp = _root;
int Countnum = 0;
while (tmp)
{
if (tmp->_col == BLACK)
{
Countnum++;
}
tmp = tmp->_left;
}
return check(_root,0,Countnum);
}
bool check(Node* root, int comparenum,int Countnum)
{
if (root == nullptr)
{
if (Countnum != comparenum)
return false;
return true;
}
if (root->_col == BLACK)
comparenum++;
if (root->_parent)
if (root->_col == RED && root->_parent->_col == root->_col)
return false;
return check(root->_left, comparenum, Countnum) && check(root->_right, comparenum, Countnum);
}
5 红黑树与AVL树之间的比较
红黑树相比较于AVL树,树的高度会高一点,但是相差不大,可以有效的减少旋转次数!查询的时间复杂度依然是O(lgN)。红黑树的应用比AVL树的应用要广的多,例如:C++ STL库 – map/set、mutil_map/mutil_set,以及Linux内核等,底层所采用的数据结构就是红黑树!