定义 : 是一种自平衡的二叉搜索树 , 也就是特殊的二叉树 .
注 : 它不是AVL树
问题 : 红黑树一个节点的左右子树高度差最多允许差多少?
答 : 长的不能超过短的的两倍
每个节点都包含5个域 : color , key , left , right 和 parent
enum Color
{BLACK , RED};
template<typename T>
class RBTree
{
public:
RBTree() :_root(nullptr) {}
private:
struct RBNode
{
RBNode(T data = T(),
Color color = BLACK,
RBNode *parent = nullptr)
:_data(data) //值
, _left(nullptr) //左孩子
, _right(nullptr) //右孩子
, _parent(parent) //父节点
, _color(color) //颜色
{}
T _data;
RBNode *_left;
RBNode *_right;
RBNode *_parent;
Color _color;
};
RBNode *_root; // 指向红黑树的根节点
//获取节点的颜色
Color color(RBNode *node)
{
return node == nullptr ? BLACK : node->_color;
}
//设置节点的颜色
void setColor(RBNode *node, Color color)
{
node->_color = color;
}
//获取左孩子节点
RBNode* left(RBNode *node)
{
return node->_left;
}
//获取右孩子节点
RBNode* right(RBNode *node)
{
return node->_right;
}
//获取父节点
RBNode* parent(RBNode *node)
{
return node->_parent;
}
};
红黑树中的一个重要的概念 : 黑高度 , 将红黑树中从某个节点x 出发(不包括该节点)到达一个叶节点的任意一条路径上的黑节点的个数称为该节点的黑高度 , 用bh(x)表示.
当在红黑树上进行插入和删除的操作的时候 ,树的红黑性质将被破坏 为了保持这些性质 , 就要改变树中某些节点的颜色以及指针的结构 . 指针的结构是通过旋转来完成的.
旋转是一种能够保持树中节点的中缀次序的局部操作
旋转(左旋和右旋)的时间复杂度都为O(1)
//左旋 , 6处
/*
基本旋转过程与AVL树的相似
但是在红黑树中 , 加入了指向父节点的指针这一成员
所以在旋转之后要去修改相关节点的父节点的指向
而要修改的指向有6处 : 曾祖父的孩子 , 祖父的父亲 ,
祖父的右孩子 , 父亲的父亲 , 父亲的左孩子 , 父亲左孩子(左孙子)的父亲
*/
void leftRotate(RBNode* node)
{
//父节点在旋转之后会成为新的根节点(局部)
RBNode* child = node->_right;
//旋转后 , 父节点的父亲变为 原祖父节点的父亲(即父亲的父亲)
child->_parent = node->_parent;
//如果祖父节点是根节点 , 旋转后将根节点置为原父节点
if (node->_parent == nullptr)
{
_root = child;
}
else
{
//祖父节点是曾祖父节点的左孩子 , 旋转后曾祖父节点的左孩子是父节点
//(即曾祖父的孩子)
if (node->_parent->_left == node)
{
node->_parent->_left = child;
}
else
{
//否则旋转后曾祖父节点的右孩子是父节点
node->_parent->_right = child;
}
}
//旋转后 , 祖父节点的右孩子是父节点的左孩子(即祖父的右孩子)
node->_right = child->_left;
if (child->_left != nullptr)
{
//旋转后 , 父节点的左孩子的父节点变为祖父节点(即左孙子的父亲)
child->_left->_parent = node;
}
//旋转后 , 父节点的左孩子是祖父节点(即父亲的左孩子)
child->_left = node;
//旋转后祖父节点的父亲变为 , 原父节点(即祖父的父亲)
node->_parent = child;
}
//右旋
/*
基本旋转过程与AVL树的相似
但是在红黑树中 , 加入了指向父节点的指针这一成员
所以在旋转之后要去修改相关节点的父节点的指向
而要修改的指向有6处 : 曾祖父的孩子 , 祖父的父亲 ,
祖父的左孩子 , 父亲的父亲 , 父亲的右孩子 , 父亲右孩子(右孙子)的父亲
*/
void rightRotate(RBNode* node)
{
//父节点在旋转之后会成为新的根节点(局部)
RBNode* child = node->_left;
//旋转后 , 父节点的父亲变为 原祖父节点的父亲(即父亲的父亲)
child->_parent = node->_parent;
//如果祖父节点是根节点 , 旋转后将根节点置为原父节点
if (node->_parent == nullptr)
{
_root = child;
}
else
{
//祖父节点是曾祖父节点的左孩子 , 旋转后曾祖父节点的左孩子是父节点
//(即曾祖父的孩子)
if (node->_parent->_left == node)
{
node->_parent->_left = child;
}
else
{
//否则旋转后曾祖父节点的右孩子是父节点
node->_parent->_right = child;
}
}
//旋转后 , 祖父节点的左孩子是父节点的右孩子(即祖父的左孩子)
node->_left = child->_right;
if (child->_right != nullptr)
{
//旋转后 , 父节点的右孩子的父节点变为祖父节点(即右孙子的父亲)
child->_right->_parent = node;
}
//旋转后 , 父节点的右孩子是祖父节点(即父亲的右孩子)
child->_right = node;
//旋转后祖父节点的父亲变为 原父节点(即祖父的父亲)
node->_parent = child;
}
(之前在BST树的帖子中已经详细讲了插入的操作 , 这里不再赘述 , 重点来讲插入后的调整)
先使用普通二叉搜索树的节点插入算法将新节点插入树中 , 然后将该节点涂为红色 . 为了不破坏红黑性质 , 需要对有关节点进行重新着色并旋转 . 因为向二叉搜索树中添加一个元素 , 就是向其中添加一个叶子节点(注意这里忽略红黑树的NIL节点) , 所以在向红黑树中插入一个元素时就必须将其涂为红色 . 如果不这样 , 就会违反红黑性质(4) , 即从某一节点到达其子孙叶节点的每一条简单路径上包含的黑色节点的个数就会不同 .
如果插入的节点的父节点是黑色 , 那么红黑性质没有破坏 , 操作就此完成 ; 但是如果插入的节点的父节点是红色的 , 就违反了性质(3) , 只有在父节点是红色的情况下是需要修复(调整)的
可以将情况归结为3种 :
2.叔叔的节点是黑色 , 祖父节点是黑色 , 且插入的新节点与父节点和祖父节点在一条直线上 . 以祖父节点进行旋转 ,将祖父的黑色(送)给旋转上来的父节点 .
3.叔叔节点是黑色 , 祖父的节点是黑色 , 且新插入的节点与父节点和祖父节点不在一条直线上 , 以父节点进行旋转 , 使其祖父孙三节点在一条直线上 , 然后再以祖父节点进行旋转 , 将祖父的黑色(送)给旋转上来的父节点 .
注 : 第一种情况相对复杂 , 如果插入的节点的叔叔节点为红色 , 那么就有可能无论如何旋转总是会出现"红红节点"的情况 . 这时所采用的解决方法就是逐层上升式的调整和着色
// 红黑树的插入
void insert(const T &val)
{
if (_root == nullptr)
{
_root = new RBNode(val, BLACK);
return;
}
RBNode *parent = nullptr;
RBNode *cur = _root;
while (cur != nullptr)
{
parent = cur;
if (cur->_data > val)
{
cur = cur->_left;
}
else if (cur->_data < val)
{
cur = cur->_right;
}
else
{
return;
}
}
//以红色节点插入到红黑树中
RBNode *node = new RBNode(val, RED, parent);
if (val < parent->_data)
{
parent->_left = node;
}
else
{
parent->_right = node;
}
//父亲节点为红色 , 红黑树性质被破坏 , 需要进行调整
if (color(parent) == RED)
{
fixAfterInsert(node);
}
}
//插入调整
/*
插入的三种情况 , 只有在父节点是红色的情况下才进行调整
情况1 : 看叔叔结点是红色, 叔叔和父节点都是红色 , 将叔叔和父亲变为黑 , 将祖父变为红 , 此时指向祖父 , 再向上检查
情况2 : 看叔叔 , 叔叔的节点是黑色 , (祖父的节点是黑色) , 且新插入的节点与父节点和祖父节点在一条直线上 , 以祖父节点进行旋转 ,将祖父的黑色(送)给旋转上来的父节点
情况3 : 看叔叔 , 叔叔节点是黑色 , (祖父的节点是黑色) , 且新插入的节点与父节点和祖父节点不在一条直线上 , 以父节点进行旋转 , 使其祖父孙三节点在一条直线上 ,
然后再以祖父节点进行旋转 , 将祖父的黑色(送)给旋转上来的父节点
*/
void fixAfterInsert(RBNode *node)
{
while (color(parent(node)) == RED)
{
//插在了祖先节点的左子树当中
if (left(parent(parent(node))) == parent(node))
{
// 插在了祖先节点的左子树当中
RBNode *uncle = right(parent(parent(node)));
// 情况1 : 叔叔结点是红色
if (color(uncle) == RED)
{
//将父亲和叔叔节点都变为黑色
setColor(parent(node), BLACK);
setColor(uncle, BLACK);
//将祖父节点变为红色
setColor(parent(parent(node)), RED);
//将指针指向祖父节点 , 继续向上检查
node = parent(parent(node));
}
else
{
// 情况3
/*
情况3 : 叔叔结点是黑色,且插入结点与其父亲结点 , 祖父结点不在一条直线上
这里我们为了和后面情况2的代码兼容,因此让node
指向中间节点(父节点),进行旋转操作后node指向
三个节点中最后一个结点。
*/
if (node == right(parent(node)))
{
node = parent(node);
//进行左旋操作
leftRotate(node);
}
// 情况2
/*
情况2 : 叔叔结点是黑色,且插入结点与其父亲结点 , 祖父结点在一条直线上
*/
//将父节点置为黑色 , 旋转后父节点为新的根节点(局部)
setColor(parent(node), BLACK);
//将祖父节点置为红色 , 旋转后成为原父节点的右孩子
setColor(parent(parent(node)), RED);
//进行右旋操作
rightRotate(parent(parent(node)));
break;
}
}
else
{
// 插在了祖先节点的右子树当中 , 与上述过程是镜像关系
RBNode *uncle = left(parent(parent(node)));
// 情况1 : 叔叔结点是红色
/*
将父亲和叔叔节点置为黑色 , 祖父节点置为红色
将指针指向祖父节点 , 继续调整
*/
if (color(uncle) == RED)
{
setColor(parent(node), BLACK);
setColor(uncle, BLACK);
setColor(parent(parent(node)), RED);
node = parent(parent(node));
}
else
{
// 情况3
/*
叔叔结点是黑色,且插入结点与其父亲结点 , 祖父结点不在一条直线上
先进行旋转 , 使插入节点 , 其父节点和祖先节点在同一条直线上
*/
if (node == left(parent(node)))
{
node = parent(node);
rightRotate(node);
}
// 情况2
setColor(parent(node), BLACK);
setColor(parent(parent(node)), RED);
leftRotate(parent(parent(node)));
break;
}
}
}
//在调整过程中有可能修改根节点的颜色为红色 , 需要修改为黑色
setColor(_root, BLACK);
}
(之前在BST树的帖子中已经详细讲了删除的操作 , 这里不再赘述 , 重点来讲删除后的调整)
从红黑树中删除一个节点的方法就是在节点删除(通过普通二叉搜索节点删除方法)后 , 针对不同情况对红黑树进行调整以保持其红黑性质的过程
删除红色不会影响红黑树的性质 , 如果是黑色的 , 就需要从child开始调整 .
删除后调整的4种情况 :
1.删除的节点是红色或者它是树的根节点 , 直接调整成黑色节点 , 结束
2.1.兄弟是黑色 , 兄弟的右孩子是红色 , 此时做两次旋转 , (此时才能借调一个黑色的节点到左子树上)
4.兄弟是红色 , 兄弟的孩子都(一定)是黑色 , 此时直接做一个旋转操作 , 并改变节点的颜色
//红黑树的删除操作
void remove(const T &val)
{
if (_root == nullptr)
return;
RBNode *parent = nullptr;
RBNode *cur = _root;
while (cur != nullptr)
{
if (cur->_data > val)
{
parent = cur;
cur = cur->_left;
}
else if(cur->_data < val)
{
parent = cur;
cur = cur->_right;
}
else
{
break;
}
}
if (cur == nullptr)
return;
//情况3 , 同BST树一样 , 找前驱节点
if (cur->_left != nullptr && cur->_right != nullptr)
{
RBNode *old = cur;
parent = cur;
cur = cur->_left;
while (cur->_right != nullptr)
cur = cur->_right;
old->_data = cur->_data;
}
RBNode *child = cur->_left;
if (child == nullptr)
child = cur->_right;
//情况2
if (child != nullptr)
{
child->_parent = cur->_parent;
if (cur->_parent == nullptr)
{
_root = child;
}
else
{
if (cur->_parent->_left == cur)
{
cur->_parent->_left = child;
}
else
{
cur->_parent->_right = child;
}
}
Color color = color(cur);
delete cur;
if (color == BLACK)
{
fixAfterRemove(child);
}
}
else
{
// child == nullptr
if (parent == nullptr)
{
_root = nullptr;
}
else
{
if (color(cur) == BLACK)
{
fixAfterRemove(cur);
}
if (cur->_parent->_left == cur)
{
cur->_parent->_left = nullptr;
}
else
{
cur->_parent->_right = nullptr;
}
delete cur;
}
}
}
//删除调整
/*
删除后调整的4种情况 :
1.删除的节点是红色或者它是树的根节点 , 直接调整成黑色节点 , 结束
2.1.兄弟是黑色 , 兄弟的右孩子是红色 , 此时做两次旋转 , (此时才能借调一个黑色的节点到左子树上)
2.2.兄弟是黑色 , 兄弟的左孩子是红色 , 此时做两次旋转
3.兄弟是黑色 , 兄弟的左右孩子都是黑色 , 直接把兄弟改成红色 , 将指针指向父节点 , 接着向上调整
4.兄弟是红色 , 兄弟的孩子都(一定)是黑色 , 此时直接做一个旋转操作 , 并改变节点的颜色
*/
void fixAfterRemove(RBNode* node)
{
while (Color(node) == BLACK)
{
//删除的节点在父节点的左侧
if (left(parent(node)) == node)
{
//兄弟节点就在右侧
RBNode *brother = right(parent(node));
//兄弟节点是红色 , 情况4
if (color(brother) == RED)
{
//旋转左旋后兄弟节点变为黑色
setColor(brother, BLACK);
//旋转后父节点变为红色
setColor(parent(node), RED);
//左旋操作
leftRotate(parent(node));
//旋转后的兄弟节点更新为原父节点(现在的)右节点
brother = right(parent(node));
}
//兄弟节点的左右孩子都为黑色 , 情况3
if(color(left(brother)) == BLACK
&& color(right(brother) == BLACK)
{
//将兄弟变为红色
setColor(brother, RED);
//将指针指向父节点 , 继续向上调整
node = parent(node);
}
else
{
//情况2.1和2.2
//兄弟的左孩子为红色
if (color(right(brother)) != RED)
{
//旋转后 , 兄弟节点的颜色变为红色
setColor(brother, RED);
//旋转后 , 兄弟节点的左孩子的颜色变为黑色
setColor(left(brother), BLACK);
//右旋操作
rightRotate(brother);
//旋转后的兄弟节点更新为原父节点(现在的)右节点
brother = right(parent(node));
}
//兄弟的右孩子为红色
//旋转后的兄弟节点的颜色为原父节点的父亲的颜色
setColor(brother, color(parent(node)));
//旋转后父节点的颜色为黑色
setColor(parent(node), BLACK);
//左旋操作
leftRotate(parent(node));
break;
}
}
//删除的节点在父节点的右侧 , 与在左侧为镜像关系
else
{
RBNode *brother = left(parent(node));
if (color(brother) == RED)
{
setColor(brother, BLACK);
setColor(parent(node), RED);
rightRotate(parent(node));
brother = left(parent(node));
}
if (color(left(brother)) == BLACK
&& color(right(brother) == BLACK)
{
setColor(brother, RED);
node = parent(node);
}
else
{
if (color(left(brother)) != RED)
{
setColor(brother, RED);
setColor(right(brother), BLACK);
leftRotate(brother);
brother = left(parent(node));
}
setColor(brother, color(parent(node)));
setColor(parent(node), BLACK);
rightRotate(parent(node));
break;
}
}
}
// 当前路径上如果碰见红色,直接把红色节点调整成黑色节点,结束
setColor(node, BLACK);
}
链接 :
<< C++高级数据结构算法#二叉搜索树(BST树)的基本操作(递归与非递归) >>