私は Mocha!!

私はこの世界を深く愛している

Fhq-Treap总结:短小精悍不旋转的神级数据结构

Fhq-Treap

首先我们从二叉搜索树讲起(顺便复习)

我们知道我们访问一个节点所需要的时间复杂度和他的深度有直接的关系,比如说一个N个节点的完全二叉树访问一个节点的最坏复杂度是O(logN),但是如果在一条N个节点的链中访问一个节点的最坏复杂度就是O(N),所以想要速度够快,那么树的结构一定要尽量平衡,保持在logN左右。

为了使一个树平衡,人们找到了很多方式,譬如说splay,他通过不断旋转的方式使得树一直保持在一个比较平衡的状态。

但是总是在不断旋转真的好麻烦啊(雾
那么有没有不需要旋转的平衡树呢??有那就是fhq神犇的fhq-treap


首先我们得知道什么是Treap
Treap=Binary Search Tree+Heap(二叉搜索树+堆)
也就是说他的整体结构满足二叉搜索树的性质,然而他的每一个节点有一个随机取的附加的关键词,他们满足堆的性质。
那么他是依靠什么维护平衡性的呢?对就是靠的附加权值,因为附加权值是随机取的,每一个权值取到的概率都是相同的,那么他的期望高度就是logN也就是说他大概就是平衡的。

然而一般的treap也要通过旋转保持平衡,但是fhq-treap不需要,这得益于他的两个神奇的核心操作MergeSplit,也就是合并和分裂。

fhq的天才思想可以看他的ppt:《范浩强谈数据结构》

Think Funtional!
            ---fhq666

Merge

merge就是把两个treap合成一个,是一个递归的过程。首先明确fhq-treap的平衡条件是他的附加权值,那么只需要比较他们的附加权值大小判断在左树还是右树即可。
可以结合代码理解:

int merge(int x,int y)//默认x<y 
{
    if(!x || !y) return x+y;
    update(x),update(y);
    if(tr[x].rnd<tr[y].rnd)//用rand值来维护堆进而维护平衡性 
    {
        tr[x].son[1]=merge(tr[x].son[1],y);
        update(x);
        return x;
    }
    else
    {
        tr[y].son[0]=merge(x,tr[y].son[0]);
        update(y);
        return y;   
    }
}

Split

split是把一颗treap分开两个树的操作,也是一个递归的过程。有两种分法,一种是按权值分,一种是按size(子树大小)分,具体用哪个要看情况。

其实也是灰常好理解的,

按权值分(注意这时候权值小于等于 k的节点都在左树中,大于 k的都在右树中【划重点】):

void split(int now,int k,int &x,int &y)//以权值k分离now树成x,y 
{
    if(!now) x=y=0;
    else
    {
        if(tr[now].v<=k) //把所有小于k的权值的节点分到一棵树中
            x=now,split(tr[now].son[1],k,tr[now].son[1],y);
        else
            y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
        update(now);
    }
}

按size分(这个分法比较像splay中找第k大的树):

void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        update(now);
         if (k<=tr[tr[now].son[0]].size)
            y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
        else
            x=now,split(tr[now].son[1],k-tr[tr[now].son[0]].size-1,tr[now].son[1],y);
        update(now);
    }
}

基本的两个操作介绍完了,那么接下来就要用这两个操作完成一系列操作。


new_node

新建权值为v节点操作,这个和splay一样(我代码里面吧node写成了code懒得改了)

int new_code(int v)
{
    tot++;
    tr[tot].size=1; tr[tot].v=v; tr[tot].rnd=rand();
    return tot;
}

Insert

插入一个权值为v节点,那么只需要以v为权值split整棵树,然后new_node(v)在merge回去

split(root,a,x,y);
root=merge(merge(x,new_code(a)),y);

Del

删除权值为v的点,先把整颗树以v为权值split成两棵树a,b,再把a树按照v-1分成c,d。这时候值为v的点一定为d的根,那么我们把d的两个子儿子merge起来(划重点:这一步就是去除掉v的影响),再把他们重新merge起来得到一个新的树,这颗树就去除掉了v的影响。

split(root,a,x,z);
split(x,a-1,x,y);
y=merge(tr[y].son[0],tr[y].son[1]);
root=merge(merge(x,y),z);

FindRank

找值为v的节点排名,就把整棵树以v-1为权值split,那么小于v的节点全在左树内,输出左树的size即可


FindNumberK

找排名为k的数,和splay一样,如果k还比左子树的size小就取左子树找
,否则k减去左子树的size和当前的1个取右子树找现在的第k个,找到有(左子树size)=k即可

int kth(int now,int k)
{
    while(1)
    {
        if(k<=tr[tr[now].son[0]].size) 
            now=tr[now].son[0];
        else
        {
            if(k==tr[tr[now].son[0]].size+1) return now;
            else 
            {
                k-=tr[tr[now].son[0]].size+1;
                now=tr[now].son[1];
            }
        }
    }
}

precursor

找前驱就把整棵树按v-1为权值split开,此时小于等于v的数全在左树里,在左树里找到排名为(左树的size)的节点权值即可。

split(root,a-1,x,y);
printf("%d\n",tr[kth(x,tr[x].size)].v);
root=merge(x,y);

successor

找后继是相同的,把整棵树按v为权值split开,此时右树排名第一的数就是后继

split(root,a,x,y);
printf("%d\n",tr[kth(y,1)].v); 
oot=merge(x,y);

l到r区间操作

先把root的以r个(注意这里是以size分了)split出来,现在左树中是1~r的树,再在左树中以l-1个split开,那么这时候l到r的数就在第二次split后的右树中,直接对一棵树操作即可。这个区间操作和splay的把区间旋转到一个子树有异曲同工之妙。

