Treap树堆的基本模板

代码

treap模拟集合的功能

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef int pointer;//采用数组代替指针
const int maxn=10001*4;//结点个数 
struct Treap{//树堆 
    struct Node{
        pointer ch[2];//两个表示儿子的编号 
        int r,v;//r表示键值,v表示权值 
        int cmp(int x)const{//比较函数 
            if(x==v)return -1;//相同返回-1 
            return x<v?0:1;
            //小于当前结点,返回0 表示这个值只可能在它的左子 
            //大于当前结点,返回1 表示这个值只可能在它的右子 
        }
    }nodes[maxn];
    int ncnt;pointer root;
    Treap(){
        root=ncnt=0;//初始化
    }
    void rotate(pointer& o,int d){//对结点o进行 左旋/右旋 
        //d表示旋转方向
        //d=0表示"左旋",d=1表示右旋
        pointer k=nodes[o].ch[d^1];
        nodes[o].ch[d^1]=nodes[k].ch[d];
        nodes[k].ch[d]=o;o=k;
        /*
        pointer k=nodes[o].ch[d^1];
        swap(nodes[o],nodes[k]);
        nodes[k].ch[d^1]=nodes[o].ch[d];
        nodes[o].ch[d]=k;
        */
    }
    void insert(pointer& o,int x){//在以编号o为根的树中插入数值x 
        if(o==0){//如果当前结点不存在 
            o=++ncnt;//申请新的结点 
            nodes[o].ch[0]=nodes[o].ch[1]=0;
            nodes[o].v=x;nodes[o].r=rand();//随机权值 
        }else{
            int d=nodes[o].cmp(x);//判断在左子还是右子 
            if(d==-1){
                return;//与当前结点权值相同,插入失败 
            }
            insert(nodes[o].ch[d],x);//在对应的结点中插入x 
            if(nodes[nodes[o].ch[d]].r > nodes[o].r)
                rotate(o,d^1);//如果根节点的权值较小,旋转 
        }
    }
    void insert(int x){//外接端口 
        insert(root,x);
    }
    void remove(pointer& o,int x){//在以编号o为根的树中删除数值x 
        if(o==0){//找到了叶子结点,也没有找到x值 
            return;//删除失败 
        }
        int d=nodes[o].cmp(x);
        if(d==-1){//如果x与当前根节点相等 
            if(nodes[o].ch[0]==0)//o的左子为空 
                o=nodes[o].ch[1];//用右子替代o 
            else if(nodes[o].ch[1]==0)//o的右子为空 
                o=nodes[o].ch[0];//用左子替代o 
            else{//两个子都为空 
                int d2=(nodes[nodes[o].ch[0]].r > nodes[nodes[o].ch[1]].r?1:0);
                //如果左子权值大,右旋;如果右子权值大,左旋; 
                rotate(o,d2);remove(nodes[o].ch[d2],x);
                //然后递归删除对应的子结点
            }
        }else
            remove(nodes[o].ch[d],x);//在对应的子结点中删除
    }
    void remove(int x){//外接端口 
        remove(root,x);
    }
    bool find(pointer o,int x){
        while(o!=0){
            int d=nodes[o].cmp(x);//判断x值在左子还是右子 
            if(d==-1)return 1;//返回找到了 
            else o=nodes[o].ch[d];//否则,在对应子树继续查找 
        }
        return 0;//没找到 
    }
    bool find(int x){//外接端口 
        return find(root,x);
    }
}trp;
int main(){
    trp.insert(1);
    trp.insert(2);
    trp.insert(3);
    printf("%d %d %d\n",trp.find(1),trp.find(2),trp.find(3));
    trp.remove(1);
    trp.remove(2);
    trp.remove(3);
    printf("%d %d %d\n",trp.find(1),trp.find(2),trp.find(3));
    return 0;
}

treap实现名次树

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef int pointer;//采用数组代替指针
const int maxn=10001*4;//结点个数 
struct Treap{//树堆(名次树)
    struct Node{
        pointer ch[2];//两个表示儿子的编号 
        int r,v,s;//r表示键值,v表示权值
        //s表示以当前点为根节点的子树的节点个数
        int cmp(int x)const{//比较函数 
            if(x==v)return -1;//相同返回-1 
            return x<v?0:1;
            //小于当前结点,返回0 表示这个值只可能在它的左子 
            //大于当前结点,返回1 表示这个值只可能在它的右子 
        }
    }nodes[maxn];
    void maintain(pointer o){//维护结点size的正确性 
        nodes[o].s=nodes[nodes[o].ch[0]].s+nodes[nodes[o].ch[1]].s+1;
    }
    //根结点size=左子size+右子size+1
    //特别的:NULL(即0号结点)的size=0
    int ncnt;pointer root;
    Treap(){
        root=ncnt=0;//初始化
    }
    void rotate(pointer& o,int d){//对结点o进行 左旋/右旋 
        //d表示旋转方向
        //d=0表示"左旋",d=1表示右旋
        pointer k=nodes[o].ch[d^1];
        nodes[o].ch[d^1]=nodes[k].ch[d];
        nodes[k].ch[d]=o;
        maintain(o);maintain(k);
        o=k;
        /*
        pointer k=nodes[o].ch[d^1];
        swap(nodes[o],nodes[k]);
        nodes[k].ch[d^1]=nodes[o].ch[d];
        nodes[o].ch[d]=k;
        */
    }
    void insert(pointer& o,int x){//在以编号o为根的树中插入数值x 
        if(o==0){//如果当前结点不存在 
            o=++ncnt;//申请新的结点 
            nodes[o].ch[0]=nodes[o].ch[1]=0;
            nodes[o].v=x;nodes[o].r=rand();//随机权值 
        }else{
            int d=nodes[o].cmp(x);//判断在左子还是右子 
            if(d==-1){
                return;//与当前结点权值相同,插入失败 
            }
            insert(nodes[o].ch[d],x);//在对应的结点中插入x 
            if(nodes[nodes[o].ch[d]].r > nodes[o].r)
                rotate(o,d^1);//如果根节点的权值较小,旋转 
        }
        maintain(o);
    }
    void insert(int x){//外接端口 
        insert(root,x);
    }
    void remove(pointer& o,int x){//在以编号o为根的树中删除数值x 
        if(o==0){//找到了叶子结点,也没有找到x值 
            return;//删除失败 
        }
        int d=nodes[o].cmp(x);
        if(d==-1){//如果x与当前根节点相等 
            if(nodes[o].ch[0]==0)//o的左子为空 
                o=nodes[o].ch[1];//用右子替代o 
            else if(nodes[o].ch[1]==0)//o的右子为空 
                o=nodes[o].ch[0];//用左子替代o 
            else{//两个子都为空 
                int d2=(nodes[nodes[o].ch[0]].r > nodes[nodes[o].ch[1]].r?1:0);
                //如果左子权值大,右旋;如果右子权值大,左旋; 
                rotate(o,d2);remove(nodes[o].ch[d2],x);
                //然后递归删除对应的子结点
            }
        }else
            remove(nodes[o].ch[d],x);//在对应的子结点中删除
        if(o!=0)maintain(o);//应要注意o!=0 
    }
    void remove(int x){//外接端口 
        remove(root,x);
    }
    bool find(pointer o,int x){
        while(o!=0){
            int d=nodes[o].cmp(x);//判断x值在左子还是右子 
            if(d==-1)return 1;//返回找到了 
            else o=nodes[o].ch[d];//否则,在对应子树继续查找 
        }
        return 0;//没找到 
    }
    bool find(int x){//外接端口 
        return find(root,x);
    }
    int kth(pointer o,int k){//查询第k大元素 
        if(o==0 || k<=0 || k> nodes[o].s)return 0;//查询失败
        int s=(nodes[o].ch[1]==0 ?0:nodes[nodes[o].ch[1]].s);//计算右子大小 
        if(k==s+1)return nodes[o].v;//恰为当前结点 
        else if(k<=s)return kth(nodes[o].ch[1],k);//在左子递归寻找 
        else return kth(nodes[o].ch[0],k-s-1);//在右子递归寻找 
    }
    int kth(int k){//外接端口 
        return kth(root,k);
    }
}trp;
int main(){
    trp.insert(1);
    trp.insert(2);
    trp.insert(3);
    printf("%d %d %d\n",trp.find(1),trp.find(2),trp.find(3));
    printf("%d %d %d\n",trp.kth(3),trp.kth(2),trp.kth(1));
    return 0;
}

未完待续

代码仍在调试阶段,讲解暂无,未完待续…

2017.8.12 goseqh 评论 erverlasting 写的隔膜中,人物瞬移的问题:It’s not a bug, it’s a feature!

承接上文

[2017.12.23]在四个月后的今天,我终于第一次写出了我自己的treap,好开心,再此对拍程序。

