替罪羊树不依靠旋转,而是依靠重构不平衡的子树使整棵树达到平衡状态。
变量介绍
1 int n; 2 struct Node { 3 int val,num; //节点的值,数量 4 int siz,hid; //以该节点为根的子树中未被删的节点数和已被删的节点数 5 int son[2],fa; //左(0)右(1)儿子,爸爸 6 char id; //是否被删(0否1是) 7 void res() { //清空,用于重构 8 hid=fa=val=num=siz=son[0]=son[1]=id=0; 9 } 10 } tree[maxn]; 11 int ins,mem[maxn],inm,root; //新节点内存回收池 12 int reb[maxn],ren[maxn],inr; //临时数组,重构用
各种操作
暴力重构
当一颗子树不平衡时,便将其重构,这是替罪羊树的核心思想。判断一颗子树是否平衡的方法有很多,我采用的方法是,若一颗子树的某个儿子子树的大小大于这颗子树的大小的alpha倍,或这颗子树被删除的节点数超过这颗子树总结点数的alpha倍,则认为这颗子树是不平衡的,需要重构。我们也可以知道,当alpha>=1或alpha<=0.5的时候无意义。alpha越大,重构次数越少,但这颗树越不平衡;alpha越小,树越平衡,但重构次数越多。因此,我们折中取alpha=0.75。(由于不想用浮点数,这里改成了整数乘法运算)
1 char need_to_reset(int now) { //以当前节点为根的子树是否需要重构 2 return tree[now].siz*3<tree[tree[now].son[0]].siz*4 3 ||tree[now].siz*3<tree[tree[now].son[1]].siz*4||tree[now].siz<tree[now].hid*3; //若某颗子树大小超过整棵子树的3/4或 4 //被删除的节点超过总结点数的3/4则需重构 5 }
重构有两种方法,我采用的是时空复杂度O(n)的中序遍历法(ps:所谓拍扁重构法是用链表的时间O(n)空间O(logn)的方法,本蒟蒻不会)。将需要的子树中序遍历,得到一个序列,再每次二分,取最中间的那个数建根,左右递归建树。
1 void redfs(int now) { 2 if(tree[now].son[0]) redfs(tree[now].son[0]); 3 if(!tree[now].id)reb[++inr]=tree[now].val,ren[inr]=tree[now].num; //若没被删除则加入临时数组,否则不加入 4 if(tree[now].son[1]) redfs(tree[now].son[1]); 5 mem[++inm]=now; 6 tree[now].res(); //清空 7 } 8 void rebuild(int l,int r,int pla,int f) { 9 if(l>r)return; 10 if(l==r) { 11 tree[pla].val=reb[l]; 12 tree[pla].siz=tree[pla].num=ren[l]; 13 tree[pla].fa=f; 14 return; 15 } 16 int mid=l+r>>1; 17 tree[pla].val=reb[mid]; 18 int s1,s2; 19 tree[pla].siz=tree[pla].num=ren[mid]; 20 tree[pla].fa=f; 21 if(l<mid) { //建左子树 22 s1=tree[pla].son[0]=mem[inm--]; 23 rebuild(l,mid-1,s1,pla); 24 } 25 if(mid<r) { //建右子树 26 s2=tree[pla].son[1]=mem[inm--]; 27 rebuild(mid+1,r,s2,pla); 28 } 29 update(pla); 30 } 31 void reset(int rot) { 32 int f=tree[rot].fa; 33 inr=0; //这个清零应该不会忘 34 redfs(rot); 35 rebuild(1,inr,((root==rot)?root=mem[inm--]:mem[inm--]),f); //注意,若需重构整棵树则要更新root 36 for(;tree[rot].fa;rot=tree[rot].fa)update(tree[rot].fa); 37 }