总结-Treap

总结-Treap

最近做了好些Treap的题,旋转和非旋转的都学了,写点总结,简单记点什么.

Treap=Tree+Heap,又叫做树堆.
是一棵拥有键值和优先级的树.
对于键值而言,Treap是一棵排序二叉树;对于优先级而言,Treap是堆.

Treap的优先级随机确定,所以时间复杂度是随机的,但是插入,删除和查找的期望时间复杂度为 O(logn) .
对于Treap的实现有两种:旋转和非旋转的.

旋转Treap

因为Treap既要满足键值排序二叉树结构,又要满足优先级的堆结构.
旋转的Treap是通过旋转来维护性质的,

如图所示,以右旋为例
若x优先级比u高,当前子树不满足堆性质,要将x旋上去.
将x旋到u处成为当前子树根,将u旋下去成为x的右儿子,那么x原来的左儿子b就空出来了,将b作为u的左儿子,旋转完毕.

rotate(同样以上图右旋为例):

void rotate(int& u,int d) {//d 0 左旋,1 右旋
    int v=ch[u][d^1];//先记录下当前u的对左儿子x
    ch[u][d^1]=ch[v][d];//u左儿子变成x右儿子
    ch[v][d]=u;//x右儿子变成u
    update(u);//更新u的子树结点和
    update(u=v);//更新x的子树结点和,并将x变成根
}
代码:
数组版
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;

const int maxn=100000+10;

#define lc ch[u][0]
#define rc ch[u][1]

int ch[maxn][2],key[maxn],sz[maxn],rnd[maxn],cnt[maxn],id,root;

void update(int u)
{
    sz[u]=sz[lc]+sz[rc]+cnt[u];
}

void rotate(int& u,int d)//旋转,d 0,left;1,right. 
{
    int v=ch[u][d^1];
    ch[u][d^1]=ch[v][d];
    ch[v][d]=u;
    update(u);
    update(u=v);
}

void insert(int& u,int val)//插入,注意维护结点的子树个数和 
{
    if(!u) {
        u=++id;
        lc=rc=0;
        key[u]=val;
        rnd[u]=rand();
        sz[u]=cnt[u]=1;
        return ;
    }
    if(key[u]==val) {++sz[u],++cnt[u];return ;}
    int d=key[u]<val;
    insert(ch[u][d],val);++sz[u];
    if(rnd[ch[u][d]]<rnd[u]) rotate(u,d^1);
}

bool remove(int& u,int val)//删除,注意维护结点的子树个数和 
{
    if(!u) return false;
    if(key[u]==val) {
        if(cnt[u]>1) return --sz[u],--cnt[u],true;//不止一个,直接删 
        if(!lc||!rc) return u=lc?lc:rc,true;//仅有一棵子树与之相连,选择将其填u 
        if(rnd[lc]>rnd[rc]) rotate(u,0);//若有两棵子树,先旋转处理优先级,再删除 
        else rotate(u,1);
        return remove(u,val);
    }
    int d=key[u]<val;
    if(!remove(ch[u][d],val)) return false;
    return --sz[u],true;
}

int find_rnk(int val)//根据key求rank,注意先计算rnk,再更新u 
{
    int u=root,rnk=1;
    while(u) {
        if(val<key[u]) u=lc;//val小于根key值,走lc 
        else if(val>key[u]) rnk+=sz[lc]+cnt[u],u=rc;//val大于根key值,走rc 
        else return rnk+sz[lc];//val等于根key值,直接返回 
    }
    return 0;
}

int find_key(int rnk)//根据rank求key 
{
    int u=root;
    while(u) {
        if(rnk<=sz[lc]) u=lc;
        else if(rnk>sz[lc]+cnt[u]) rnk-=sz[lc]+cnt[u],u=rc;
        else return key[u];
    }
    return 0;
}

int find_pre(int val)//前驱 
{
    int u=root,pre;
    while(u) {
        if(key[u]<val) pre=key[u],u=rc;
        else u=lc;
    }
    return pre;
}

int find_nxt(int val)//后继 
{
    int u=root,nxt;
    while(u) {
        if(key[u]>val) nxt=key[u],u=lc;
        else u=rc;
    }
    return nxt;
}

int main()
{
    int n,op,x,cnt=0;
    scanf("%d",&n);
    while(n--) {
        ++cnt;
        scanf("%d%d",&op,&x);
        switch(op) {
            case 1:insert(root,x);break;
            case 2:remove(root,x);break;
            case 3:printf("%d\n",find_rnk(x));break;
            case 4:printf("%d\n",find_key(x));break;
            case 5:printf("%d\n",find_pre(x));break;
            case 6:printf("%d\n",find_nxt(x));break;
        }
    }
    return 0;
}
指针版

这里用了一个null来避免访问空指针

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int maxn=100000+10;

#define lc (u->ch[0])
#define rc (u->ch[1])

struct Node {
    Node* ch[2];
    int key,rnd,sz,cnt;
    Node(){ch[0]=ch[1]=NULL;sz=cnt=0;}
    Node(int key,int rnd):key(key),rnd(rnd){}
    void update() {
        sz=ch[0]->sz+ch[1]->sz+cnt;
    }
}*root,*null;//引入null来避免访问非法地址 

void rotate(Node* &u,int d)//旋转,d 0,left;1,right. 
{
    Node* v=u->ch[d^1];
    u->ch[d^1]=v->ch[d];
    v->ch[d]=u;
    u->update();
    (u=v)->update();
}

void insert(Node* &u,int val)
{
    if(!u||u==null) {//root不要和null赋成一样的,避免null中的sz不为0 
        u=new Node(val,rand());
        u->sz=u->cnt=1;
        lc=rc=null;
        return ;
    }
    if(val==(u->key)) {++(u->sz),++(u->cnt);return ;}
    int d=(u->key)<val;
    insert(u->ch[d],val);++(u->sz);
    if((u->rnd)>(u->ch[d]->rnd))
        rotate(u,d^1);
}

bool remove(Node* &u,int val)
{
    if(u==null) return false;
    if((u->key)==val) {
        Node* t=u;
        if(u->cnt>1) return --(u->sz),--(u->cnt),true;
        if(lc==null||rc==null) return u=(lc!=null?lc:rc),delete t,true;
        if((lc->rnd)>(rc->rnd)) rotate(u,0);
        else rotate(u,1);
        return remove(u,val);
    }
    int d=(u->key)<val;
    if(!remove(u->ch[d],val)) return false;
    return --(u->sz),true;
}

int find_rnk(int val)
{
    int rnk=1;
    Node* u=root;
    while(u!=null) {
        if(val<(u->key)) u=lc;
        else if(val>(u->key)) rnk+=(lc->sz)+(u->cnt),u=rc;
        else return rnk+(lc->sz);
    }
    return rnk;
}

int find_key(int rnk)
{
    Node* u=root;
    while(u!=null) {
        if(rnk<=(lc->sz)) u=lc;
        else if(rnk>(lc->sz)+(u->cnt)) rnk-=(lc->sz)+(u->cnt),u=rc;
        else return u->key;
    }
    return 0;
}

int find_pre(int val)
{
    int pre;
    Node* u=root;
    while(u!=null) {
        if(val>(u->key)) pre=(u->key),u=rc;
        else u=lc;
    }
    return pre;
}

int find_nxt(int val)
{
    int nxt;
    Node* u=root;
    while(u!=null) {
        if(val<(u->key)) nxt=(u->key),u=lc;
        else u=rc;
    }
    return nxt;
}

int main()
{
    null=new Node();//null赋一个地址,不然也会RE 
    null->ch[0]=null->ch[1]=null;//将null的左右儿子也赋成null 
    int n,op,x,cnt=0;
    scanf("%d",&n);
    while(n--) {
        ++cnt;
        scanf("%d%d",&op,&x);
        switch(op) {
            case 1:insert(root,x);break;
            case 2:remove(root,x);break;
            case 3:printf("%d\n",find_rnk(x));break;
            case 4:printf("%d\n",find_key(x));break;
            case 5:printf("%d\n",find_pre(x));break;
            case 6:printf("%d\n",find_nxt(x));break;
        }
    }
    return 0;
}

非旋转Treap

在网上看到了一篇讲的很清楚的文章 非旋转Treap及可持久化[Merge,Split]

非旋转的Treap采用了merge(合并)和split(拆分)两个基本操作来实现其他操作.
以insert为例,对于当前值val,先在原树中以val值拆分成两棵树,再将该点插到其中一棵树上,再合并.(insert=split+merge+merge)
再如remove,对于当前值val,现在原树中以val值拆分成两棵树,再次拆分,将分出来的两棵子树合并.(remove=split+split+merge)

代码(感谢涨老师提供的板子,@DraZxlNDdt)
#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int INF=0x7fffffff;
const int maxn=100000+10;

struct Treap {
    int key,sz;
    Treap *lc,*rc;
    Treap(){}
    Treap(int key,Treap* f):key(key),sz(1),lc(f),rc(f){}
    void update() {
        sz=1+(lc?lc->sz:0)+(rc?rc->sz:0);
    }
}*root;

typedef pair<Treap*,Treap*> Doot;//便于返回两个根 

int size(Treap* u) {return u?u->sz:0;}

Treap* merge(Treap* A,Treap* B) {//A树中最大值小于B树中最小值 
    if(!A||!B) return A?A:B;
    if((A->sz)*RAND_MAX<(A->sz+B->sz)*rand()) {//B做根 
        B->lc=merge(A,B->lc);
        B->update();
        return B;
    } else {//A做根 
        A->rc=merge(A->rc,B);
        A->update();
        return A;
    }
}

Doot split_key(Treap* X,int val) {//根据键值key拆分 
    if(!X) return Doot(NULL,NULL);
    Doot Y;
    if(val<=X->key) {//拆分左子树 
        Y=split_key(X->lc,val);
        X->lc=Y.second;
        X->update();
        Y.second=X;
    } else {//拆分右子树 
        Y=split_key(X->rc,val);
        X->rc=Y.first;
        X->update();
        Y.first=X;
    }
    return Y;
}

Doot split_rnk(Treap* X,int rnk) {//根据名次rank 拆分 
    if(!X) return Doot(NULL,NULL);
    Doot Y;
    if(rnk<=size(X->lc)) {//拆分左子树 
        Y=split_rnk(X->lc,rnk);
        X->lc=Y.second;
        X->update();
        Y.second=X;
    } else {//拆分右子树 
        Y=split_rnk(X->rc,rnk-=size(X->lc)+1);
        X->rc=Y.first;
        X->update();
        Y.first=X;
    }
    return Y;
}

void insert(int val) {//插入=拆分+新节点+合并 
    Doot Y=split_key(root,val);
    root=merge(Y.first,merge(new Treap(val,NULL),Y.second));
}

void remove(int val) {//删除=拆分+拆分+合并 
    Doot Y=split_key(root,val);
    root=merge(Y.first,split_rnk(Y.second,1).second);
}

int find_rnk(Treap* u,int val) {//根据key找rank 
    if(!u) return 0;
    if(u->key>val) return find_rnk(u->lc,val);
    if(u->key==val) {
        int rnk=find_rnk(u->lc,val);
        return rnk?rnk:size(u->lc)+1;
    }
    int rnk=find_rnk(u->rc,val);
    return rnk+(rnk?size(u->lc)+1:0);
}

int find_key(Treap* u,int rnk) {//根据rank找key 
    if(size(u->lc)>=rnk) return find_key(u->lc,rnk);
    if(size(u->lc)+1==rnk) return u->key;
    return find_key(u->rc,rnk-=size(u->lc)+1);
}

int find_pre(Treap* u,int val) {//根据key找前驱 
    if(!u) return INF;
    if(u->key>=val) return find_pre(u->lc,val);
    int pre=find_pre(u->rc,val);
    return pre==INF?u->key:pre;
}

int find_nxt(Treap* u,int val) {//根据key找后继 
    if(!u) return INF;
    if(u->key<=val) return find_nxt(u->rc,val);
    int nxt=find_nxt(u->lc,val);
    return nxt==INF?u->key:nxt;
}

int main()
{
    int n,op,x;
    scanf("%d",&n);
    while(n--) {
        scanf("%d%d",&op,&x);
        switch(op) {
            case 1:insert(x);break;
            case 2:remove(x);break;
            case 3:printf("%d\n",find_rnk(root,x));break;
            case 4:printf("%d\n",find_key(root,x));break;
            case 5:printf("%d\n",find_pre(root,x));break;
            case 6:printf("%d\n",find_nxt(root,x));break;
        }
    }
    return 0;
}

一些题目:

1.BZOJ 3224 题目 题解
2.BZOJ 1588 题目 题解
3.BZOJ 1208 题目 题解
4.BZOJ 2733 题目 题解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值