treap代码 treap.cpp

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=1000000+10;
namespace Treap{
    int ch[maxn][2],k[maxn],r[maxn],siz[maxn],ncnt;
    int Root=0;void init(){Root=0;ncnt=0;}
    int _cmp(int t,int x){
        if(k[t]==x)return -1;
        return x < k[t] ? 0 : 1;
    }
    void maintain(int t){
        siz[t]=siz[ch[t][0]]+siz[ch[t][1]]+1;
    }
    void rotate(int& t,int d){
        int k=ch[t][d^1];
        ch[t][d^1]=ch[k][d];ch[k][d]=t;
        maintain(t);t=k;maintain(k);
    }
    void insert(int& t,int x){
        if(t==0){
            t=++ncnt;ch[t][0]=ch[t][1]=0;
            k[t]=x;r[t]=rand();siz[t]=1;
        }else{
            int d=_cmp(t,x);if(d==-1)return;
            insert(ch[t][d],x);maintain(t);
            if(r[t]<r[ch[t][d]])rotate(t,d^1);
        }
    }
    void erase(int& t,int x){
        if(t==0)return;     int d=_cmp(t,x);
        if(d==-1){
            if(ch[t][0]==0 && ch[t][1]==0)t=0;
            else if(ch[t][0]==0 || ch[t][1]==0)
                t=ch[t][0]+ch[t][1];
            else{
                rotate(t,0);erase(ch[t][0],x);
                maintain(t);
            }
        }else erase(ch[t][d],x),maintain(t);
    }
    int rank(int t,int x){
        if(t==0)return 1;   int d=_cmp(t,x);
        if(d==-1)return siz[ch[t][0]]+1;
        int ans=rank(ch[t][d],x);
        return d==1 ? ans+siz[ch[t][0]]+1 : ans;
    }
    int _comp(int x,int y){
        if(x==y)return -1;
        return x>y ? 0 : 1;
    }
    int xth(int t,int x){
        if(t==0)return x<=0 ? -0x7f7f7f7f : 0x7f7f7f7f;
        int d=_comp(siz[ch[t][0]]+1,x);
        if(d==-1)return k[t];
        if(d== 1)x-=siz[ch[t][0]]+1;
        return xth(ch[t][d],x);
    }
    void insert(int x){     insert(Root,x);}
    void erase (int x){      erase(Root,x);}
    int  rank  (int x){return rank(Root,x);}
    int  xth   (int x){return  xth(Root,x);} 
}
#include<cctype>
int geti(){
    int ans=0;int flag=0;char c=getchar();
    while(!isdigit(c)){flag|=c=='-';c=getchar();}
    while( isdigit(c)){ans=ans*10+c-'0';c=getchar();}
    return flag?-ans:ans;
}
template<class T>inline void puti(T x){
    if(x<0)x=-x,  putchar('-');
    if(x>9)puti(x/10); putchar(x%10+'0');
}
int main(){
    int q=geti();//instructions
    while(q--){
        int op=geti(),x=geti();
             if(op==1)    Treap::insert(x);//insert
        else if(op==2)     Treap::erase(x);//erase
        else if(op==3)puti(Treap::rank(x)),putchar('\n');//rank
        else if(op==4) puti(Treap::xth(x)),putchar('\n');//xth
    }
    return 0;
}

暴力代码 bforce.cpp

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=1000000+10;bool exist[maxn];
int rank(int x){
    int ans=0;
    for(int i=1;i<x;i++)ans+=exist[i];
    return ans+1;
}
int xth(int x){
    for(int i=1;x;i++){
        x-=exist[i];if(x==0)return i;
    }
}
int main(){
    int q;scanf("%d",&q);
    while(q--){
        int op,x;scanf("%d%d",&op,&x);
             if(op==1)exist[x]=1;
        else if(op==2)exist[x]=0;
        else if(op==3)printf("%d\n",rank(x));
        else if(op==4) printf("%d\n",xth(x));
    }
    return 0;
}

数据生成器 data.cpp

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<ctime>
using namespace std;
int rand(int L,int R){return rand()%(R-L+1)+L;}
bool exist[1000000+10];int cnt=0;
int main(){
    srand(time(NULL));
    int n=rand(50,100);printf("%d\n",n);
    while(cnt<30){
        int x=rand(1,1000);
        if(exist[x])continue;
        exist[x]=1;cnt++;
        printf("1 %d\n",x);
    }
    while(cnt>20){//erase
        for(int j=1;j<=1000 && cnt>20;j++){
            if(exist[j] && (rand()&1)){
                cnt--;exist[j]=0;printf("2 %d\n",j);
            }
        }
    }
    for(int i=41;i<=n;i++){
        int op=rand(3,4);
        if(op==3){
            int x;
            while(1)for(int i=1;i<=1000;i++)
                if(exist[i] && (rand()&1)){
                    x=i;goto outside;
                }
            outside:printf("3 %d\n",x);
        }else if(op==4){
            int x=rand(1,cnt);
            printf("4 %d\n",x);
        }
    }
    return 0;
}

