码迷,mamicode.com
首页 > 其他好文 > 详细

Splay详解(二)

时间:2017-11-26 11:06:55      阅读:168      评论:0      收藏:0      [点我收藏+]

标签:har   ace   ref   二叉查找树   org   connect   定义   简单   roo   

前言

上一节中,我们讲述了Splay的核心操作rotate与splay
本节我会教大家如何用这两个函数实现各种强大的功能
为了方便讲解,我们拿这道题做例题来慢慢分析

利用splay实现各种功能

首先,我们需要定义一些东西

各种指针

    struct node
    {
        int v;//权值
        int fa;//父亲节点
        int ch[2];//0代表左儿子,1代表右儿子
        int rec;//这个权值的节点出现的次数
        int sum;//子节点的数量
    };
    int pointnum,tot;//pointnum代表算上重复的有多少节点,tot表示不算重复的有多少节点

rotate

splay

这两个函数就不讲了,前面已经讲的挺详细了

插入

根据前面讲的,我们在插入一个数之后,需要将其旋转到根
所以insert函数可以这么写

inline void insert(int v)
{
    int p=build(v);//p代表插到了哪里
    splay(p,root);
}

那么build函数怎么写呢?
当这棵树已经没有节点的时候,我们直接新建一个节点就好

inline int newpoint(int v,int fa)//v:权值;fa:它的爸爸是谁
{
    tree[++tot].fa=fa;
    tree[tot].v=v;
    tree[tot].sum=tree[tot].rec=1;
    return tot;
}

当这可树有节点的时候,我们根据二叉查找树的性质,不断向下走,直到找到一个可以插入的点,注意在走的时候需要更新一个每个节点的sum值

int build(int v)
{
    pointnum++;
    if(tot==0){root=1;newpoint(v,0);}
    else
    {
        int now=root;
        while(1)
        {
            tree[now].sum++;
            if(tree[now].v==v){tree[now].rec++;return now;}//出现过
            int nxt=v<tree[now].v?0:1;
            if(!tree[now].ch[nxt])
            {
                newpoint(v,now);
                tree[now].ch[nxt]=tot;
                return tot;
            }
            now=tree[now].ch[nxt];
        }
    }
    return 0;
}

删除

删除的功能是:删除权值为v的节点
我们不难想到:我们可以先找到他的位置,再把这个节点删掉

int find(int v)
{
    int now=root;
    while(1)
    {
        if(tree[now].v==v)   {splay(now,root);return now;}
        int nxt=v<tree[now].v?0:1;
        if(!tree[now].ch[nxt])return 0;
        now=tree[now].ch[nxt];
    }
}

这个函数可以找到权值为v的节点的位置,比较好理解,注意别忘记把找到的节点splay到根
另外我们还需要一个彻底删除的函数

inline void dele(int x)
{
    tree[x].sum=tree[x].v=tree[x].rec=tree[x].fa=tree[x].ch[0]=tree[x].ch[1]=0;
    if(x==tot)  tot--;
}

接下来的任务就是怎么样才能保证删除节点后整棵树还满足二叉查找树的性质
注意:我们在查找完一个节点的时候已经将他旋转到根了,所以他左边一定都比他小,除此之外没有比他小的节点了(否则还要考虑他父亲比他小的情况)

那么此时会出现几种情况

  • 权值为v的节点已经出现过
    这时候直接把他的rec和sum加上1就好
  • 本节点没有左儿子
    直接把他的右儿子设置成根
  • 既有左儿子,又有右儿子
    在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子

最后把这个节点删掉就好

void pop(int v)
{
    int deal=find(v);
    if(!deal)   return ;
    pointnum--;
    if(tree[deal].rec>1){tree[deal].rec--;tree[deal].sum--;return ;}
    if(!tree[deal].ch[0])    root=tree[deal].ch[1],tree[root].fa=0;
    else
    {
        int le=tree[deal].ch[0];
        while(tree[le].ch[1])    le=tree[le].ch[1];
        splay(le,tree[deal].ch[0]);
        int ri=tree[deal].ch[1];
        connect(ri,le,1);connect(le,0,1);
        update(le);
    }
    dele(deal);
}

查询x数的排名

这个简单,如果我们找到了权值为x的节点,那么答案就是他的左子树的大小+1
否则的话根据二叉查找树的性质不断的向下走就可以,注意如果这次是向右走的话答案需要加上它左子树的大小和这个节点的rec值

int rank(int v)// 查询值为v的数的排名 
{
    int ans=0,now=root;
    while(1)
    {
        if(tree[now].v==v)    return ans+tree[tree[now].ch[0]].sum+1;
        if(now==0)  return 0;
        if(v<tree[now].v)    now=tree[now].ch[0];
        else                 ans+=tree[tree[now].ch[0]].sum+tree[now].rec,now=tree[now].ch[1];
    }
    if(now)    splay(now,root);
    return 0;
}

查询排名为x的数

这个操作就是上面那个操作的逆向操作
用used变量记录该节点以及它的左子树有多少节点
如果x>左子树的数量且< used,那么当前节点的权值就是答案
否则根据二叉查找树的性质继续向下走
同样注意在向右走的时候要更新x

int arank(int x)//查询排名为x的数是什么 
{
    int now=root;
    while(1)
    {
        int used=tree[now].sum-tree[tree[now].ch[1]].sum;
        if(x>tree[tree[now].ch[0]].sum&&x<=used)    break;
        if(x<used)    now=tree[now].ch[0];
        else    x=x-used,now=tree[now].ch[1];
    }
    splay(now,root);
    return tree[now].v;
}

求x的前驱

这个更容易,我们可以维护一个ans变量,然后对整棵树进行遍历,同时更新ans

int lower(int v)// 小于v的最大值 
{
    int now=root;
    int ans=-maxn;
    while(now)
    {
        if(tree[now].v<v&&tree[now].v>ans)    ans=tree[now].v;
        if(v>tree[now].v)    now=tree[now].ch[1];
        else    now=tree[now].ch[0];
    }
    return ans;
}

求x的后继

这个和上一个一样,就不细讲了

int upper(int v)
{
    int now=root;
    int ans=maxn;
    while(now)
    {
        if(tree[now].v>v&&tree[now].v<ans)    ans=tree[now].v;
        if(v<tree[now].v)    now=tree[now].ch[0];
        else    now=tree[now].ch[1];
    }
    return ans;
}

完整代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=1e6+10;
const int maxn=0x7fffff;
inline char nc()
{
    static char buf[MAXN],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
    char c=nc();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
    return x*f;
}
struct SPLAY
{
    #define root tree[0].ch[1]
    struct node
    {
        int v,fa,ch[2],rec,sum;
    };
    node tree[MAXN];
    int pointnum,tot;
    SPLAY(){pointnum=tot=0;}
    int iden(int x){return tree[tree[x].fa].ch[0]==x?0:1;}
    inline void connect(int x,int fa,int how){tree[x].fa=fa;tree[fa].ch[how]=x;}
    inline void update(int x){tree[x].sum=tree[tree[x].ch[0]].sum+tree[tree[x].ch[1]].sum+tree[x].rec;}
    inline void rotate(int x)
    {
        int y=tree[x].fa;
        int R=tree[y].fa;
        int Rson=iden(y);
        int yson=iden(x);
        int b=tree[x].ch[yson^1];
        connect(b,y,yson);
        connect(y,x,yson^1);
        connect(x,R,Rson);
        update(y);update(x);
    }
    void splay(int pos,int to)// 把编号为pos的节点旋转到编号为to的节点 
    {
        to=tree[to].fa;
        while(tree[pos].fa!=to)
        {
            if(tree[tree[pos].fa].fa==to)    rotate(pos);
            else if(iden(tree[pos].fa)==iden(pos))    rotate(tree[pos].fa),rotate(pos);
            else    rotate(pos),rotate(pos);
        }
    }
    inline int newpoint(int v,int fa)//
    {
        tree[++tot].fa=fa;
        tree[tot].v=v;
        tree[tot].sum=tree[tot].rec=1;
        return tot;
    }
    inline void dele(int x)
    {
        tree[x].ch[0]=tree[x].ch[1]=0;
        if(x==tot)  tot--;
    }
    int find(int v)
    {
        int now=root;
        while(1)
        {
            if(tree[now].v==v)   {splay(now,root);return now;}
            int nxt=v<tree[now].v?0:1;
            if(!tree[now].ch[nxt])return 0;
            now=tree[now].ch[nxt];
        }
    }
    int build(int v)
    {
        pointnum++;
        if(tot==0){root=1;newpoint(v,0);}
        else
        {
            int now=root;
            while(1)
            {
                tree[now].sum++;
                if(tree[now].v==v){tree[now].rec++;return now;}//出现过
                int nxt=v<tree[now].v?0:1;
                if(!tree[now].ch[nxt])
                {
                    newpoint(v,now);
                    tree[now].ch[nxt]=tot;
                    return tot;
                }
                now=tree[now].ch[nxt];
            }
        }
        return 0;
    }
    inline void insert(int v)
    {
        int p=build(v);//p代表插到了哪里
        splay(p,root);
    }
    void pop(int v)
    {
        int deal=find(v);
        if(!deal)   return ;
        pointnum--;
        if(tree[deal].rec>1){tree[deal].rec--;tree[deal].sum--;return ;}
        if(!tree[deal].ch[0])    root=tree[deal].ch[1],tree[root].fa=0;
        else
        {
            int le=tree[deal].ch[0];
            while(tree[le].ch[1])    le=tree[le].ch[1];
            splay(le,tree[deal].ch[0]);
            int ri=tree[deal].ch[1];
            connect(ri,le,1);connect(le,0,1);
            update(le);
        }
        dele(deal);
    }
    int rank(int v)// 查询值为v的数的排名 
    {
        int ans=0,now=root;
        while(1)
        {
            if(tree[now].v==v)    return ans+tree[tree[now].ch[0]].sum+1;
            if(now==0)  return 0;
            if(v<tree[now].v)    now=tree[now].ch[0];
            else                 ans+=tree[tree[now].ch[0]].sum+tree[now].rec,now=tree[now].ch[1];
        }
        if(now)    splay(now,root);
        return 0;
    }
    int arank(int x)//查询排名为x的数是什么 
    {
        int now=root;
        while(1)
        {
            int used=tree[now].sum-tree[tree[now].ch[1]].sum;
            if(x>tree[tree[now].ch[0]].sum&&x<=used)    break;
            if(x<used)    now=tree[now].ch[0];
            else    x=x-used,now=tree[now].ch[1];
        }
        splay(now,root);
        return tree[now].v;
    }
    int lower(int v)// 小于v的最大值 
    {
        int now=root;
        int ans=-maxn;
        while(now)
        {
            if(tree[now].v<v&&tree[now].v>ans)    ans=tree[now].v;
            if(v>tree[now].v)    now=tree[now].ch[1];
            else    now=tree[now].ch[0];
        }
        return ans;
    }
    int upper(int v)
    {
        int now=root;
        int ans=maxn;
        while(now)
        {
            if(tree[now].v>v&&tree[now].v<ans)    ans=tree[now].v;
            if(v<tree[now].v)    now=tree[now].ch[0];
            else    now=tree[now].ch[1];
        }
        return ans;
    }
}s;
int main()
{
    #ifdef WIN32
    freopen("a.in","r",stdin);
    #else
    #endif
    int n=read();
    while(n--)
    {
        int opt=read(),x=read();
        if(opt==1)    s.insert(x);
        else if(opt==2)    s.pop(x);
        else if(opt==3)    printf("%d\n",s.rank(x));
        else if(opt==4)    printf("%d\n",s.arank(x));
        else if(opt==5)    printf("%d\n",s.lower(x));
        else if(opt==6)    printf("%d\n",s.upper(x));
    }
} 

至此,splay最常用的几种函数就解决了,
下面来看几道裸题

例题

不知道为什么,我的splay跑的特别快,可能是脸太好了吧??

洛谷P2234 [HNOI2002]营业额统计

http://www.cnblogs.com/zwfymqz/p/7896128.html

洛谷P2286 [HNOI2004]宠物收养场

http://www.cnblogs.com/zwfymqz/p/7895794.html

Splay详解(二)

标签:har   ace   ref   二叉查找树   org   connect   定义   简单   roo   

原文地址:http://www.cnblogs.com/zwfymqz/p/7898210.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!