左倾红黑树 ( L L R B ) (LLRB) (LLRB)
左倾红黑树从概念上讲是一颗2-3树,不过这颗2-3树是使用二叉树形式实现的。
每个节点都有一个颜色,如果是红色,表示该节点的父亲和该节点组成了2-3树中的3叉节点,即利用节点和其颜色信息联合起来 表示2-3树中的3叉节点。
旋转操作
NODE* LBTree::rotate_left(NODE* node) {
NODE* res = node->rc;
node->rc = res->lc;
res->lc = node;
swap(res->color, node->color);
return res;
}
NODE* LBTree::rotate_right(NODE* node) {
NODE* res = node->lc;
node->lc = res->rc;
res->rc = node;
swap(res->color, node->color);
return res;
}
插入
插入的过程完全可以类比2-3树的插入:
-
如果待插入的叶子节点只有一个键,那么直接插入,类比到左倾红黑树,就是插入到该叶子节点的左侧(如果是右侧则左旋),并置节点的颜色为红色。
-
如果 被插入的叶子节点有两个键,也就是节点满了,那么先插进去形成3个键的临时节点(4叉节点),然后选择中间的键交给其父亲节点,自身再分裂成两个节点,每个节点含有一个键。类比到左倾红黑树,插入到叶子节点的左侧(如果是右侧则左旋),然后叶子节点的父亲右旋,并改变它们的颜色(取反),实际上取反对应的就是4叉节点的分裂。
// 颜色取反操作
void LBTree::color_flip(NODE* node) {
node->color ^= 1;
if (node->lc) node->lc->color ^= 1;
if (node->rc) node->rc->color ^= 1;
}
NODE* LBTree::fix_up(NODE* node) {
if (!is_red(node->lc) && is_red(node->rc))
node = rotate_left(node);
if (is_red(node->lc) && is_red(node->lc->lc))
node = rotate_right(node);
if (is_red(node->lc) && is_red(node->rc))
color_flip(node);
return node;
}
NODE* LBTree::Insert(NODE* _root, int key) {
if (_root == nullptr) {
return new NODE(key, KRED);
}
if (key == _root->key)
;
else if (key < _root->key)
root->lc = Insert(root->lc, key);
else
root->rc = Insert(root->rc, key);
return fix_up(_root);
}
删除
没法和2-3树的删除进行类比。
删除节点分为两种情况:删除叶子节点和删除内部节点,但删除内部节点可以转化为删除叶子节点(假设读者已知二叉搜索树的删除过程),所以合并成一种情况考虑。考虑删除叶子节点:如果叶子节点是红色节点,直接删除不会影响黑色平衡。如果是黑色节点,则没法直接删了。所以为了让待删除的叶子节点一定是红色节点,在向下搜索的过程中保证当前节点的儿子是红色边的端点,即node->child is red || node->child->child is red
,怎么保证喃?如果当前节点的儿子和当前节点的儿子的儿子都不是红色节点,则翻转当前节点及它儿子们的颜色,回溯的时候再修复。论文Left-leaning Red-Black Trees 叙述了这个操作,且不会影响树的平衡。
上图翻转的操作将
h
h
h变成了2-3树中的4叉节点,注意到没,这和插入节点是相反的过程。
删除的过程有一点与2-3树删除共通:可以从右兄弟节点借一个键过来。
NODE* LBTree::Delete_min(NODE* node) {
if (node->lc == nullptr) {
delete node;
return nullptr;
}
if (!is_red(node->lc) && !is_red(node->lc->lc))
node = move_red2left(node); // 能从右儿子借最好,不能的话临时合并成4叉节点
node->lc = Delete_min(node->lc);
return fix_up(node);
}
NODE* LBTree::Delete_arby(NODE* _root, int key) {
if (_root == nullptr) return nullptr;
if (key < _root->key) {
if (!is_red(_root->lc) && !is_red(_root->lc->lc))
_root = move_red2left(_root);
_root->lc = Delete_arby(_root->lc, key);
}
else {
if (is_red(_root->lc)) _root = rotate_right(root); // 右旋使得右儿子是红色节点
if (key == _root->key && _root->rc == nullptr) { // 已经到了叶子,直接删除
delete _root;
return NULL;
}
if (!is_red(_root->rc) && !is_red(_root->rc->lc)) // 能从左儿子借最好,不能的话合并成4叉节点
_root = move_red2right(_root);
if (key == _root->key) {
_root->key = get_min(_root);
_root->rc = Delete_min(_root->rc);
}
else
_root->rc = Delete_arby(_root->rc, key);
}
return fix_up(_root);
}
参考
1.Left-leaning Red-Black Trees
2. https://oi-wiki.org/ds/llrbt/【强烈推荐】