红黑树和BST树、AVL树一样,都是带有排序性质的树。那么与这两种树不同的地方在哪?为什么在C++STL中的set和map都使用的红黑树?
本文将用易于理解的描述,使得每个人都能看懂红黑树中的调整操作。
因为每个节点不是红色就是黑色,所以需要定义一个颜色相关的枚举量。
还需要操作其父节点,所以定义一个parent指针。
template<typename _Ty>
class RBTree
{
private:
// 节点颜色
enum Color
{
BLACK,
RED
};
// 节点定义
struct Node
{
Node(_Ty data = _Ty(), Node* left = nullptr,
Node* right = nullptr, Node* parent = nullptr,
Color color = BLACK)
: val_(data)
, left_(left)
, right_(right)
, parent_(parent)
, color_(color)
{}
_Ty val_;
Node* left_;
Node* right_;
Node* parent_; // 指向当前节点的父节点
Color color_; // 当前节点的颜色
};
Node* root_;
};
此时要检查父节点颜色,如果父节点是黑色,插入完成;
如果父节点是红色,则出现连续的红色节点,就要开始做红黑树的插入调整操作:
父节点是红色,爷爷节点是黑色,且三者在同一侧。
如图新插入的节点C,如果父节点是红色,则为了保持红黑树的第4条性质,需要交换父亲节点和爷爷节点的颜色:
从局部图来看,这样似乎没错,但考虑不周,在这里还需要考虑爷爷节点的另一个孩子节点,这里叫作叔叔节点,如果叔叔节点本身为红色,那么经过这样交换后,又不满足红黑树性质了。所以正确的情况为:
即使到了这一步,也不要觉得完成了,我们把新节点的爷爷节点的颜色改成红色了,这时候还需要考虑两种情况
插入节点的父节点是红色,爷爷节点是黑色,三者在同一侧,叔叔节点是黑色。
为了满足红黑树性质,需要将其父节点颜色与爷爷节点颜色进行交换。
出现的问题:将爷爷节点A改成红色,那么就会导致从根节点到节点D的这条路径中,少一个黑色节点。不满足红黑树第5条性质(所有路径的黑色节点数量相等)。
解决方案:以A节点为参数做右旋转操作,旋转完成后,符合红黑树的性质,调整完成。
父节点是红色,爷爷节点是黑色,三者不在一侧,叔叔节点是黑色。
如图:如果直接调整颜色,那么还是会影响红黑树的第5个性质,所以可以先将三者旋转成同一侧,显然,这里需要对父节点B做左旋转,旋转后如图
将其旋转完成后,ABC处于同一侧,这时候再说如何改变颜色,但通过观察,旋转完成后,变成和情况2是一个情况,所以直接让其转到情况2的操作:
上面的三种情况,都是插入的节点在爷爷节点的左孩子上,那么与之对应的,右边也有相应的三种情况,所以红黑树的插入调整,一共有六种调整。
总结:红黑树的插入最重要的是考虑叔叔节点的颜色。
// 返回节点颜色
Color getColor(Node* node)
{
return node == nullptr ? BLACK : node->color_;
}
// 设置颜色
void setColor(Node* node, Color color)
{
node->color_ = color;
}
// 返回节点的左孩子
Node* getLeft(Node* node)
{
return node->left_;
}
// 返回节点的右孩子
Node* getRight(Node* node)
{
return node->right_;
}
// 返回节点的父节点
Node* getParent(Node* node)
{
return node->parent_;
}
如图,对node进行左旋转操作,它和AVL树的左旋转有什么区别?
AVL树左旋转代码:
// 左旋转操作
void leftRotate(Node* node)
{
Node* child = node->right_;
node->right_ = child->left_;
child->left_ = node;
}
由于红黑树多出了parent指针,所以在旋转时,要更改六个地址:
加上对parent操作的代码:
// 左旋转操作
void leftRotate(Node* node)
{
Node* child = node->right_;
child->parent_ = node->parent_;
if (node->parent_ == nullptr)
{
root_ = child;
}
else
{
if (node->parent_->left_ == node)
{
// node在父节点的左孩子
node->parent_->left_ = child;
}
else
{
// node在父节点的右孩子
node->parent_->right_ = child;
}
}
node->right_ = child->left_;
if (child->left_ != nullptr)
{
child->left_->parent_ = node;
}
child->left_ = node;
node->parent_ = child;
}
代码实现和左旋转刚好相反
// 右旋转
void rightRotate(Node* node)
{
Node* child = node->left_;
child->parent_ = node->parent_;
if (node->parent_ == nullptr)
{
// node原来就是root节点
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树的插入代码
// 插入操作
void insert(const _Ty& data)
{
if (root_ == nullptr) // 1
{
root_ = new Node(data);
return;
}
Node* parent = nullptr; // 2
Node* cur = root_; // 3
while (cur != nullptr)
{
if (cur->val_ > data) // 4
{
parent = cur;
cur = cur->left_;
}
else if (cur->val_ < data) // 5
{
parent = cur;
cur = cur->right_;
}
else
{
return; // 遇到重复的,则什么都不做 // 6
}
}
// 设置当前节点的parent和颜色
Node* node = new Node(data, nullptr, nullptr, parent, RED); // 7
if (parent->val_ > data)
{
parent->left_ = node;
}
else
{
parent->right_ = node;
}
// 如果新插入的红色节点,父节点也是红色,
// 则不满足红黑树性质,进行插入调整操作
if (RED == getColor(parent)) // 8
{
fixAfterInsert(node);
}
}
该函数的作用就是在进行插入完成后,为了保证红黑树性质,所做出的调整(修正)操作,涉及更改颜色,旋转操作。
// 红黑树的插入调整操作
void fixAfterInsert(Node* node)
{
// 如果当前红色节点的父节点也是红色
while (RED == getColor(getParent(node))) // 1
{
// 如果当前节点的父节点是爷爷节点的左孩子
if (getLeft(getParent(getParent(node))) == getParent(node)) // 2
{
// 插入的节点在左子树
// 找到叔叔节点,改变颜色
Node* uncle = getRight(getParent(getParent(node))); // 3
if (RED == getColor(uncle)) // 情况1:叔叔节点是红色
{
setColor(uncle, BLACK);
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
// 继续调整上面的
node = getParent(getParent(node)); // 4
}
else
{
// 先处理情况三,转成情况二
if (getRight(getParent(node)) == node) // 5
{
node = getParent(node);
leftRotate(node);
}
// 6 处理情况二
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
rightRotate(getParent(getParent(node)));
break; // 调整完成
}
}
else // 7
{
// 插入的节点在右子树
Node* uncle = getLeft(getParent(getParent(node)));
if (RED == getColor(uncle)) // 情况1:叔叔节点是红色
{
setColor(uncle, BLACK);
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
// 继续调整上面的
node = getParent(getParent(node));
}
else
{
// 先处理情况三,转成情况二
if (getLeft(getParent(node)) == node)
{
node = getParent(node);
rightRotate(node);
}
// 处理情况二
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
leftRotate(getParent(getParent(node)));
break; // 调整完成
}
}
}
// 强制root_为黑色
setColor(root_, BLACK); // 8
}
这里可以配合上面的图片来理解:
红黑树的删除,按找孩子节点划分,可分为:
按照删除节点的颜色划分,可分为:
删除的节点在父节点左边,右边的兄弟是黑色,兄弟节点的右孩子节点是红色。
如图,删除的B节点是黑色节点,为了保证红黑树的性质,需要从兄弟节点C借一个黑色节点,这时候需要C节点的孩子是红色,因为可以将其染成黑色,来保证红黑树的性质。
解决方案:
(1)交换父节点A和兄弟节点C的颜色;
(2)把兄弟节点的孩子节点置为黑色;
(3)对父节点A做一个左旋转操作。
删除的节点在父节点左边,右边的兄弟节点是黑色,兄弟节点的左孩子是红色。
如下图,这种情况下不能像情况1那样,直接从兄弟节点借调一个,因为兄弟节点C右边的路径上没有红色节点用以涂成黑色。
解决方案:
(1)对兄弟节点进行右旋转,使得C的左孩子和C在一条路径上,得到下图中的图2;
(2)为了保证红黑树性质,需要将D和C节点的颜色交换(A可能为红色,不能出现连续红色);
(3)接下来是不是很熟悉,就是转换成了上面的情况1;
删除节点是父节点左孩子,兄弟节点是黑色,兄弟节点的两个孩子节点都是黑色。
这种情况下,不能从兄弟节点那里借调。可以选择的方法是:直接将兄弟节点染成红色。
将兄弟节点涂成红色后,还要检查父节点颜色,如果是红色,则出现连续红色,需要调整,将其父节点染成黑色;
如果是黑色,则向上回溯,查看是否是其他情况。
待删除节点是左孩子,兄弟节点是红色。
调整目的还是和上面一样,先给待删除节点找一个黑色兄弟节点。
解决方案:
(1)将兄弟节点C和父节点A的颜色进行交换;
(2)对父节点A做左旋转操作,这样就会给待删除节点B匹配一个黑色的兄弟节点;
(3)然后转换为上面三种情况。
有上面的四种情况,那就有其镜像的情况,所以删除调整操作一共有8种情况。
总结:删除最重要的是看兄弟节点颜色。
同样是基于BST树的删除:
// 删除操作
void remove(const _Ty& data)
{
if (root_ == nullptr) // 1
{
return;
}
Node* cur = root_; // 2
while (cur != nullptr)
{
if (cur->val_ > data)
{
cur = cur->left_;
}
else if (cur->val_ < data)
{
cur = cur->right_;
}
else
{
break;
}
}
// 没找到节点,返回 // 3
if (cur == nullptr)
{
return;
}
// 删除前驱节点 情况三 // 4
if (getLeft(cur) != nullptr && getRight(cur) != nullptr)
{
Node* pre = cur->left_; // 5
while (getParent(pre) != nullptr)
{
pre = pre->right_;
}
cur->val_ = pre->val_;
cur = pre; // cur指向前驱节点
}
// 删除cur指向的节点 情况一和二 // 6
Node* child = cur->left_; // 让child指向不为空的孩子
if (child == nullptr)
{
child = cur->right_;
}
// left right parent
if (child != nullptr) // 7
{
child->parent_ = cur->parent_;
if (getParent(cur) == nullptr)
{
root_ = child; // 8
}
else // 9
{
if (getLeft(getParent(cur)) == cur)
{
cur->parent_->left_ = child;
}
else
{
cur->parent_->right_ = child;
}
}
Color c = getColor(cur); // 10
delete cur;
// 如果删除的黑色节点,需要调整到满足红黑树性质 // 11
if (c == BLACK)
{
fixAfterRemove(child);
}
}
else // child == nullptr
{
if (getParent(cur) == nullptr) // 12
{
delete cur;
root_ = nullptr;
return;
}
else
{
// 删除的cur是叶子节点
if (BLACK == getColor(cur)) //13
{
fixAfterRemove(cur);
}
if (getLeft(getParent(cur)) == cur) // 14
{
cur->parent_->left_ = nullptr;
}
else
{
cur->parent_->right_ = nullptr;
}
delete cur;
}
}
}
调整操作一共八种情况,左右各四种,理论讲解完后,现在实现代码:
// 红黑树的删除调整操作
void fixAfterRemove(Node* node)
{
while (getColor(node) == BLACK) // 1
{
if (getLeft(getParent(node)) == node) // 2
{
// 删除的黑色节点在左子树
Node* brother = getRight(getParent(node)); // 3
if (getColor(brother) == RED) // 情况四 // 4
{
// 交换父节点和兄弟节点颜色
setColor(getParent(node), RED);
setColor(brother, BLACK);
leftRotate(getParent(node));
brother = getRight(parent(node));
}
// 兄弟是黑色 // 5
if (getColor(getLeft(brother)) == BLACK
&& getColor(getRight(brother)) == BLACK) // 情况三 // 6
{
setColor(brother, RED);
node = getParent(node);
}
else
{
if (getColor(getRight(brother)) != RED) // 情况二 // 7
{
setColor(brother, RED);
setColor(getLeft(brother), BLACK);
rightRotate(brother);
brother = getRight(getParent(node));
}
// 归结为情况一 // 8
setColor(brother, getColor(getParent(node)));
setColor(getParent(node), BLACK);
setColor(getRight(brother), BLACK);
leftRotate(getParent(node));
break;
}
}
else // 9
{
// 删除的黑色节点在右子树
Node* brother = getLeft(getParent(node));
if (getColor(brother) == RED)
{
// 交换父节点和兄弟节点颜色
setColor(getParent(node), RED);
setColor(brother, BLACK);
rightRotate(getParent(node));
brother = getLeft(getParent(node));
}
// 兄弟是黑色
if (getColor(getLeft(brother)) == BLACK
&& getColor(getRight(brother)) == BLACK) // 情况三
{
setColor(brother, RED);
node = getParent(node);
}
else
{
if (getColor(getLeft(brother)) != RED) // 情况二
{
setColor(brother, RED);
setColor(getRight(brother), BLACK);
leftRotate(brother);
brother = getLeft(getParent(node));
}
// 归结为情况一
setColor(brother, getColor(getParent(node)));
setColor(getParent(node), BLACK);
setColor(getLeft(brother), BLACK);
rightRotate(getParent(node));
break;
}
}
}
// 如果发现node指向的节点是红色,直接涂成黑色,调整结束
setColor(node, BLACK); // 10
}
#include
#include
using namespace std;
template<typename _Ty>
class RBTree
{
public:
RBTree() :root_(nullptr) {}
// 插入操作
void insert(const _Ty& data)
{
if (root_ == nullptr)
{
root_ = new Node(data);
return;
}
Node* parent = nullptr;
Node* cur = root_;
while (cur != nullptr)
{
if (cur->val_ > data)
{
parent = cur;
cur = cur->left_;
}
else if (cur->val_ < data)
{
parent = cur;
cur = cur->right_;
}
else
{
return; // 遇到重复的,则什么都不做
}
}
// 设置当前节点的parent和颜色
Node* node = new Node(data, nullptr, nullptr, parent, RED);
if (parent->val_ > data)
{
parent->left_ = node;
}
else
{
parent->right_ = node;
}
// 如果新插入的红色节点,父节点也是红色,
// 则不满足红黑树性质,进行插入调整操作
if (RED == getColor(parent))
{
fixAfterInsert(node);
}
}
// 删除操作
void remove(const _Ty& data)
{
if (root_ == nullptr)
{
return;
}
Node* cur = root_;
while (cur != nullptr)
{
if (cur->val_ > data)
{
cur = cur->left_;
}
else if (cur->val_ < data)
{
cur = cur->right_;
}
else
{
break;
}
}
// 没找到节点,返回
if (cur == nullptr)
{
return;
}
// 删除前驱节点 情况三
if (getLeft(cur) != nullptr && getRight(cur) != nullptr)
{
Node* pre = cur->left_;
while (getRight(pre) != nullptr)
{
pre = pre->right_;
}
cur->val_ = pre->val_;
cur = pre; // cur指向前驱节点
}
// 删除cur指向的节点 情况一和二
Node* child = cur->left_; // 让child指向不为空的孩子
if (child == nullptr)
{
child = cur->right_;
}
// left right parent
if (child != nullptr)
{
child->parent_ = cur->parent_;
if (getParent(cur) == nullptr)
{
root_ = child;
}
else
{
if (getLeft(getParent(cur)) == cur)
{
cur->parent_->left_ = child;
}
else
{
cur->parent_->right_ = child;
}
}
Color c = getColor(cur);
delete cur;
// 如果删除的黑色节点,需要调整到满足红黑树性质
if (c == BLACK)
{
fixAfterRemove(child);
}
}
else // child == nullptr
{
if (getParent(cur) == nullptr)
{
delete cur;
root_ = nullptr;
return;
}
else
{
// 删除的cur是叶子节点
if (BLACK == getColor(cur))
{
fixAfterRemove(cur);
}
if (getLeft(getParent(cur)) == cur)
{
cur->parent_->left_ = nullptr;
}
else
{
cur->parent_->right_ = nullptr;
}
delete cur;
}
}
}
private:
// 节点颜色
enum Color
{
BLACK,
RED
};
// 节点定义
struct Node
{
Node(_Ty data = _Ty(), Node* left = nullptr,
Node* right = nullptr, Node* parent = nullptr,
Color color = BLACK)
: val_(data)
, left_(left)
, right_(right)
, parent_(parent)
, color_(color)
{}
_Ty val_;
Node* left_;
Node* right_;
Node* parent_; // 指向当前节点的父节点
Color color_; // 当前节点的颜色
};
// 返回节点颜色
Color getColor(Node* node)
{
return node == nullptr ? BLACK : node->color_;
}
// 设置颜色
void setColor(Node* node, Color color)
{
node->color_ = color;
}
// 返回节点的左孩子
Node* getLeft(Node* node)
{
return node->left_;
}
// 返回节点的右孩子
Node* getRight(Node* node)
{
return node->right_;
}
// 返回节点的父节点
Node* getParent(Node* node)
{
return node->parent_;
}
// 左旋转操作
void leftRotate(Node* node)
{
Node* child = node->right_;
child->parent_ = node->parent_;
if (node->parent_ == nullptr)
{
root_ = child;
}
else
{
if (node->parent_->left_ == node)
{
// node在父节点的左孩子
node->parent_->left_ = child;
}
else
{
// node在父节点的右孩子
node->parent_->right_ = child;
}
}
node->right_ = child->left_;
if (child->left_ != nullptr)
{
child->left_->parent_ = node;
}
child->left_ = node;
node->parent_ = child;
}
// 右旋转
void rightRotate(Node* node)
{
Node* child = node->left_;
child->parent_ = node->parent_;
if (node->parent_ == nullptr)
{
// node原来就是root节点
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;
}
// 红黑树的插入调整操作
void fixAfterInsert(Node* node)
{
// 如果当前红色节点的父节点也是红色
while (RED == getColor(getParent(node)))
{
// 如果当前节点的父节点是爷爷节点的左孩子
if (getLeft(getParent(getParent(node))) == getParent(node))
{
// 插入的节点在左子树
// 找到叔叔节点,改变颜色
Node* uncle = getRight(getParent(getParent(node)));
if (RED == getColor(uncle)) // 情况1:叔叔节点是红色
{
setColor(uncle, BLACK);
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
// 继续调整上面的
node = getParent(getParent(node));
}
else
{
// 先处理情况三,转成情况二
if (getRight(getParent(node)) == node)
{
node = getParent(node);
leftRotate(node);
}
// 处理情况二
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
rightRotate(getParent(getParent(node)));
break; // 调整完成
}
}
else
{
// 插入的节点在右子树
Node* uncle = getLeft(getParent(getParent(node)));
if (RED == getColor(uncle)) // 情况1:叔叔节点是红色
{
setColor(uncle, BLACK);
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
// 继续调整上面的
node = getParent(getParent(node));
}
else
{
// 先处理情况三,转成情况二
if (getLeft(getParent(node)) == node)
{
node = getParent(node);
rightRotate(node);
}
// 处理情况二
setColor(getParent(node), BLACK);
setColor(getParent(getParent(node)), RED);
leftRotate(getParent(getParent(node)));
break; // 调整完成
}
}
}
// 强制root_为黑色
setColor(root_, BLACK);
}
// 红黑树的删除调整操作
void fixAfterRemove(Node* node)
{
while (getColor(node) == BLACK)
{
if (getLeft(getParent(node)) == node)
{
// 删除的黑色节点在左子树
Node* brother = getRight(getParent(node));
if (getColor(brother) == RED) // 情况四
{
// 交换父节点和兄弟节点颜色
setColor(getParent(node), RED);
setColor(brother, BLACK);
leftRotate(getParent(node));
brother = getRight(parent(node));
}
// 兄弟是黑色
if (getColor(getLeft(brother)) == BLACK
&& getColor(getRight(brother)) == BLACK) // 情况三
{
setColor(brother, RED);
node = getParent(node);
}
else
{
if (getColor(getRight(brother)) != RED) // 情况二
{
setColor(brother, RED);
setColor(getLeft(brother), BLACK);
rightRotate(brother);
brother = getRight(getParent(node));
}
// 归结为情况一
setColor(brother, getColor(getParent(node)));
setColor(getParent(node), BLACK);
setColor(getRight(brother), BLACK);
leftRotate(getParent(node));
break;
}
}
else
{
// 删除的黑色节点在右子树
Node* brother = getLeft(getParent(node));
if (getColor(brother) == RED) // 情况四
{
// 交换父节点和兄弟节点颜色
setColor(getParent(node), RED);
setColor(brother, BLACK);
rightRotate(getParent(node));
brother = getLeft(getParent(node));
}
// 兄弟是黑色
if (getColor(getLeft(brother)) == BLACK
&& getColor(getRight(brother)) == BLACK) // 情况三
{
setColor(brother, RED);
node = getParent(node);
}
else
{
if (getColor(getLeft(brother)) != RED) // 情况二
{
setColor(brother, RED);
setColor(getRight(brother), BLACK);
leftRotate(brother);
brother = getLeft(getParent(node));
}
// 归结为情况一
setColor(brother, getColor(getParent(node)));
setColor(getParent(node), BLACK);
setColor(getLeft(brother), BLACK);
rightRotate(getParent(node));
break;
}
}
}
// 如果发现node指向的节点是红色,直接涂成黑色,调整结束
setColor(node, BLACK);
}
Node* root_; // 红黑树的根节点
};
int main(void)
{
RBTree<int> rbtree;
for (int i = 1; i <= 4; ++i)
{
rbtree.insert(i);
}
return 0;
}