注意我在下面文艺平衡树的代码里面区间是l+1到r+1的,为什么呢?因为我学习dalao设了两个哨兵节点(也就是第一个和第n+2个节点起边界作用)


结构体模版bzoj3224普通平衡树:

//fhq-treap
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std;
const int N=100010;
struct trnode
{
    int son[2],v,rnd,size;
}tr[N]; int tot=0,root=0;
void update(int x)
{
    tr[x].size=tr[tr[x].son[0]].size+tr[tr[x].son[1]].size+1;   
}
int new_code(int v)
{
    tot++;
    tr[tot].size=1; tr[tot].v=v; tr[tot].rnd=rand();
    return tot;
}
int merge(int x,int y)//默认x<y 
{
    if(!x || !y) return x+y;
    if(tr[x].rnd<tr[y].rnd)//用rand值来维护堆进而维护平衡性 
    {
        tr[x].son[1]=merge(tr[x].son[1],y);
        update(x);
        return x;
    }
    else
    {
        tr[y].son[0]=merge(x,tr[y].son[0]);
        update(y);
        return y;   
    }
}
void split(int now,int k,int &x,int &y)//以权值k分离now树成x,y 
{
    if(!now) x=y=0;
    else
    {
        if(tr[now].v<=k) //把所有小于k的权值的节点分到一棵树中
            x=now,split(tr[now].son[1],k,tr[now].son[1],y);
        else
            y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
        update(now);
    }
}
int kth(int now,int k)
{
    while(1)
    {
        if(k<=tr[tr[now].son[0]].size) 
            now=tr[now].son[0];
        else
        {
            if(k==tr[tr[now].son[0]].size+1) return now;
            else 
            {
                k-=tr[tr[now].son[0]].size+1;
                now=tr[now].son[1];
            }
        }
    }
}
int main()
{
    srand((unsigned)time(NULL));
    int T; int flag,x,y,z,a,b;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&flag,&a);
        if(flag==1)
        {
            split(root,a,x,y);
            root=merge(merge(x,new_code(a)),y);
        }
        else if(flag==2)
        {
            split(root,a,x,z);
            split(x,a-1,x,y);
            y=merge(tr[y].son[0],tr[y].son[1]);
            root=merge(merge(x,y),z);
        }
        else if(flag==3)
        {
            split(root,a-1,x,y);
            printf("%d\n",tr[x].size+1);
            root=merge(x,y);
        }
        else if(flag==4)
            printf("%d\n",tr[kth(root,a)].v);
        else if(flag==5)
        {
            split(root,a-1,x,y);
            printf("%d\n",tr[kth(x,tr[x].size)].v);
            root=merge(x,y);
        }
        else if(flag==6)
        {
            split(root,a,x,y);
            printf("%d\n",tr[kth(y,1)].v); 
            root=merge(x,y);
        }
    }
    return 0;
}

bzoj3223文艺平衡树:

//fhq-treap
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std;
const int N=100010;
struct trnode
{
    int son[2],v,rnd,size;
    int fz;
}tr[N]; int tot=0,root=0;
int n;
void update(int x)
{
    tr[x].size=tr[tr[x].son[0]].size+tr[tr[x].son[1]].size+1;   
    if(x&&tr[x].fz)
    {
        tr[x].fz^=1;
        swap(tr[x].son[0],tr[x].son[1]);
        tr[tr[x].son[0]].fz^=1,tr[tr[x].son[1]].fz^=1;
    }
}
int new_code(int v)
{
    tot++;
    tr[tot].size=1; tr[tot].v=v; tr[tot].rnd=rand(); tr[tot].fz=0;
    return tot;
}
int build(int l,int r)
{
    if(l>r) return 0;
    int mid=(l+r)/2,v=mid-1;
    int now=new_code(v);
    tr[now].son[0]=build(l,mid-1);
    tr[now].son[1]=build(mid+1,r);
    update(now);
    return now;
}
int merge(int x,int y)//默认x<y 
{
    if(!x || !y) return x+y;
    update(x),update(y);
    if(tr[x].rnd<tr[y].rnd)//用rand值来维护堆进而维护平衡性 
    {
        tr[x].son[1]=merge(tr[x].son[1],y);
        update(x);
        return x;
    }
    else
    {
        tr[y].son[0]=merge(x,tr[y].son[0]);
        update(y);
        return y;   
    }
}
void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        update(now);
         if (k<=tr[tr[now].son[0]].size)
            y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
        else
            x=now,split(tr[now].son[1],k-tr[tr[now].son[0]].size-1,tr[now].son[1],y);
        update(now);
    }
}
void rev(int l,int r)
{
    int a,b,c,d;
    split(root,r+1,a,b);
    split(a,l,c,d);
    tr[d].fz^=1;
    root=merge(merge(c,d),b);
}
int pri[N],len=0;
void print(int x)
{
    if (!x) return;
    update(x);
    print(tr[x].son[0]);
    if (tr[x].v>=1 && tr[x].v<=n) printf("%d ",tr[x].v);
    print(tr[x].son[1]);
}
int main()
{
    srand((unsigned)time(NULL));
    int m;scanf("%d%d",&n,&m);
    root=build(1,n+2);
    for(int i=1;i<=m;i++)
    {
        int l,r;scanf("%d%d",&l,&r);
        rev(l,r);
    }
    print(root);
    return 0;
}
阅读更多
版权声明:本文为博主原创文章,转载请附上原博客链接。 https://blog.csdn.net/CABI_ZGX/article/details/79963427
个人分类: 算法论文 treap
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

Fhq-Treap总结:短小精悍不旋转的神级数据结构

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