替罪羊树学习笔记

· · 算法·理论

这里效果更佳

替罪羊树是一种简单粗暴的平衡树,它在一颗子树不平衡的时候会把整棵子树拍扁重构。

简单说一下替罪羊树是怎么插入删除的:

然后我们要说说每个节点要维护哪些信息了:

struct node{
    int v,lc,rc,c,s1,s2,s3;
    node():v(0),lc(0),rc(0),c(0),s1(0),s2(0),s3(0){}
}tree[maxn];
inline void pushup(const int&x){
    tree[x].s1=tree[tree[x].lc].s1+tree[tree[x].rc].s1+1,
    tree[x].s2=tree[tree[x].lc].s2+tree[tree[x].rc].s2+(tree[x].c!=0),
    tree[x].s3=tree[tree[x].lc].s3+tree[tree[x].rc].s3+tree[x].c;
}

现在我们再说说如何维护平衡: 替罪羊树是根据子树大小来判断是否平衡的,首先,我们需要先设定一个平衡值 \alpha0.5<\alpha<1),我喜欢将其设定为 \frac{3}{4}。当一个节点的两个儿子的 s1 的最大值比这个节点的 s1

如果发现不平衡,就按照中序遍历将其拍扁然后重构,注意,在拍扁的过程中如果发现一个节点已经被删除了就别再放到拍扁的数列里了。 ```cpp void dfs(const int&x){ if(!tree[x].s2)return ; dfs(tree[x].lc); if(tree[x].c)ldr[ldr_cnt++]=x; dfs(tree[x].rc); } void build(int&x,int l,int r){ if(l==r){x=0;return;} int mid=l+r>>1;x=ldr[mid]; build(tree[x].lc,l,mid); build(tree[x].rc,mid+1,r); pushup(x); } inline void maintain(int&x){ pushup(x); if(tree[x].s1*3<=max(tree[tree[x].lc].s1,tree[tree[x].rc].s1)*4||tree[x].s2*4<=tree[x].s1*3){ ldr_cnt=0; dfs(x); build(x,0,ldr_cnt); } } ``` `ins` 和 `del` 上面都说了怎么做了,而查询操作和普通 bst 类似,就不说了,直接放代码: ```cpp #include<stdio.h> #include<ctype.h> #define getc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) char buf[1<<21],*p1=buf,*p2=buf; inline void read(int&x){ char c=getc(),f=0;x=0; for(;!isdigit(c);c=getc())f^=!(c^45); for(;isdigit(c);c=getc())x=(x<<1)+(x<<3)+(c^48); if(f)x=-x; } const int maxn=1e5+1; struct node{ int v,lc,rc,c,s1,s2,s3; node():v(0),lc(0),rc(0),c(0),s1(0),s2(0),s3(0){} }tree[maxn]; int nodecnt,ldr[maxn],ldr_cnt; inline int max(int a,int b){return a>b?a:b;} inline void pushup(const int&x){ tree[x].s1=tree[tree[x].lc].s1+tree[tree[x].rc].s1+1, tree[x].s2=tree[tree[x].lc].s2+tree[tree[x].rc].s2+(tree[x].c!=0), tree[x].s3=tree[tree[x].lc].s3+tree[tree[x].rc].s3+tree[x].c; } void dfs(const int&x){ if(!tree[x].s2)return ; dfs(tree[x].lc); if(tree[x].c)ldr[ldr_cnt++]=x; dfs(tree[x].rc); } void build(int&x,int l,int r){ if(l==r){x=0;return;} int mid=l+r>>1;x=ldr[mid]; build(tree[x].lc,l,mid); build(tree[x].rc,mid+1,r); pushup(x); } inline void maintain(int&x){ pushup(x); if(tree[x].s1*3<=max(tree[tree[x].lc].s1,tree[tree[x].rc].s1)*4||tree[x].s2*4<=tree[x].s1*3){ ldr_cnt=0; dfs(x); build(x,0,ldr_cnt); } } void ins(int&x,const int&k){ if(!tree[x].s2)x=++nodecnt,tree[x].v=k,tree[x].c=1; else if(k<tree[x].v)ins(tree[x].lc,k); else if(k>tree[x].v)ins(tree[x].rc,k); else ++tree[x].c; maintain(x); } void del(int&x,const int&k){ if(!tree[x].s2) return; if(k<tree[x].v)del(tree[x].lc,k); else if(k>tree[x].v)del(tree[x].rc,k); else --tree[x].c; maintain(x); } int q_rnk(const int&x,const int&v){ if(!tree[x].s2)return 1; if(v>tree[x].v) return tree[tree[x].lc].s3+tree[x].c+q_rnk(tree[x].rc,v); return q_rnk(tree[x].lc,v); } int q_val(const int&x,const int&r){ if(r<=tree[tree[x].lc].s3)return q_val(tree[x].lc,r); if(r>tree[tree[x].lc].s3+tree[x].c)return q_val(tree[x].rc,r-tree[tree[x].lc].s3-tree[x].c); return tree[x].v; } int main(){ int rt=0,m,op,x; read(m); for(;m--;){ read(op),read(x); switch(op){ case 1:ins(rt,x);break; case 2:del(rt,x);break; case 3:printf("%d\n",q_rnk(rt,x));break; case 4:printf("%d\n",q_val(rt,x));break; case 5:printf("%d\n",q_val(rt,q_rnk(rt,x)-1));break; default:printf("%d\n",q_val(rt,q_rnk(rt,x+1))); } } return 0; } ``` 其实替罪羊树应该还可以分裂合并,做法类似 rbt、AVL、BB[α]。 还有,很显然,替罪羊树的高度是 $O(\log n)$,所以查询的复杂度明显是 $O(\log n)$,但是插入删除时由于维护平衡需要拍扁重构整棵子树,所以复杂度是**均摊** 的 $\log n$。