替罪羊树不依靠旋转,而是依靠重构不平衡的子树使整棵树达到平衡状态。

变量介绍

 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;                                                        //临时数组,重构用 
var

各种操作

暴力重构

当一颗子树不平衡时,便将其重构,这是替罪羊树的核心思想。判断一颗子树是否平衡的方法有很多,我采用的方法是,若一颗子树的某个儿子子树的大小大于这颗子树的大小的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 }
need_to_reset

重构有两种方法,我采用的是时空复杂度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 }
reset

相关文章: