详细解说数组版伸展树splay及模板

一直不敢写splay,感觉好麻烦,但是今日强迫自己看了bilibili上【算法讲堂】【电子科技大学】【ACM】Splay入门视频,感觉收获很大,比较详细的介绍了好打又好理解的splay,在这里加上注释和自己的理解,结合《史上最详尽的平衡树(splay)讲解与模板》文章的理解,写了如下板子。
此程序是【bzoj3224】普通平衡树的已AC的标程。

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
using namespace std;
#define MAXN 1000006
struct node{
    int data;
}a[MAXN];//a存具体插入的数 
int fa[MAXN],s[MAXN][2],size[MAXN],num[MAXN];
//fa存父节点,s存左右孩子节点编号,size[i]存i为根的子树中的数的个数; 
//num[i]中节点i中数值相同的数的个数。 
int sz,root;
//sz表示树上的节点数,root存当前树根节点 

void clear(int x){
    s[x][0]=s[x][1]=fa[x]=size[x]=num[x]= a[x].data = 0;
}
//看x是父节点的左还是右孩子 
int whichson(int x){return s[fa[x]][1] == x;}//1代表右孩子
//维护x节点上的数据信息 
void maintain(int x){
    size[x] = size[s[x][0]] + size[s[x][1]] + num[x];
} 
//将son设置为fa的w方向的孩子 
void setson(int son,int f,int w){
    if(son != 0) fa[son] = f;
    if(f != 0) s[f][w] = son;  
}
void rotate(int x){  //此旋转有玄机,详细解说可见bilibili中电子科技大学算法课堂。 
    //f是x的父亲,ff是x的爷爷,w是x是父亲f的左或右孩子的方向。 
    int f = fa[x], ff = fa[f] , w = whichson(x);
    //wf是f是ff的左或右孩子的方向 
    int wf = whichson(f);
    int p = s[x][!w];//记录x的与x不同方向孩子节点编号。
    setson(p,f,w);
    setson(x,ff,wf);
    setson(f,x,!w);
    maintain(f);
    maintain(x); 
}
void splay(int x){
    for(;fa[x];rotate(x)){
        //当父亲与x同是其上一层的左孩子或右孩子,即x,fa[x],fa[fa[x]]三点一线时,便先旋父亲。 
        if(fa[fa[x]] && whichson(x) == whichson(fa[x]))
            rotate(fa[x]);
    }
    root = x; 
}
/*插入操作:
1.如果root=0,即树为空的话,做一些特殊的处理后返回即可。
2.按照二叉查找树的方法一直向下找,如果遇到一个结点的关键字等于当前要插入的点的话,
我们就等于把这个结点加了一个权值。因为在二叉搜索树中是不可能出现两个相同的点的。
并且要将当前点和它父亲结点的各项值更新一下,并做splay;如果已经到了最底下了,那么就
可以直接插入。整个树的大小要+1,新结点的左儿子右儿子、父亲还有各项值要一一对应。并且
最后要做一下他父亲的maintain,并做splay。
*/
void insert(int x){
    if(root==0){
        sz++;s[sz][0]=s[sz][1]=fa[sz] = 0;
        root = sz;
        size[sz] = num[sz] = 1;
        a[sz].data = x;
        return;
    }
    int now = root,f = 0;
    while(1){
        if(x == a[now].data){
            num[now] ++;maintain(now);maintain(f);splay(now);break;
        }
        f = now;
        now = s[now][a[now].data < x];
        if(now == 0){//到达插入的位置 
            sz++;
            s[sz][0] = s[sz][1] = 0;
            fa[sz] = f;
            size[sz] = num[sz] = 1;
            s[f][a[f].data < x] = sz;
            a[sz].data = x;
            maintain(f);
            splay(sz);
            break;
        }
    }
}
//查找x的排名 
/*
初始化:ans=0,当前点=root
如果x比当前结点小,即应该向左子树寻找,ans不用改变。
如果x比当前结点大,即应该向右子树寻找,ans需要加上左子树的大小以及根的权值大小。
最后做splay
*/ 
int find(int x){
    int now = root,ans = 0;
    while(1){
        if(x < a[now].data){
            now = s[now][0];
        }
        else{
            ans += s[now][0] ? size[s[now][0]]:0;
            if(x == a[now].data){
                splay(now);
                return ans + 1;
            }
            ans += num[now];
            now = s[now][1];
        }
    }
}
/*
初始化:当前点root
如果当前点有左子树,并且x比左子树的大小小的话,即向左子树寻找;
如果  左子树的数量< x <=左子树数量+该节点上的权值,则该节点便是要找的值。
否则 x-前面的数量,向右子树寻找。 
*/ 
int findx(int x){
    int now = root;
    while(1){
        if(s[now][0] && x <= size[s[now][0]]) now = s[now][0];
        else{
            int tmp = (s[now][0] ? size[s[now][0]] :0) + num[now];
            if(x <= tmp) return a[now].data;
            x-= tmp;
            now = s[now][1]; 
        }
    }
} 
//找根节点的左子树中的最右节点 即根节点的前驱 
int pre(){ 
    int now = s[root][0];
    while(s[now][1]) now = s[now][1];
    return now;
}
//找根节点的右子树中的最左节点 即根节点的后继 
int nxt(){
    int now = s[root][1];
    while(s[now][0]) now = s[now][0];
    return now;
}
void del(int x){
    int whatever = find(x);
    //当根节点中有多余1个以上的数,则删除一个即可,平衡树上节点数不变。 
    if(num[root] > 1) {
        num[root]--;maintain(root);return;
    }
    //当根节点无左右子树时,直接删掉根节点即可 
    if(!s[root][0] && !s[root][1]){ 
        clear(root);
        root = 0; return;
    }
    //如果无左子树则将右子树作为根节点。 
    if(!s[root][0]){
        int oldroot = root;
        root = s[root][1];
        fa[root] = 0;
        clear(oldroot); 
        return;
    }
    //如果无左右子树则将左子树作为根节点。 
    if(!s[root][1]){
        int oldroot = root;
        root = s[root][0];
        fa[root] = 0;
        clear(oldroot); 
        return;
    }
    //如果左右子树均有,则找到根节点的前驱,并将其splay至根节点,然后
    //将oldroot的右子树直接接至旋至根节点的新的节点的右孩子。
    //维护根节点上信息 
    int leftbig = pre(),oldroot = root;
    splay(leftbig);
    s[root][1] = s[oldroot][1];
    fa[s[oldroot][1]] = root;
    clear(oldroot);
    maintain(root);
}
int main(){
    int n,opt,x;
    cin >> n;
    for(int i= 1;i<= n;i++){
        scanf("%d%d",&opt,&x);
        if(opt == 1) insert(x);
        else if(opt == 2) del(x);
        else if(opt == 3) printf("%d\n",find(x));
        else if(opt == 4) printf("%d\n",findx(x));
        else if(opt == 5) {
            insert(x);
            printf("%d\n",a[pre()].data);
            del(x);
        }
        else if(opt == 6){
            insert(x);
            printf("%d\n",a[nxt()].data);
            del(x);
        }        
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值