本篇文章进行数据结构中RBTree(红黑树)的学习!!!
概念:

红黑树的规则:
// 红黑树的颜色
enum Color
{
RED,
BLACK
};
// RBTree的节点
template <typename K, typename V>
struct RBTreeNode
{
RBTreeNode(pair<K, V> _kv)
: left(nullptr)
, right(nullptr)
, parent(nullptr)
, kv(_kv)
, col(RED)
{}
RBTreeNode<K, V>* left; // 节点的左孩子
RBTreeNode<K, V>* right; // 节点的右孩子
RBTreeNode<K, V>* parent; // 节点双亲
pair<K, V> kv; // 数值
Color col; // 颜色
};
注意:RB树节点与avl树节点相似,不同的是增加了节点的颜色
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
// 红黑树的规则:1.根节点是黑色的,黑节点的左右孩子可能为红也可能为黑
// 2.如果一个节点为红色,则它的左右孩子节点为黑色(没有连续的红节点)
// 3.对于每个节点,从该节点到其所有后代叶节点的路径上,均包含相同的数目的黑色节点(每条路径包含相同数目的黑色节点)
// 4.叶节点(nullptr)是黑色的
bool insert(const pair<K, V>& kv)
{
if (root == nullptr)
{
root = new Node(kv);
root->col = BLACK; // 根节点为黑色
return true;
}
// 按搜索树规则插入
Node* cur = root;
Node* parent = nullptr;
while (cur != nullptr)
{
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;
}
}
cur = new Node(kv);
// 如果插入节点是红色,可能破坏规则2 --- 如果插入节点是黑色,一定会破坏规则3(3很难处理)
cur->col = RED;
if (parent->kv.first > kv.first) {
parent->left = cur;
}
else {
parent->right = cur;
}
cur->parent = parent;
// ...
}
注意:

前言:因为新节点的默认颜色是红色,因此:
如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;
如果新插入节点的双亲节点颜色为红色时,就违反了规则三:“不能存在连续的红色节点”,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点💗💗💗
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整
// 存在连续红色节点时,需要一直向上调整
while (parent != nullptr && parent->col == RED) // 因为插入节点是红色,如果parent也是红色(连续红节点),那么需要调整
{
Node* grandfather = parent->parent; // cur的祖父
// 找叔叔时需要判断在祖父的哪一边
if (grandfather->left == parent)
{
Node* uncle = grandfather->right;
// 情况一:grandfather为黑,parent为红,uncle为红,cur为红 -- 需要调整g,p,u的颜色
if (uncle && uncle->col == RED)
{
parent->col = BLACK;
uncle->col = BLACK;
grandfather->col = RED;
// 调整的这颗树可能为局部子树,需要继续向上处理,祖父的父亲可能为红节点, 更新cur,parent
cur = grandfather;
parent = cur->parent;
}
else
{
Node* uncle = grandfather->left;
if (uncle != nullptr && uncle->col == RED)
{
parent->col = BLACK;
uncle->col = BLACK;
grandfather->col = RED;
cur = grandfather;
parent = cur->parent;
}


// 存在连续红色节点时,需要一直向上调整
while (parent != nullptr && parent->col == RED) // 因为插入节点是红色,如果parent也是红色(连续红节点),那么需要调整
{
Node* grandfather = parent->parent; // cur的祖父
// 找叔叔时需要判断在祖父的哪一边
if (grandfather->left == parent)
{
Node* uncle = grandfather->right;
// 情况一:grandfather为黑,parent为红,uncle为红,cur为红 -- 需要调整g,p,u的颜色
if (uncle && uncle->col == RED)
{
parent->col = BLACK;
uncle->col = BLACK;
grandfather->col = RED;
// 调整的这颗树可能为局部子树,需要继续向上处理,祖父的父亲可能为红节点, 更新cur,parent
cur = grandfather;
parent = cur->parent;
}
// 情况二:grandfather为黑,parent为红,uncle为nullptr/黑,cur为红 -- 需要旋转+变色处理
else
{
// 右旋,插入节点为parent的左边,祖父为旋转点,parent为祖父的左边,uncle为祖父的右边
if (cur == parent->left)
{
// g
// p
// c
RotateR(grandfather);
// 更新颜色 -- 旋转后parent为根,根要变黑(根变黑后所有子树的路径的黑节点数量都加1) -- grandfather为根的子树,变红
parent->col = BLACK;
grandfather->col = RED;
}
}
else
{
Node* uncle = grandfather->left;
if (uncle != nullptr && uncle->col == RED)
{
parent->col = BLACK;
uncle->col = BLACK;
grandfather->col = RED;
cur = grandfather;
parent = cur->parent;
}
else
{
// 左旋,插入节点为parent的右边,祖父为旋转点,parent为祖父的右边,uncle为祖父的左边
// g
// p
// c
if (cur == parent->right)
{
RotateL(grandfather);
// 更新颜色
parent->col = BLACK;
grandfather->col = RED;
}
}
`
具体情况分析 + 解决方法:

注意:情况1是直线的情况,而情况三是折线的情况,即cur插入的方向与p指向c的方向相反

具体图 + 解决方案:
p为g的左孩子,u为p的右孩子或不存在,c为p的右孩子时,使用左右双旋解决,左旋用p作为旋转点,,右旋使用g为旋转点,旋转后将g颜色改为红,c改为黑
p为g的右孩子,u为p的左孩子或不存在,c为p的左孩子时,使用右左双旋解决,右旋用p作为旋转点,,左旋使用g为旋转点,旋转后将g颜色改为红,c改为黑

bool insert(const pair<K, V>& kv)
{
if (root == nullptr)
{
root = new Node(kv);
root->col = BLACK; // 根节点为黑色
return true;
}
// 按搜索树规则插入
Node* cur = root;
Node* parent = nullptr;
while (cur != nullptr)
{
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;
}
}
cur = new Node(kv);
// 如果插入节点是红色,可能破坏规则2 --- 如果插入节点是黑色,一定会破坏规则3(3很难处理)
cur->col = RED;
if (parent->kv.first > kv.first) {
parent->left = cur;
}
else {
parent->right = cur;
}
cur->parent = parent;
// 存在连续红色节点时,需要一直向上调整
while (parent != nullptr && parent->col == RED) // 因为插入节点是红色,如果parent也是红色(连续红节点),那么需要调整
{
Node* grandfather = parent->parent; // cur的祖父
// 找叔叔时需要判断在祖父的哪一边
if (grandfather->left == parent)
{
Node* uncle = grandfather->right;
// 情况一:grandfather为黑,parent为红,uncle为红,cur为红 -- 需要调整g,p,u的颜色
if (uncle && uncle->col == RED)
{
parent->col = BLACK;
uncle->col = BLACK;
grandfather->col = RED;
// 调整的这颗树可能为局部子树,需要继续向上处理,祖父的父亲可能为红节点, 更新cur,parent
cur = grandfather;
parent = cur->parent;
}
// 情况二:grandfather为黑,parent为红,uncle为nullptr/黑,cur为红 -- 需要旋转+变色处理
else
{
// 右旋,插入节点为parent的左边,祖父为旋转点,parent为祖父的左边,uncle为祖父的右边
if (cur == parent->left)
{
// g
// p
// c
RotateR(grandfather);
// 更新颜色 -- 旋转后parent为根,根要变黑(根变黑后所有子树的路径的黑节点数量都加1) -- grandfather为根的子树,变红
parent->col = BLACK;
grandfather->col = RED;
}
else // 第三种情况,左右双旋,cur插入parent的右边,且parent为g的左边,,形成折线
{
// g
// p
// c
RotateL(parent);
RotateR(grandfather);
// 更新颜色
grandfather->col = RED;
cur->col = BLACK;
}
break;
}
}
else
{
Node* uncle = grandfather->left;
if (uncle != nullptr && uncle->col == RED)
{
parent->col = BLACK;
uncle->col = BLACK;
grandfather->col = RED;
cur = grandfather;
parent = cur->parent;
}
else
{
// 左旋,插入节点为parent的右边,祖父为旋转点,parent为祖父的右边,uncle为祖父的左边
// g
// p
// c
if (cur == parent->right)
{
RotateL(grandfather);
// 更新颜色
parent->col = BLACK;
grandfather->col = RED;
}
else // 右左双旋,cur插入parent的左边,且parent为g的右边,形成折线
{
// g
// p
// c
RotateR(parent);
RotateL(grandfather);
// 更新颜色
grandfather->col = RED;
cur->col = BLACK;
}
break;
}
}
}
root->col = BLACK; // 调整完后,根节点可能是红,需要调整为黑
return true;
}
// 左旋,右边高,左边矮
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
parent->right = subRL;
if (subRL != nullptr)
subRL->parent = parent;
Node* PpNode = parent->parent;
subR->left = parent;
parent->parent = subR;
if (parent == root)
{
root = subR;
root->parent = nullptr;
}
else
{
if (PpNode->left == parent) {
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 != nullptr)
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;
}
}
【其他文章】
红黑树的删除不做讲解(比插入难很多,性价比低):《算法导论》或者《STL源码剖析》
bool erase(const K& key)
{
Node* cur = root;
Node* Cparent = nullptr;
while (cur)
{
if (cur->kv.first > key)
{
Cparent = cur;
cur = cur->left;
}
else if (cur->kv.first < key)
{
Cparent = cur;
cur = cur->right;
}
else // 找到开始删除
{
// 删除节点存在左节点
if (cur->right == nullptr)
{
// 右节点为空且没有父节点,说明只有右子树或只有一个根节点
if (Cparent == nullptr)
{
root = root->left;
if (root !=nullptr)
root->parent = nullptr;
}
else
{
if (cur == Cparent->left) {
Cparent->left = cur->left;
if (cur->left)
cur->left->parent = Cparent;
}
else
{
Cparent->right = cur->left;
if (cur->left)
cur->left->parent = Cparent;
}
}
}
// 删除节点存在右节点
else if (cur->left == nullptr)
{
// 右节点为空且没有父节点,说明只有右子树或只有一个根节点
if (Cparent == nullptr)
{
root = root->right;
if (root != nullptr)
root->parent = nullptr;
}
else
{
if (cur == Cparent->right)
{
Cparent->right = cur->right;
if (cur->right)
cur->right->parent = Cparent;
}
else
{
Cparent->left = cur->right;
if (cur->right)
cur->right->parent = Cparent;
}
}
}
else // 删除节点存在左右节点 -- 替换数据法
{
Node* Left_Max = cur->left;
Node* LMparent = cur;
while (Left_Max->right)
{
LMparent = Left_Max;
Left_Max = Left_Max->right;
}
// 交换Cparent左子树和删除节点的值
std::swap(Left_Max->kv.first, cur->kv.first);
std::swap(Left_Max->kv.second, cur->kv.second);
// Cparent链接cur中左/右节点
if (LMparent->left == Left_Max)
{
LMparent->left = Left_Max->left;
if (cur->left)
Left_Max->left->parent = LMparent;
}
else
{
LMparent->right = Left_Max->left;
if (Left_Max->left)
Left_Max->left->parent = LMparent;
}
cur = Left_Max;
}
while (Cparent && Cparent->col == RED)
{
Node* grandfather = Cparent->parent;
if (grandfather && grandfather->right == Cparent)
{
Node* uncle = grandfather->left;
break;
}
else
{
Node* uncle = grandfather->right;
break;
}
}
if (root)
root->col = BLACK; // 删除节点可能为根 -- 删除的处理是找替代节点,只有二个节点时让root指向下一个节点
delete cur;
return true;
}
}
return false;
}
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(
l
o
g
2
N
log_2 N
log2N),红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的2倍
相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多