平衡树

splay

其实离听splay也不过一个月的时间,但今天复习的时候真的是忘的一干二净。复习其实是预习后来总结一下(明白要经常复习的重要性)。

参考https://baijiahao.baidu.com/s?id=1613228134219334653&wfr=spider&for=pc

splay维护平衡的方法是不停的将一个点旋转到根来维护平衡,核心操作就是旋转。

旋转时分为两种形式:1.三点一线就先旋转父亲,再旋转x。2.不是三点一线就一直旋转x 。

代码很巧妙(程序语音的魅力),先贴出来结合着分析。

void rotate(int x){//旋转 
     int old=f[x],oldf=f[old],which=get(x);//old是x爸爸,oldf是爷爷,which是连向父亲边的方向 
     ch[old][which]=ch[x][which^1];f[ch[old][which]]=old;//右旋:x的右儿子变为old的左儿子;左旋:x的左儿子变为old的右儿子 
     f[old]=x;ch[x][which^1]=old;
     f[x]=oldf;
     if (oldf)
          ch[oldf][ch[oldf][1]==old]=x;
     update(old);update(x);
}
旋转

注意连边的方向,手动模拟一下是最好的。

一直旋转到根再加一个这个就可以了。

void splay(int x)
{
     for (int fa;(fa=f[x]);rotate(x))
          if (f[fa])
               rotate((get(x)==get(fa)?fa:x));//三点一线就先旋转父亲,再旋转x;不是三点一线就一直旋转x 
     root=x;//把x一直旋转到根 
}
旋转到根

以上就是splay的核心操作。


还有一些基础操作,很好理解,所以就直接贴了

插入

void insert(int v){//插入 
     if (root==0) {sz++;ch[sz][0]=ch[sz][1]=f[sz]=0;key[sz]=v;cnt[sz]=1;size[sz]=1;root=sz;return;}
     int now=root,fa=0;
     while (1){
          if (key[now]==v){
               cnt[now]++;update(now);update(fa);splay(now);break;//记得要splay一下 
          }
          fa=now;
          now=ch[now][key[now]<v];
          if (now==0){//找到叶子节点 
               sz++;
               ch[sz][0]=ch[sz][1]=0;key[sz]=v;size[sz]=1;
               cnt[sz]=1;f[sz]=fa;ch[fa][key[fa]<v]=sz;
               update(fa);
               splay(sz);//记得要splay一下 
               break;
          }
     }
}
插入

查询x的排名

int find(int v){
     int ans=0,now=root;
     while (1){
          if (v<key[now])
               now=ch[now][0];
          else{
               ans+=(ch[now][0]?size[ch[now][0]]:0);
               if (v==key[now]) {splay(now);return ans+1;}
               ans+=cnt[now];
               now=ch[now][1];
          }
     }
}
查询x的排名

找到排名为x的点

int findx(int x){
    int now=root;
    while (1){
        if (ch[now][0]&&x<=size[ch[now][0]])
            now=ch[now][0];
        else{
                int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];
                if (x<=temp)
                   return key[now];
                x-=temp;now=ch[now][1];
            }
    }
}
找到排名为x的点

前驱

int pre(){
     int now=ch[root][0];//先往左走一步,前驱是左子树的最大 
     while (ch[now][1]) now=ch[now][1];
     return now;
}
前驱

后继

int next(){//先往右走一步,后继是右子树的最小 
     int now=ch[root][1];
     while (ch[now][0]) now=ch[now][0];
     return now;
}
后继

删除

删除要稍微复杂一点,但同样的,把这个点旋转到根,然后有三种情况:

1.改点有重复个数,那么就把个数-1就行了。

2.有一个儿子,那么如果有左儿子就让左儿子做根,有右儿子就让右儿子做根。

3.有两个儿子,就让前驱(即左儿子)做根,然后直接将右儿子变成原来左儿子(现在的根)的右儿子,再清理掉老根就好了。

void del(int x){//删除 
     find(x);//find(x)后x位于根节点 
     if (cnt[root]>1) {cnt[root]--;update(root);return;}//有重复的个数
     if (!ch[root][0]&&!ch[root][1]) {clear(root);root=0;return;} //一个儿子 
     if (!ch[root][0]){int oldroot=root;root=ch[root][1];f[root]=0;clear(oldroot);return;}
     else if (!ch[root][1]){int oldroot=root;root=ch[root][0];f[root]=0;clear(oldroot);return;}
     int leftbig=pre(),oldroot=root;//2个儿子 
     splay(leftbig);//让前驱做根 
     f[ch[oldroot][1]]=root;
     ch[root][1]=ch[oldroot][1];
     clear(oldroot);
     update(root);
     return;
}
删除

Treap就下次再整理~

原文地址:https://www.cnblogs.com/yyys-/p/11153916.html