学习笔记——替罪羊树

替罪羊树就是平衡树的一种,所以他一定就满足二叉搜索树的性质

专属于他的性质由一下几个:

  1.他不像splay treap一样会转,它会在每次判断左右子树是否平衡即,左右子树下有效点的数目是否差太多而退化成一条链(有效点在删除操作时解释),所以他会在判断后把不平衡的子树(或包含这颗子树的最大子树)在中序遍历后重新构造。而平衡系数(即左右子树中有效节点数目最大的占总节点的比例)由你自己定。一般为0.75(1代表无论怎么比例不合理都可以,0代表无论怎么合理都不行,0.5就是左右子树下有效节点一样多)。一般splay为0.7,treap为0.8

  2.他的删除不是直接删去,他会打标记来显示这个点有没有删去。没打删去标记的就是有效节点。在删去过多使树的平衡失调时,就要重构(或者是删去整棵树的一半的节点,根据个人喜好来判断)

  3.他的插入是正常的,但你要记得去看插入后是否会破坏平衡的性质而去决定是否重构

下面,以各大oj都可以看见的普通平衡树为例的代码(这个代码在洛谷上测试最后一个点会T了,但是,如果你不在结构体中存左右儿子的下标就不会T,但我懒得改了,所以请大家自己动手丰衣足食

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <iostream>
  4 #include <algorithm>
  5 #define alpha 0.8
  6 #define maxn 200001
  7 using namespace std;
  8 struct scapegoat{
  9     int son[2],val,valid,total;
 10     //valid子树中有效的点个数,total子树中总点数 
 11     bool exist;//是否被删 
 12 }e[maxn];
 13 int memory[maxn];/*内存池,用了多少*/
 14 int cur[maxn];//拍扁时要用的内存空间
 15 int root,pool,poi,cnt,to_rebuild;
 16 //pool指向memory[i]的指针,poi是拍扁是指向cur[i]的指针
 17 inline bool isbad(int now){//看是否要重建 
 18     if((double)e[now].valid*alpha<=(double)max(e[e[now].son[0]].valid,e[e[now].son[1]].valid))
 19     //如果根上点的总个数与左右子树中子树中点数最大的大小关系符合我们的要求 
 20         return true;//就不重建 
 21     else return false;
 22 }
 23 void dfs(int now){
 24     //对子树进行中序遍历,由图可知,中序遍历是数组 
 25     if(!now) return;dfs(e[now].son[0]);
 26     if(e[now].exist) cur[++poi]=now;
 27     //如果这个点没有被删除,则他会在拍扁是存在
 28     else memory[++pool]=now;
 29     dfs(e[now].son[1]);
 30 }
 31 void build(int l,int r,int &now){//注意,now会随着改变 
 32     int mid=l+r>>1; now=cur[mid]; 
 33     //你在重新建树时,你已经找好序列了,就在cur[i]中
 34     //所以你此时只要使序列中相互关系改变。
 35     //注意,你cur[i]中放的是下标,你要把中间元素的编号取出来 
 36     if(l==r){
 37         e[now].son[0] = e[now].son[1] = 0;
 38         e[now].total = e[now].valid = 1;
 39         return; //如果建到叶节点,就把一切清零 
 40     }
 41     if(l<mid) build(l,mid-1,e[now].son[0]);//你mid已经建好了
 42     //由于mid有下取整,所以当r=l+1时,mid就是l,但这样不应该递归 
 43     else e[now].son[0]=0;//当上一行情况出现时,这个点左子树就是0
 44     build(mid+1,r,e[now].son[1]); 
 45     e[now].total=e[e[now].son[0]].total+e[e[now].son[1]].total+1; 
 46     e[now].valid=e[e[now].son[0]].valid+e[e[now].son[1]].valid+1;
 47 }
 48 inline void rebuild(int &now){//注意,now是取址。重新建树 
 49     poi=0;dfs(now);
 50     //子树大小从头开始算起,边建树边中序遍历
 51     if(poi) build(1,poi,now);
 52     else now=0;
 53     //如果之前已经建好了一部分,就要顺着建好的继续建树
 54     //否则,就要从头开始建树 
 55 }
 56 inline int find_rank(int k) {
 57     int now=root,ans=1;
 58     while(now){
 59         if(e[now].val>=k) now=e[now].son[0];
 60         //如果这个点的值大于等于k,则我们要去左子树找
 61         else{
 62             ans+=e[e[now].son[0]].valid+e[now].exist;
 63             //跳过左子树去右子树时,要把ans+=左子树的大小(如果这个点存在的话,也加上这个点
 64             now=e[now].son[1];
 65         }
 66     }
 67     return ans;
 68 }
 69 inline int find_kth(int k){//找排名为k的数 
 70     int now=root;//从头开始找
 71     while(now) {
 72         if(e[now].exist&&e[e[now].son[0]].valid+1==k) return e[now].val;
 73         else if(e[e[now].son[0]].valid>=k) now=e[now].son[0];
 74         //向左走 ,保证k大于左子树的valid
 75         else{
 76             k-=e[e[now].son[0]].valid+e[now].exist;
 77             now=e[now].son[1];
 78             //在这个点徘徊时,k会先减去左子树的值,因为k一定比左子树的valid大
 79             //这时,now就是右子树的开端了,代表要去右子树找
 80         }
 81     }
 82 }
 83 void insert(int &now,int val){
 84     if(!now){
 85         now=memory[pool--];e[now].val=val;
 86         //这时,当你找到一个新的节点并插入时,这个节点的排序上一层就是memory[i--]
 87         e[now].exist=e[now].total=e[now].valid=1;
 88         e[now].son[0]=e[now].son[1]=0;
 89         return;//这个新点存在,没有孩子
 90     }
 91     e[now].total++,e[now].valid++;//向下更新 
 92     if(e[now].val>=val) insert(e[now].son[0],val); 
 93     else insert(e[now].son[1],val);
 94     if(!isbad(now)){
 95         if(to_rebuild){//如果不是在刚拍扁就性质差
 96             if(e[now].son[0]==to_rebuild) rebuild(e[now].son[0]);
 97             else rebuild(e[now].son[1]);to_rebuild=0;
 98             //如果在插入后,树的性质不好了,就开始把性质不好的子树拍扁重构
 99         }
100     }
101     else to_rebuild=now;
102     //这次侥幸逃过,但会被标记,如果下次性质不好,那就一定是这个点的锅 
103 }
104 inline void delete_pos(int &now, int tar){//删去规定位置的值
105     if(e[now].exist&&e[e[now].son[0]].valid+1==tar){//如果找到了,且之前没删过
106         e[now].exist=0;e[now].valid--;return;
107     }
108     e[now].valid--;
109     if(e[e[now].son[0]].valid+e[now].exist>=tar) delete_pos(e[now].son[0],tar);
110     //如果左子树的还存在的个数加上这个点(如果有)大于目标在的地方,就去左子树找
111     else delete_pos(e[now].son[1],tar-e[e[now].son[0]].valid-e[now].exist);
112     //否则就去右子树找,并且排名要减去左子树以及这个点的数目(如果这个点存在)和 
113 }
114 inline void delete_val(int tar){
115     delete_pos(root,find_rank(tar));
116     if((double)e[root].total*alpha>e[root].valid) rebuild(root);
117 }
118 int main(){
119     int opt,x,m;
120     for(register int i=2000000;i>=1;i--) memory[++pool]=i;
121     //初始化替罪羊树的内存池,每个内存池的的每一层就是i
122     scanf("%d",&m);
123     while(m--){
124         scanf("%d%d",&opt,&x);
125         if(opt==1) insert(root, x);
126         if(opt==2) delete_val(x);
127         if(opt==3) printf("%d\n",find_rank(x));
128         if(opt==4) printf("%d\n",find_kth(x));
129         if(opt==5) printf("%d\n",find_kth(find_rank(x)-1));
130         if(opt==6) printf("%d\n",find_kth(find_rank(x+1)));
131     }
132 }

猜你喜欢

转载自www.cnblogs.com/fallen-down/p/10806220.html