替罪羊树

Posted Halifuda

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了替罪羊树相关的知识,希望对你有一定的参考价值。

技术分享图片
#include<cstdio>
#include<ctime>
#define mid(l,r) ((l+r)/2)
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define nil 0
#define alpha 0.75f
#define MXN 100000+3
int val[MXN],size[MXN],fa[MXN],left[MXN],right[MXN],recycle[MXN];
int root,ntop,rtop=-1,count,lst[MXN],cnt;
int newnode(int k){
    int nw;
    if(rtop!=-1) nw=recycle[rtop--];
    else nw=++ntop;
    val[nw]=k;
    size[nw]=1;
    fa[nw]=nil;
    left[nw]=nil;
    right[nw]=nil;
    return nw;
}
bool check(int now){
    if(size[left[now]]<=alpha*size[now]&&size[right[now]]<=alpha*size[now]) return false;
    return true;
}
void travel(int now){
    if(now==nil) return;
    travel(left[now]);
    lst[cnt++]=now;
    travel(right[now]);
    return;
}
int build(int l,int r){
    if(l>=r) return nil;
    int nw=lst[mid(l,r)];
    left[nw]=build(l,mid(l,r));
    right[nw]=build(mid(l,r)+1,r);
    fa[left[nw]]=nw;
    fa[right[nw]]=nw;
    size[nw]=size[left[nw]]+size[right[nw]]+1;
    return nw;
}
void insert(int &now,int ins){
    if(root==nil) root=ins;
    else if(now==nil) now=ins;
    else{
        size[now]++;
        if(val[now]>=val[ins]){
            if(left[now]==nil){
                left[now]=ins;
                fa[ins]=now;
            }
            else insert(left[now],ins);
        }
        else{ 
            if(right[now]==nil){
                right[now]=ins;
                fa[ins]=now;
            }
            else insert(right[now],ins);
        }
    }
    return;
}
void insert(int k){
    int ins=newnode(k);
    insert(root,ins);
    int t=0,f,flag;
    for(int i=ins;fa[i]!=nil;i=fa[i]){
        if(check(i)) t=i;
    }
    if(t){ 
        cnt=0;
        f=fa[t];
        if(left[f]==t) flag=0;
        else flag=1;
        travel(t);
        int a=build(0,cnt);
        if(t==root) root=a;
        else{
            fa[a]=f;
            if(flag) right[f]=a;
            else left[f]=a;
        }
    }
    return;
}
void remove(int now,int k){
    if(now==nil) return;
    if(val[now]==k){
        if(left[now]==nil&&right[now]==nil){
            if(left[fa[now]]==now) left[fa[now]]=nil;
            else right[fa[now]]=nil;
            fa[now]=nil;
            if(now==root) root=nil;
            recycle[++rtop]=now;
            return;
        }
        else if(left[now]==nil){
            if(left[fa[now]]==now) left[fa[now]]=right[now];
            else right[fa[now]]=right[now];
            fa[right[now]]=fa[now];
            fa[now]=nil;
            if(now==root) root=right[now];
            recycle[++rtop]=now;
            return;
        }
        else if(right[now]==nil){
            if(left[fa[now]]==now) left[fa[now]]=left[now];
            else right[fa[now]]=left[now];
            fa[left[now]]=fa[now];
            fa[now]=nil;
            if(now==root) root=left[now];
            recycle[++rtop]=now;
            return;
        }
        else{
            int t=left[now];
            while(right[t]!=nil) t=right[t];
            val[now]=val[t];
            size[now]--;
            remove(left[now],val[t]);
            return;
        }
    }
    else{
        size[now]--;
        if(val[now]>k) remove(left[now],k);
        else remove(right[now],k);
    }
    return;
}
int search(int now,int k){
    if(now==nil) return nil;
    if(val[now]==k) return now;
    if(val[now]>k) return search(left[now],k);
    if(val[now]<k) return search(right[now],k);
}
int select(int now,int k){
    if(now==nil) return nil;
    if(size[left[now]]+1==k) return now;
    if(size[left[now]]+1>=k) return select(left[now],k);
    if(size[left[now]]+1<k) return select(right[now],k-size[left[now]]-1);
}
int rank(int now,int k){
    if(now==nil) return 1;
    if(val[now]>=k) return rank(left[now],k);
    if(val[now]<k) return rank(right[now],k)+size[left[now]]+1; 
}
int get_prev(int now,int k){
    if(now==nil) return -0x6fffffff;
    if(val[now]<k) return max(val[now],get_prev(right[now],k));
    else return get_prev(left[now],k);
}
int get_succ(int now,int k){
    if(now==nil) return 0x6fffffff;
    if(val[now]>k) return min(val[now],get_succ(left[now],k));
    else return get_succ(right[now],k);
}
int main(){
    int n;
    scanf("%d",&n);
    root=nil;
    int p,x,a=1;
    while(n--){
        scanf("%d%d",&p,&x);
        if(p==1) insert(x);
        if(p==2) remove(root,x);
        if(p==3) printf("%d\n",rank(root,x));
        if(p==4) printf("%d\n",val[select(root,x)]);
        if(p==5) printf("%d\n",get_prev(root,x));
        if(p==6) printf("%d\n",get_succ(root,x));
    }
    return 0;
}
ScapegoatTree

替罪羊树是平衡树中的一股泥石流。平衡树的常规操作——旋转在这里毫无用武之地,取而代之的是拍扁重构。

首先我们可以想象一个简单的问题:最优的二叉搜索树的形状一定是一棵结构颇似线段树的树,它的高度是logn

然后我们可以想一想写线段树时是怎么写的,是递归平分区间。

然后我们还可以通过二叉搜索树最普通的性质:中序遍历是一个有序的数列而得知这条性质的一种应用:对一个有序数列进行分治建树,可以获得一棵二叉搜索树。

以上三点是替罪羊树维护平衡的基本思路:当发现某一棵子树不平衡时,利用中序遍历获得一个有序数列,再利用类似于线段树的写法,将这棵树重新建为一棵完美的二叉树。显然,拍扁的复杂度为O(n),重构的复杂度为O(logn),总体复杂度为O(n)

我们可能会问,这样不是和退化为链的复杂度一样吗?替罪羊树利用以下判断平衡的条件解决这项问题:

size(left(x))<=α*size(x)且size(right(x))<=α*size(x),0.5<=α<=1时,子树平衡。显然当α=0.5时,这是一棵严格的左右子树大小相等的树。然而替罪羊树一般使α0.7附近,称为宽松平衡,来减少重构操作,且使每次重构的规模不会过大。另一项操作是,重构时选择最大的不平衡子树来重构,以使整棵树尽量平衡,减少后续的重构次数。此时替罪羊树的均摊复杂度约等于O(logn)。可以证明,然而本人蒟蒻丝毫看不懂...

替罪羊树有两种可行的删除:一种是像正常二叉平衡树一样,一种是给这个节点打上标记,代表它被删除了,进行运算时完全不考虑它,等它所在子树重构时再真正删除。特别的,当被标记节点数超过一半时直接重构整棵树。

我用上一篇写的SBT和这个替罪羊树对比,替罪羊树与SBT时间耗费大体相当。另外,上一篇的删除操作有误...此处已改正。

我还是比较喜欢平衡树这种结构的(^_^)ノ现在学习了三种平衡树,感觉替罪羊树像是一个人维护一根铁杆,铁杆弯曲了的话强行掰直;SBT像是在杠杆上左右摇晃,不断寻找平衡点;splay像是一个人单手托着装满豆子的盘子,发现不平衡,晃一晃就又平衡了...不过还有奇葩的2-3-4树,看图解感觉在有丝分裂...

替罪羊树思路很简单,学起来就比较容易,我自认代码比较清晰,因此没有过多解释。

PS:

一般情况下我写平衡树只有插入维护平衡(当然splay的话查找之类的也要),删除是不维护的。由于删除操作不会使树的高度变深,因此若只对一棵树进行删除和查找,明显复杂度不会变高。不过不清楚这样做是否会影响真实的复杂度。

以上是关于替罪羊树的主要内容,如果未能解决你的问题,请参考以下文章

替罪羊树模版 普通平衡树

[数据结构]替罪羊树简介

平衡树合集(Treap,Splay,替罪羊,FHQ Treap)

[BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树

几个平衡树板子

浅谈替罪羊树