目录
一、红黑树概念
1.概念
红黑树也是一种二叉搜索树,在每个结点上增加一个存储位,来表示该节点的颜色,节点要么是红色要么是黑色。
如果说 AVL
树是天才设计,那么 红黑树 就是 天才中的天才设计,不同于 AVL
树的极度自律,红黑树 只在条件符合时,才会进行 旋转降高度,因为旋转也是需要耗费时间的
红黑树在减少旋转次数时,在整体性能上仍然没有落后 AVL 树太多
1.1定义
红黑树 也是 三叉链 结构,不过它没有 平衡因子,取而代之的是 颜色
红黑树 的节点定义如下:(这里是通过 枚举 定义的颜色)
enum Colour
{
RED,
BLACK,
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)// 推荐初始化为红
{}
};
2.性质
红黑树具有以下性质:
(1)每个结点不是红色就是黑色
(2)根节点是黑色的
(3)没有连续的红色节点
(4)每条路径上都包含相同数目的黑色节点
由于(3)和(4)互相控制,因此满足以上性质就能保证:最长路径中节点个数不会超过最短路径节点个数的2倍。
二、红黑树的插入操作
2.1、抽象图
在演示 红黑树 的插入操作时,也需要借助 抽象图,此时的 抽象图 不再代表高度,而是代表 黑色节点 的数量
抽象图中关注的是 黑色节点 的数量
2.2、插入流程
红黑树 的插入流程也和 二叉搜索树 基本一致,先找到合适的位置,然后插入新节点,当节点插入后,需要对颜色进行判断,看看是否需要进行调整。红黑树 在插入节点后,可以选择新节点为 红 或 黑
插入流程:
· 判断根是否为空,如果为空,则进行第一次插入,成功后返回 true
· 找到合适的位置进行插入,如果待插入的值比当前节点值大,则往 右 路走,如果比当前节 点值 小,则往 左 路走
·判断父节点与新节点的大小关系,根据情况判断链接至 左边 还是 右边
·根据颜色,判断是否需要进行 染色、旋转 调整高度
1) 如果为 红 ,可能违反规则3,需要进行调整
2) 如果为 黑,必然违反规则4,并且因为这一条路,影响了其他所有路径,调整起来比较麻烦因此 推荐在插入时,新节点默认为 红色,插入后,不一定调整,即使调整,也不至于 影响全局
三、红黑树颜色处理
所有插入的新节点颜色都是红色,当父亲是红色时,需要进行颜色处理,分3种情况进行处理。在这3种情况中,cur为新插入节点,p为父亲节点,u为叔叔节点,g为祖父节点。下面的树都可能是一棵完整的树,也有可能是一棵子树。
(1)抽象图
如下a、b、c、d、e都是子树:
(1)假如g是根节点,根据红黑树性质,根节点必须是黑色,那就把g再变黑就好了
(2)假如g不是根节点,g是子树,那么g还有parent节点。
①如果g的parent是黑色,满足红黑树性质,那么停止调整。
②如果g的parent是红色,就破坏了规则(3),那么还需要继续向上调整。
调整方法为:把g当作cur,继续向上调整,直到p不存在,或p存在且为黑停止。
(2)具象图
①g是根节点,直接把p和u变黑,g变红
②g不是根节点,g是子树,把p和u变黑,g变红之后,还要继续向上调整,直到p不存在,或p存在且为黑停止
2. cur为红,p为红,g为黑,u为黑或u不存在(单旋)
这种情况下,g、p、cur形成直线,先看cur为红,p为红,g为黑,u为黑的情况
这是由情况一cur红,p红,g黑,u存在且为红处理以后变换而来,比如以下情况:
在这种情况下,cur原来的颜色一定是黑色,只不过在处理的过程当中,将cur的颜色变成了红色,所以cur不是新增节点,而是新增节点的祖先节点。
(1)抽象图
如下a、b、c、d、e都是子树,由于要旋转,所以要分为两种情况:当p是g的左子树,cur是p的左子树时,g右单旋,p变黑,g变红:
当p是g的右子树,cur是p的右子树时,g左单旋,p变黑,g变红:
(2)具象图
cur是新增节点的祖先节点,那么a、b一定不为空,由于从g到u的路径上有2个黑色节点,那么a和b都存在一个黑色节点,因此,c中也有一个黑色节点,才能满足每条路径上有相同数目的黑色节点。因此d、e要么同时不存在,要么同时为空。
当p是g的左子树,cur是p的左子树时,将节点g右单旋,p变黑,g变红:
当p是g的右子树,cur是p的右子树时,将节点g左单旋,p变黑,g变红:
再看cur为红,p为红,g为黑,u不存在的情况:
u不存在的情况更为简单,假如p是g的左子树,cur是p的左子树,将节点g右单旋,p变黑,g变红即可
假如p是g的右子树,cur是p的右子树,将节点g左单旋,p变黑,g变红即可
3.cur为红,p为红,g为黑,u不存在或u为黑(双旋)
这种情况下g、p、cur形成折线,先看cur为红,p为红,g为黑,u为黑的情况:
(1)抽象图
当p是g的左子树,cur是p的右子树时,处理方法:p左单旋,g右单旋,cur变黑,g变红:
当p是g的右子树,cur是p的左子树时,处理方法:p右单旋,就变成了情况二
(2)具象图
当p是g的左子树,cur是p的右子树时,将p左单旋,g右单旋,cur变黑,g变红
当p是g的右子树,cur是p的左子树,p右单旋,g左单旋,p变黑,g变红
4.红黑树节点插入代码
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)// 往左找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)// 往右找
{
parent = cur;
cur = cur->_right;
}
else
{
return false; // 值相等
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent; // 反向链接父亲
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
// 情况1:uncle存在且为红,变色处理,并继续往上调整
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上调整
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:uncle不存在/uncle存在且为黑,旋转+变色
{
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
//parent->_col = RED;
grandfather->_col = RED;
}
break;
}
}
else // (grandfather->_right == parent)
{
// g
// u p
// c (c)
Node* uncle = grandfather->_left;
// 情况1:uncle存在且为红,变色处理,并继续往上调整
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上调整
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:uncle不存在/uncle存在且为黑,旋转+变色
{
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
五、红黑树查找
查找比较简单,递归向左走或向右走:
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > key)
cur = cur->_left;
else if (cur->_kv.first < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
六、红黑树检查是否平衡
检查是否平衡还是要用到递归子函数
(1)需要判断是否满足红黑树的4条性质
(2)计算最左路径上的黑色节点个数,递归路径时,需要计算该条路径上的黑色节点个数是否和最左路径上的节点个数相等
bool Isbalance()
{
if (_root && _root->_col == RED)
{
cout << "根节点颜色是红色" << endl;
return false;
}
int benchmark = 0;// 基准值
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++benchmark;
cur = cur->_left;
}
// 连续红色节点
return _Check(_root, 0, benchmark);
}