红黑树
平衡性
红黑树是一种平衡树,它首先满足二叉排序树的所有性质。我们在它的每个节点上定义一个颜色——红色或黑色。颜色必须满足以下三个性质:
- 根节点是黑色的。
- 红色节点的儿子必须是黑色的。(等价于:不能出现两个连续的红色节点)
- 对于任意一个节点\(u\),在它的子树中,从它出发到所有空节点的路径上经过的黑色节点个数全部相等。其中空节点定义为“叶节点的子节点”以及“只有一个子节点的节点的另一个子节点”。
我们对颜色没有其它要求了。由此可见,红黑树的平衡并不是严格的平衡,但它把不平衡的范围限制在了两倍以内——它严格要求黑色节点的数量保持平衡,并通过限制“不能出现连续红色节点”使得深度之差不会超过两倍。考虑根节点,根据性质3从它出发到所有空节点经过的黑节点个数相等,设经过\(M\)个黑节点,那么最长的一条链也不能超过\(2M\),因为红色节点必须间隔地出现,不然就会出现连续的红色节点了。因此树的高度不能超过\(2M\),因此总的节点个数就不能超过\(2^{2M}\)。而一条链的长度还必须至少是\(M\),因此总的节点个数至少得有\(2^M\)。这样我们就得到了由\(M\)确定的节点个数的一个取值范围,\(2^M \leq |V| \leq 2^{2M}\),解得\(\dfrac{1}{2}\log |V| \leq M \log |V|\)。同时树的高度\(M \leq h \leq 2M\)。由此代入得\(\dfrac{1}{2}\log |V| \leq h \leq 2\log|V|\)。所以\(h = O(\log |V|)\)。
红黑树的插入
和普通二叉排序树一样,我们从根节点出发找到插入节点应该到达的位置,然后维护红黑树颜色的性质。
如果新节点被接在一个黑节点后面当儿子,那我们发现只需令新节点的颜色为红色,那么任何一条性质都不会被破坏,插入就直接完成了。
如果新节点被接在红节点后面了怎么办呢?此时如果把新节点染成红色,那么就出现了两个连续的红节点,矛盾;如果染成黑色,那么性质3就被破坏了,因为从根节点到其它空节点间经过的黑色节点数量不会发生任何变化,而新的空节点到根节点之间却多了一个黑节点。 可见光改变插入节点的颜色不能解决问题,此时必须对平衡树的结构本身进行调整,而这种调整就是平衡树的“旋转”操作和对树上其它节点的颜色的修改操作,我们需要对各种可能出现的情况做出不同的应对。下面来一一讨论:
设新插入的节点为X,父节点为P,祖父节点为G,祖父的另一个子节点为S。我们只需讨论P是红色的情况。由于P是红色,G必须是黑色。而S可能是红色也可能是黑色,所以我们首先基于S的颜色分类。
我们还必须考虑P,G,S这些节点的存在性问题。如果P不存在,那么X就是根节点,意味着这之前平衡树没有任何节点,所以直接令X为黑色(性质1)即可;既然P存在,如果G不存在,那么说明P是根节点,所以X被接在黑节点后面了,直接染红色即可;既然P,G都存在,如果S不存在,我们发现那时我们需要做的事和S存在且为黑色是一模一样的,所以这时候我们可以“认为S是黑色的”——事实上这是一个更具有一般性的想法,如果我们想象空节点都是黑色的,并且在数路径上的黑色节点时把空节点包括进去,那么红黑树的所有性质依然是满足的,因此“认为空节点是黑色”其实可以直接作为红黑树定义的一部分,只不过我们没有这么做。
如果S是黑色,此时有两种情况:(1) X,P,G三点共线。此时令P旋转,我们就找到了一种符合条件的着色方案:令X、G为红色,P、S为黑色。(2)X,P,G为折线,此时我们旋两次X,就找到了着色方案:令P、G为红色,X、S为黑色。可以验证从根节点到所有空节点经过的黑节点个数都与插入前保持一致。并且由于我们都把祖父位置的节点染成了黑色,性质2也满足。
如果S是红色,那么我们不旋转,而是直接令X、G为红色,P、S为黑色。可以验证从根节点到所有空节点经过的黑节点个数都与插入前保持一致。但原先祖父节点是黑色的,现在祖父节点变成红色了,如果它的父亲也是红色,那么性质2就被破坏了。此时需要不停向上修改节点颜色,问题变得非常复杂。一个最好的办法是通过某种方法避免出现“S是红色”这种情况。这种情况的出现是由于在插入位置到根节点的这条链上,出现了“一个黑色节点有两个红色儿子”这种情况,我们希望在这整条链上避免这种情况发生。当我们从根节点出发往下走的时候,一旦发现这种情况,即出现当前节点U为黑色而它的子节点都是红色,那么立即令U改为红色而子节点改为黑色。这样做可能使得U和fa(U)都是红色了,但所幸U的兄弟节点不可能也是红色,不然就和fa(U)的红色冲突了。因此这是和上一段讨论的“S是黑色”时发生的冲突一样的情况(当时我们相当于默认了X初始为红色),当时是没有子树的情况,我们发现有没有子树并不影响我们的讨论,所以我们采用当时的处理方式,如果三点共线就对父节点旋转一次,否则就把自己旋转两次,然后按照一样的方式重新着色,这样解决了冲突,可以继续往下走了。(如果U就是根节点,那么直接把U也染成黑色即可)。由于我们保证了往下走的过程中每个节点都不会同时拥有两个红色儿子,因此到了最后要插入的位置就不会出现红色兄弟的情况了!
红黑树的删除
如果要删除的节点有两个子节点,那么在它的子树中一定存在后继。那么我们可以转而删除后继节点,然后把要删除的节点赋值为后继节点。这样二叉排序树的性质依然是满足的。转化为后继节点有一个好处,因为后继是没有左子节点的,因此我们对删除的讨论只需考虑包括“没有子节点或只有一个子节点”的情况。
对于这样的节点,如果它是红色的,又很好办:如果它是红色的叶节点,那么直接删除即可;如果它是红色的并有一个子节点,那么直接把子节点接到父节点上然后删除它即可。所以我们只需考虑删除黑色点的情况。
对于黑色点,如果他有子节点并且子节点是红色的,由于他只有这一个子节点,我们只需保证删除后这个子树里的“黑高”不变,考虑把他和子节点的颜色交换,即子节点变成黑色,它自己变成红色,那么删除它以后子树的黑高确实不变,一切性质都保持得很好,所以我们也解决了。余下的问题就是如何删除“黑色叶节点”或“带有单个黑色子节点的黑色节点”了。
对于前者,如果我们直接删除该节点,会使得剩下的空节点黑高少1从而破坏性质3;对于后者,如果我们直接删除该节点会使得子树内的黑高都少1,也破坏性质3。这两个问题其实是同一个问题,只要我们能通过某一些调整使得以被删点为根节点构成的子树内的黑高都增加1,到那时我们直接删去该节点就正好能使子树的黑高不变。为此,我们来分情况讨论如何完成“使子树的黑高增加1”这项工作。
设要被删除的节点是N,它的父亲为P,兄弟为S,S有内侧儿子C,外侧儿子D(C,D要么都不存在,要么都存在,我们记得不存在的点可以当作黑色)。N一定是黑色,现在我们要分别讨论P、S、C、D的颜色。
假设S是红色,那么P,C,D只能是黑色。要让N的黑高增加1,我们旋S,并把P染红,S染黑。(这样保证了不出现连续红色节点)这样以后N,C,D的黑高都不变,一切性质依然满足。但我们成功地把N的兄弟节点变成了黑色,这样我们就只需解决兄弟节点使黑色的问题了!
所以现在我们在S一定是黑色的情况下讨论:
如果P是红色,C、D是红色——那么旋转S,再交换P、S的颜色即可;
如果P是红色,C、D是黑色——只要交换P、S的颜色即可;
如果P是黑色,C、D是红色——旋转S,把D染黑即可。
如果P是黑色,C、D是黑色——这时我们把S染红,那么C、D的黑高都减少了1,所以我们现在需要把N、C、D子树的黑高全都+1,这等价于给P子树的黑高+1。而这样我们就把问题转化了一个给深度更低的子树黑高+1了,我们往上递归,余下就不用管了。如果已经到了根节点无法递归了,说明整棵树上除了N的子树以外黑高都被减少了1,所以我们已经达成了“N的子树黑高增加1”的效果了。
这样我们就讨论完了所有C、D同色的情况。接下来考虑C、D不同色的情况:
我们假设C是内侧节点,D是外侧节点。如果C是黑色,D是红色,那么旋转S,再把D染黑即可。如果C是红色,D是黑色(我们将发现P的颜色不影响我们的操作正确性),那么旋转C并交换C、S的颜色,此时一切性质都保持,但红色节点到了外侧,黑色节点到了内测,这就转化为前一种情况了。
这样我们就解决了黑高增加1的问题。于是我们只需直接删除N点就完成了红黑树的删除操作。
#include <cstdio>
using namespace std;
class RedBlackTree{
class Node{
public:
Node *ch[2],*fa;
int *val,*color,*side,*cnt,*sz;
Node(){
ch[0] = nullptr;
ch[1] = nullptr;
fa = nullptr;
val = nullptr;
color = nullptr;
side = nullptr;
cnt = nullptr;
sz = nullptr;
}
~Node(){
if(val != nullptr) delete val;
if(color != nullptr) delete color;
if(side != nullptr) delete side;
if(cnt != nullptr) delete cnt;
if(sz != nullptr) delete sz;
}
};
public:
Node *rt;
int total_node;
Node* RBT_find(int x){
if(total_node == 0) return nullptr;
Node *u = rt;
while(*(u->val) != x){
int cur_val = *(u->val);
if(u->ch[0] != nullptr && x < cur_val) u = u->ch[0];
else if(u->ch[1] != nullptr && x > cur_val) u = u->ch[1];
else break;
}
return u;
}
int RBT_rank_x(int x){
if(total_node == 0) return 0;
Node *u = rt;
int res = 0;
while(*(u->val) != x){
int cur_val = *(u->val);
if(u->ch[0] != nullptr && x < cur_val) u = u->ch[0];
else if(u->ch[1] != nullptr && x > cur_val){
res += *(u->cnt);
if(u->ch[0] != nullptr) res += *(u->ch[0]->sz);
u = u->ch[1];
}else break;
}
if(*(u->val) == x){
if(u->ch[0] != nullptr) res += *(u->ch[0]->sz);
return res+1;
}
return res+1;
}
int RBT_rank_k(int k){
if(total_node == 0) return 0;
if(k > total_node) return 0;
if(k <= 0) return 0;
Node *u = rt;
int tmp = k;
while(u != nullptr){
if(u->ch[0] != nullptr && *(u->ch[0]->sz) >= tmp) u = u->ch[0];
else{
int pp = *(u->cnt);
if(u->ch[0] != nullptr) pp += *(u->ch[0]->sz);
if(pp >= tmp) return *(u->val);
else{
if(u->ch[1] != nullptr){
tmp -= pp;
u = u->ch[1];
}else break;
}
}
}
return 0;
}
Node* RBT_pre(Node* v){
if(v->ch[0] == nullptr){
Node* u = v;
while(u->fa != nullptr && *(u->side) == 0) u = u->fa;
if(u->fa == nullptr) return nullptr;
return u->fa;
}else{
Node* u = v->ch[0];
while(u->ch[1] != nullptr) u = u->ch[1];
return u;
}
}
Node* RBT_nxt(Node* v){
if(v->ch[1] == nullptr){
Node* u = v;
while(u->fa != nullptr && *(u->side) == 1) u = u->fa;
if(u->fa == nullptr) return nullptr;
return u->fa;
}else{
Node* u = v->ch[1];
while(u->ch[0] != nullptr) u = u->ch[0];
return u;
}
}
void maintain_sz(Node* u){
if(u == nullptr) return;
*(u->sz) = *(u->cnt);
if(u->ch[0] != nullptr) *(u->sz) += *(u->ch[0]->sz);
if(u->ch[1] != nullptr) *(u->sz) += *(u->ch[1]->sz);
}
void rotate(Node* u){
if(u == rt) return;
Node *p = u->fa;
Node *s = u->fa->ch[*(u->side)^1];
Node *c = u->ch[*(u->side)^1];
Node *d = u->ch[*(u->side)];
int u_sd = *(u->side), p_sd = *(p->side);
if(c != nullptr) *(c->side) = *(c->side)^1;
*(u->side) = *(p->side), *(p->side) = u_sd^1;
Node *g = p->fa;
if(g == nullptr) rt = u;
if(c != nullptr) c->fa = p;
p->ch[u_sd] = c, u->ch[u_sd^1] = p;
p->fa = u, u->fa = g;
if(g != nullptr) g->ch[p_sd] = u;
maintain_sz(p), maintain_sz(u);
}
bool BothSonRed(Node* u){
if(u->ch[0] == nullptr || u->ch[1] == nullptr) return 0;
if(*(u->ch[0]->color)==1 && *(u->ch[1]->color)==1) return 1;
return 0;
}
bool NoSon(Node* u){
if(u->ch[0] == nullptr && u->ch[1] == nullptr) return 1;
return 0;
}
void pushup(Node *u){
if(u == nullptr) return;
maintain_sz(u);
pushup(u->fa);
}
void RBT_insert(int x){
if(total_node == 0){
++total_node;
rt = new Node;
rt->val = new int(x);
rt->color = new int(0);
rt->side = new int(0);
rt->cnt = new int(1);
rt->sz = new int(1);
return;
}
Node *u = rt;
while(u != nullptr){
if(*(u->val) == x){
++total_node;
*(u->cnt) = *(u->cnt)+1;
*(u->sz) = *(u->sz)+1;
pushup(u);
return;
}
if(BothSonRed(u)){
*(u->color) = 1;
*(u->ch[0]->color) = 0;
*(u->ch[1]->color) = 0;
if(u->fa != nullptr && *(u->fa->color)==1){
Node *p = u->fa;
Node *g = p->fa;
Node *s = g->ch[*(p->side)^1];
if(*(p->side) == *(u->side)){
*(p->color) = 0;
*(g->color) = 1;
rotate(p);
}else{
*(u->color) = 0;
*(g->color) = 1;
rotate(u);
rotate(u);
}
}else if(u->fa == nullptr){
*(u->color) = 0;
}
}else{
int cur_val = *(u->val);
if(u->ch[0] != nullptr && x < cur_val){
u = u->ch[0];
}else if(u->ch[1] != nullptr && x > cur_val){
u = u->ch[1];
}else{
++total_node;
Node *cur = new Node;
cur->val = new int(x);
cur->color = new int(1);
cur->cnt = new int(1);
cur->sz = new int(1);
cur->side = new int(0);
cur->fa = u;
if(x > *(u->val)){
*(cur->side) = 1;
u->ch[1] = cur;
}else{
u->ch[0] = cur;
}
if(*(u->color) == 1){
if(*(cur->side) == *(u->side)){
*(u->color) = 0;
*(u->fa->color) = 1;
rotate(u);
}else{
*(u->fa->color) = 1;
*(cur->color) = 0;
rotate(cur);
rotate(cur);
}
}
pushup(cur);
return;
}
}
}
}
void RBT_swap(Node *x, Node *y){
int tmp = *(x->val);
*(x->val) = *(y->val);
*(y->val) = tmp;
tmp = *(x->cnt);
*(x->cnt) = *(y->cnt);
*(y->cnt) = tmp;
tmp = *(x->sz);
*(x->sz) = *(y->sz);
*(y->sz) = tmp;
}
void remove_simple(Node *u){
if(u->ch[0] == nullptr && u->ch[1] == nullptr){
int aru = *(u->side);
Node *tmp = u->fa;
u->fa->ch[aru] = nullptr;
if(u != nullptr) delete u;
pushup(tmp);
}else{
int aru = 0;
if(u->ch[1] != nullptr) aru = 1;
int u_sd = *(u->side);
if(u->fa != nullptr) u->fa->ch[u_sd] = u->ch[aru];
if(u->fa == nullptr) rt = u->ch[aru];
u->ch[aru]->fa = u->fa;
*(u->ch[aru]->side) = u_sd;
Node *tmp = u->fa;
if(u != nullptr) delete u;
pushup(tmp);
}
--total_node;
}
bool is_black(Node *u){
if(u == nullptr) return 1;
if(*(u->color) == 0) return 1;
return 0;
}
bool is_red(Node *u){
if(u == nullptr) return 0;
if(*(u->color) == 1) return 1;
return 0;
}
void increase_black_height(Node *u){
if(u == nullptr) return;
if(u == rt) return;
Node *p = u->fa;
Node *s = p->ch[*(u->side)^1];
if(s != nullptr && *(s->color) == 1){
rotate(s);
*(s->color) = 0;
*(p->color) = 1;
}
s = p->ch[*(u->side)^1];
if(s == nullptr){
increase_black_height(u->fa);
return;
}
Node *c = s->ch[*(u->side)^1];
Node *d = s->ch[*(u->side)];
if(is_red(p) && is_red(c) && is_red(d)){
rotate(s);
*(s->color) = 1;
*(p->color) = 0;
}else if(is_red(p) && is_black(c) && is_black(d)){
*(s->color) = 1;
*(p->color) = 0;
}else if(is_black(p) && is_red(c) && is_red(d)){
rotate(s);
*(d->color) = 0;
}else if(is_black(p) && is_black(c) && is_black(d)){
*(s->color) = 1;
increase_black_height(u->fa);
}else{
if(c != nullptr && *(c->color) == 1){
rotate(c);
*(c->color) = 0;
*(s->color) = 1;
increase_black_height(u);
}else{
rotate(s);
*(s->color) = *(p->color);
*(p->color) = 1;
}
}
}
void RBT_remove(int x){
if(x == *(rt->val)){
if(*(rt->cnt) > 1){
--*(rt->cnt);
--*(rt->sz);
--total_node;
return;
}else if(total_node == 1){
--total_node;
if(rt != nullptr) delete rt;
rt = nullptr;
return;
}
}
Node *u = RBT_find(x);
if(*(u->cnt) > 1){
--*(u->cnt);
--*(u->sz);
pushup(u);
return;
}
if(u->ch[0] != nullptr && u->ch[1] != nullptr){
Node *v = RBT_nxt(u);
RBT_swap(u,v);
u = v;
}
if(*(u->cnt) > 1){
--*(u->cnt);
--*(u->sz);
pushup(u);
return;
}
if(*(u->color) == 1){
remove_simple(u);
}else{
if(u->ch[0] != nullptr || u->ch[1] != nullptr){
int aru = 0;
if(u->ch[1] != nullptr) aru = 1;
if(*(u->ch[aru]->color) == 1){
*(u->color) = 1;
*(u->ch[aru]->color) = 0;
remove_simple(u);
return;
}
}
increase_black_height(u);
remove_simple(u);
}
}
int pre_val(int x){
Node* h = RBT_find(x);
if(h == nullptr) return 0;
else{
if(*(h->val) < x) return *(h->val);
else{
Node* tmp = RBT_pre(h);
if(tmp == nullptr) return 0;
else return *(tmp->val);
}
}
}
int nxt_val(int x){
Node* h = RBT_find(x);
if(h == nullptr) return 0;
else{
if(*(h->val) > x) return *(h->val);
else{
Node* tmp = RBT_nxt(h);
if(tmp == nullptr) return 0;
else return *(tmp->val);
}
}
}
void RBT_clear(Node *u){
if(u == nullptr) return;
if(u->ch[0] != nullptr) RBT_clear(u->ch[0]);
if(u->ch[1] != nullptr) RBT_clear(u->ch[1]);
if(u != nullptr) delete u;
}
RedBlackTree(){
rt = nullptr;
total_node = 0;
}
~RedBlackTree(){
RBT_clear(rt);
}
}qxz;
int T,opt,x;
int main(){
// freopen("file.in","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&opt,&x);
if(opt == 1){
qxz.RBT_insert(x);
}
if(opt == 2){
qxz.RBT_remove(x);
}
if(opt == 3){
printf("%d\n",qxz.RBT_rank_x(x));
}
if(opt == 4){
printf("%d\n",qxz.RBT_rank_k(x));
}
if(opt == 5){
printf("%d\n",qxz.pre_val(x));
}
if(opt == 6){
printf("%d\n",qxz.nxt_val(x));
}
}
return 0;
}