windows下的batch对拍 try.bat

@echo off
set /a i=1
:begin
  if %i% GTR 100 goto end
  echo round %i% ...
  data.exe   > input.txt
  treap.exe  < input.txt > output.txt
  bforce.exe < input.txt > std.txt
  fc output.txt std.txt
  if errorlevel 1 pause
  set /a i=i+1
goto begin
:end
echo end!
pause

即使本程序已经过多次对拍,如有谬误,敬请谅解。

附赠头文件 Treap.hpp

重要的是吐槽的内容,指出了一些常见的错误。

#pragma once      //这是 GGN 在 2017.12.23 写得一个小头文件 
#include<cstdlib> //这些注释标明了一些容易写错的地方,以及一些吐槽
#ifndef maxn      //你可以在include之前define一个maxn,以实现对内存大小的控制 
  #define maxn (1000000+10) // GGN 指针用得挺好的,但是更喜欢用数组存树... 
#endif
namespace std{    //你要像用stl一样写上 "using namespace std;" 
    class Treap{  //我懒得把这个类写成模板了... 
        int ch[maxn][2],k[maxn],r[maxn],siz[maxn],ncnt; //懒得定义结构体存结点 
        int Root;  //这是树的根 
        int _cmp(int t,int x){
            if(k[t]==x)return -1;
            return x < k[t] ? 0 : 1;
        }
        void maintain(int t){ //在涉及元素插入删除,子树交换时一定要【maintain】 
            siz[t]=siz[ch[t][0]]+siz[ch[t][1]]+1;//这里应该记得 "+1" 
        }
        void rotate(int& t,int d){//treap的核心思想就是rotate 
            int k=ch[t][d^1];
            ch[t][d^1]=ch[k][d];ch[k][d]=t;//交换顺序逻辑性其实很强 
            maintain(t);t=k;maintain(k);//注意maintain的顺序 
        }
        void insert(int& t,int x){//insert/erase/rotate 传引用,其余不用 
            if(t==0){
                t=++ncnt;ch[t][0]=ch[t][1]=0;//申请新的节点要注意清零 
                k[t]=x;r[t]=rand();siz[t]=1;//记得将siz设成1 
            }else{
                int d=_cmp(t,x);if(d==-1)return;
                insert(ch[t][d],x);maintain(t);//记得maintain 
                if(r[t]<r[ch[t][d]])rotate(t,d^1);//此处不用maintain 
            }
        }
        void erase(int& t,int x){//在涉及树形态变化时,要传引用,反之不用 
            if(t==0)return;     int d=_cmp(t,x);
            if(d==-1){
                if(ch[t][0]==0 && ch[t][1]==0)t=0;//直接砍掉,丢到内存里就好了 
                else if(ch[t][0]==0 || ch[t][1]==0)
                    t=ch[t][0]+ch[t][1];//简洁明了 
                else{
                    rotate(t,0);erase(ch[t][0],x);//左旋,右旋其实都可以 
                    maintain(t);//此处需要maintain 
                }
            }else erase(ch[t][d],x),maintain(t);//maintain 
        }
        int rank(int t,int x){
            if(t==0)return 1;   int d=_cmp(t,x);
            if(d==-1)return siz[ch[t][0]]+1;
            int ans=rank(ch[t][d],x);
            return d==1 ? ans+siz[ch[t][0]]+1 : ans;//注意哪个加,哪个不加 
        }
        int _comp(int x,int y){
            if(x==y)return -1;
            return x>y ? 0 : 1;//comp和cmp的定义左右大小正好是烦的 
        }
        int xth(int t,int x){
            if(t==0)return x<=0 ? -0x7f7f7f7f : 0x7f7f7f7f;//玄学... 
            int d=_comp(siz[ch[t][0]]+1,x);
            if(d==-1)return k[t];
            if(d== 1)x-=siz[ch[t][0]]+1;//注意哪个减,哪个不减 
            return xth(ch[t][d],x);
        }
        public:
            void init(){Root=0;ncnt=0;}
            Treap(){init();}
            void insert(int x){     insert(Root,x);}
            void erase (int x){      erase(Root,x);if(Root==0)init();}//节约空间 
            int  rank  (int x){return rank(Root,x);}
            int  xth   (int x){return  xth(Root,x);}
            bool empty ()     {return Root==0;}
            int  size  ()     {return siz[Root];}
    };
}
#undef maxn
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值