1. 红黑树的概念
红黑树,是一种二叉搜索树,但在每个节点上增加一个一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根节点到叶子节点的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长两倍。因而是接近平衡的。
2.红黑树的性质
- 每个节点不是红色就是黑色
- 根节点是黑色的
- 红色结点的孩子必须是黑色
- 对于每个结点到其所有后代的叶结点的简单路径上,均包含相同数目的黑色结点。
- 每个空的叶子节点都是黑色的
3.红黑树结点的定义
// 结点的颜色
enum Color {RED,BLACK};
template<class K,class V>
struct RBSTreeNode
{
RBSTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
RBSTreeNode<K, V>* _left;
RBSTreeNode<K, V>* _right;
RBSTreeNode<K, V>* _parent;
pair<K, V> _kv;
color _col;
};
思考:为什么要把新增结点的默认颜色给成红色的?
答:因为若把默认节点的颜色给为黑色,就会每一条路径上的平衡(违反了第4条),很危险;若把默认结点给为红色可能违反第3条,但是也只是对当前路径上的平衡有影响,不会影响其他路径。
4.红黑树的插入操作
1)按二叉搜索树的性质插入结点
2)检测插入新结点后,红黑树的性质是否遭到破坏
3)如果被破坏,则进行调整,使得重新平衡
因为新增结点是红色,所以若新增结点的双亲结点是黑色就没有违反红黑树的任何性质,不需要做任何调整。若新增结点的双亲结点是红色就会违反第三条性质,此时需要分情况讨论:
约定:cur为当前结点,parent为父节点,grandfather为祖父节点,uncle为叔叔节点
- 情况一:cur为红色,p为红色,g为黑色,u存在且为红色
- 解决方法:将p,u改为黑色,g改为红色,然后把g当成cur继续向上调整。
- 情况二:cur为红色,p为红色,g为黑色,u不存在/u为黑色
- 解决方法:将p变为黑色,g变为红色,继续向上调整
注意: 红黑树中,仍需看情况进行以下四种旋转:(cur与parent的相对位置)
左左——右单旋,右右——左单旋, 左右——左右双旋 ,右左——右左双旋
并且需要单旋时:先变色再单旋
需要双旋时:先旋转一次,然后变色再进行一次单旋
具体的旋转过程可以参考我的上篇博客:AVL树 中对旋转的讲解
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)
{
parent = cur;
if (kv.first < cur->_kv.first)
cur = cur->_left;
else if (kv.first > cur->_kv.first)
cur = cur->_right;
else
return false;//如果树中已经有该元素,则插入失败
}
//找到插入位置,插入节点
//插入的节点颜色为红色,破坏红黑树的性质3,更好处理
cur = new Node(kv);
cur->_col = RED;
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//插入节点成功后,检查红黑树的性质有没有被破坏
//若是有则要进行节点的颜色调整以满足红黑树性质
//若是父节点存在且父节点的颜色为红色则需要调整,否则满足红黑树性质
while (parent && parent->_col == RED)
{
// 注意:grandFather一定存在
// 因为parent存在,且不是黑色节点,则parent一定不是根,则其一定有双亲
Node* grandfather = parent->_parent;
//1、父节点是祖父节点的左孩子
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//1、叔叔节点存在且叔叔节点的颜色为红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
//2、叔叔节点不存在或者叔叔节点的颜色为黑色
else
{
//1、如果cur是parent的右孩子,此时需要进行左单旋将情况转换为情况2
if (parent->_right == cur)
{
RotateL(parent);
swap(cur, parent);
}
//1、如果cur是parent的z左孩子,此时只需进行一个右单旋,并将parent的颜色变为黑,grandparent的颜色置红
RotateR(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
break;
}
}
//2、父节点是祖父节点的右孩子
else
{
Node* uncle = grandfather->_left;
//1、叔叔节点存在且叔叔节点的颜色为红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
//2、叔叔节点不存在或者叔叔节点的颜色为黑色
else
{
//1、若是cur为parent的左孩子,先进行一个右单旋转换为情况二一起处理
if (parent->_left == cur)
{
RotateR(parent);
swap(cur, parent);
}
//2、若是cur为parent的右孩子,进行一个左单旋,并将parent的颜色变为黑,grandparent的颜色置红
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
break;
}
}
}
//旋转完成之后,将根节点的颜色置成黑色
_root->_col = BLACK;
return true;
}
完整代码在下篇博客:红黑树的完整实现:https://blog.csdn.net/ly_6699/article/details/89816627
5. 红黑树的性能
红黑树是一棵近似平衡的二叉搜索树,相对于AVL树,降低了插入时的旋转次数,从而提高了二叉搜索树的性能。