treap(树堆)
是在二叉搜索树的基础上,通过维护随机附加域,使其满足堆性质,从而使树相对平衡的二叉树;
为什么可以这样呢?
因为在维护堆的时候可以同时保证搜索树的性质;
(比如当一棵树的一个域满足堆的性质时,只要不断的互换左右,她的另一个域总会满足搜索树的性质)
(当一棵树的一个域满足搜索树的性质时,只要不断的上下旋转,她的另一个域总会满足二叉堆的性质)
于是树堆有两种实现:
旋转treap:
她的操作基于旋转;
注意:当存在相同val时,对于旋转treap,是存在一个节点中的;
roll(旋转操作)
交换点x与她的x.ch(i):
fa(x).ch(i)=x.ch(i);
x.ch(i)=x.ch(i).ch(i^1);
x.ch(i).ch(i^1)=x;
以上操作是一个改变上下位置却不影响搜索树性质的方法,与splay类似;
1 void roll(int &now){ 2 int wh=data[data[now].ch[0]].key<data[data[now].ch[1]].key?0:1; 3 int son=data[now].ch[wh]; 4 data[now].ch[wh]=data[son].ch[wh^1]; 5 data[son].ch[wh^1]=now; 6 up(now); 7 now=son; 8 }
insert(插入操作)
普通的搜索树的插入操作,是插入一个叶子;
普通的堆也是插入一个叶子,然后再把她旋转到相应的位置上;
这样的话,只要按搜索树的法则插入一个节点,然后再以不影响搜索树性质的方式把她旋转到符合堆性质的位置即可;
1 void insert(int &now){ 2 if(now==0){ 3 now=make_data(x); 4 return; 5 } 6 if(data[now].value==x){ 7 data[now].cnt++; 8 data[now].size++; 9 } 10 else{ 11 int wh=x < data[now].value ? 0 : 1; 12 insert(data[now].ch[wh]); 13 if(data[now].key>=data[data[now].ch[wh]].key) 14 roll(now); 15 } 16 up(now); 17 }
del(删除操作)
与堆类似的;
把她通过旋转操作下沉到叶子节点,然后再断开即可;
1 void del(int &now){ 2 if(data[now].value==x){ 3 if(data[now].cnt==1){ 4 if(data[now].ch[0]*data[now].ch[1]==0){ 5 now=data[now].ch[1]+data[now].ch[0]; 6 return ; 7 } 8 roll(now); 9 int wh=data[data[now].ch[0]].value==x?0:1; 10 del(data[now].ch[wh]); 11 } 12 else{ 13 data[now].size--; data[now].cnt--; 14 } 15 } 16 else{ 17 int wh=data[now].value>x?0:1; 18 del(data[now].ch[wh]); 19 } 20 up(now); 21 }
这两个操作是与堆流程类似的操作(虽然作为c++党我不写堆);
还有寻找Kth number,寻找rank,寻找last、next等等与平衡树相关的操作,不在此处赘述;
局限性:不能完成区间操作;
有关代码:
1 #include<cstdio> 2 #include<cstdlib> 3 using namespace std; 4 #define INF 2147483647 5 int n; 6 struct poo 7 { 8 int size,value,key,cnt; 9 int ch[2]; 10 }data[100001]; 11 int tot,root,x; 12 int make_data(int ); 13 void insert(int&); 14 void roll(int&); 15 int find( ); 16 int rank( ); 17 void del(int&); 18 int las(int ); 19 int nex(int ); 20 void up(int ); 21 int main() 22 { 23 int i,j; 24 data[0].value=INF;data[0].key=INF; 25 scanf("%d",&n); 26 for(i=1;i<=n;i++){ 27 scanf("%d%d",&j,&x); 28 switch(j){ 29 case 1:insert(root);break; 30 case 2: del(root);break; 31 case 3: printf("%d ",rank( ));break; 32 case 4: printf("%d ",find( ));break; 33 case 5: printf("%d ", las(root));break; 34 case 6: printf("%d ", nex(root));break; 35 } 36 } 37 return 0; 38 } 39 int make_data(int value){ 40 tot++; 41 data[tot].cnt++; 42 data[tot].key=(rand()/2+rand()/2); 43 data[tot].size=1; 44 data[tot].value=value; 45 return tot; 46 } 47 void insert(int &now){ 48 if(now==0){ 49 now=make_data(x); 50 return; 51 } 52 if(data[now].value==x){ 53 data[now].cnt++; 54 data[now].size++; 55 } 56 else{ 57 int wh=x < data[now].value ? 0 : 1; 58 insert(data[now].ch[wh]); 59 if(data[now].key>=data[data[now].ch[wh]].key) 60 roll(now); 61 } 62 up(now); 63 } 64 void roll(int &now){ 65 int wh=data[data[now].ch[0]].key<data[data[now].ch[1]].key?0:1; 66 int son=data[now].ch[wh]; 67 data[now].ch[wh]=data[son].ch[wh^1]; 68 data[son].ch[wh^1]=now; 69 up(now); 70 now=son; 71 } 72 int find(){ 73 int now=root; 74 int ls,rs; 75 ls=data[now].ch[0];rs=data[now].ch[1]; 76 while(x<=data[ls].size||x>data[now].size-data[rs].size){ 77 if(data[ls].size>=x) 78 now=ls; 79 else{ 80 x=x+data[rs].size-data[now].size; 81 now=rs; 82 } 83 ls=data[now].ch[0];rs=data[now].ch[1]; 84 } 85 return data[now].value; 86 } 87 int rank(){ 88 int now=root,ans=0; 89 int ls=data[now].ch[0],rs=data[now].ch[1]; 90 while(x!=data[now].value&&x!=0) 91 { 92 if(x<data[now].value) 93 now=ls; 94 else{ 95 ans+=data[now].size-data[rs].size; 96 now=rs; 97 } 98 ls=data[now].ch[0];rs=data[now].ch[1]; 99 } 100 return ans+data[ls].size+1; 101 } 102 void del(int &now){ 103 if(data[now].value==x){ 104 if(data[now].cnt==1){ 105 if(data[now].ch[0]*data[now].ch[1]==0){ 106 now=data[now].ch[1]+data[now].ch[0]; 107 return ; 108 } 109 roll(now); 110 int wh=data[data[now].ch[0]].value==x?0:1; 111 del(data[now].ch[wh]); 112 } 113 else{ 114 data[now].size--; data[now].cnt--; 115 } 116 } 117 else{ 118 int wh=data[now].value>x?0:1; 119 del(data[now].ch[wh]); 120 } 121 up(now); 122 } 123 int las(int now){ 124 int ans=0,an=0; 125 if(!now)return 0; 126 if(data[now].value<x){ 127 ans=data[now].value; 128 an=las(data[now].ch[1]); 129 ans=an!=0?an:ans; 130 } 131 else{ 132 ans=las(data[now].ch[0]); 133 } 134 return ans; 135 } 136 int nex(int now){ 137 int ans=0,an=0; 138 if(!now)return 0; 139 if(data[now].value>x){ 140 ans=data[now].value; 141 an=nex(data[now].ch[0]); 142 ans=an!=0?an:ans; 143 } 144 else{ 145 ans=nex(data[now].ch[1]); 146 } 147 return ans; 148 } 149 void up(int now){ 150 data[now].size=data[data[now].ch[0]].size+data[data[now].ch[1]].size+data[now].cnt; 151 } 152 //treap on the 2017.1.21 153 //10 154 //1 5 155 //4 1 156 //1 6 157 //1 7 158 //1 10 159 //1 3 160 //1 4 161 //6 2 162 //1 8 163 //5 9 164 // 165 //14 166 //1 5 167 //1 6 168 //1 7 169 //1 10 170 //1 3 171 //1 4 172 //1 8 173 //3 3 174 //3 4 175 //3 5 176 //3 6 177 //4 5 178 //4 6 179 //4 7
非旋转treap
同样是树堆,旋转treap通过与堆类似的旋转操作维护,但非旋转treap,通过与平衡树类似的拆分|合并操作完成;
注意:当存在相同val时,对于非旋转treap,是存在不同节点中的;
split(拆分操作)
这里介绍按value拆分(还有按排名拆分不讲)
首先预留两个变量名作为两棵树的树根名;
然后从原树根开始,按查找value为k的点的方法往下走,对于每一个走到的点,她大于value则属于右树,否则属于左树,
因为是从上往下走的,所以后进入树的点是先入者的子节点(符合堆性质)
因为当x.val>value时下一步走x.ls,之后的点全比x小,所以把x接到右树后,下一个接入右树的点——不管是谁——应当变成x的左儿子;
对x.val≤value,也是相似的;
1 void split(int now,int<r,int&rtr,int value){ 2 if(!now){ 3 ltr=rtr=0; 4 return; 5 } 6 if(data[now].val<=value) 7 ltr=now,split(data[now].ch[1],data[ltr].ch[1],rtr,value); 8 else 9 rtr=now,split(data[now].ch[0],ltr,data[rtr].ch[0],value); 10 up(now); 11 }
merge(合并操作)
这里介绍当treapA的所有节点val小于treapB时的操作(即通常使用的操作)
可以看出只要把A的右链和B的左链,拆开,按符合堆上下顺序排好,再连上符合搜索树的父子关系即可;
因为AB分别满足堆性质,所以所谓的“按符合堆上下顺序排好”,只要互相参差插入即可,不改变A,B各自的相对上下顺序;
连父子关系时,属于A树的点需要连一个rs(因为排在她下方的点都比她大)属于B树的点需要连一个ls(因为排在她下方的点都比她小)
实现是递归的
merge( &now , a , b )-->( a.key < b.key ? ( now=a.rs , merge( &now.rs , a.rs , b ) ) : ( now=b.ls , merge( &now.ls , a , b.ls ) ) );
1 void merge(int&now,int s,int b){ 2 if(!s||!b){ 3 now=s+b;return; 4 } 5 if(data[s].key<data[b].key) 6 now=s,merge(data[now].ch[1],data[s].ch[1],b); 7 else 8 now=b,merge(data[now].ch[0],s,data[b].ch[0]); 9 up(now); 10 }
insert(value):把树split成A树≤value,B树>value,然后建一个value的点x,然后merge(root,A,x),merge(root,root,B);
del(value):把树拆成三部分,中间是等于value的点——把这部分的点减少一个,然后再合并树的三部分;
非旋转treap是一种非常好的平衡树,她有treap的优良性质——常数比Splay小,而且还支持区间操作和可持久化,
重点是代码短
唯一的缺点的是不好理解
有关代码:
1 #include<cstdio> 2 #include<cstdlib> 3 using namespace std; 4 const int INF=2147483600; 5 struct Treap{ 6 int val,key,size; 7 int ch[2]; 8 }data[200010]; 9 int root,tot; 10 void make_data(int&,int ); 11 void up(int ); 12 void merge(int&,int,int); 13 void split(int ,int&,int&,int ); 14 void insert(int ); 15 void del(int ); 16 void rank(int ); 17 void find(int ,int ); 18 int las(int ); 19 int nex(int ); 20 int main() 21 { 22 srand(2.17); 23 int i,j,k,m; 24 root=0;data[0].key=INF;data[0].val=INF;data[0].size=0; 25 scanf("%d",&m); 26 for(i=1;i<=m;i++){ 27 scanf("%d%d",&j,&k); 28 if(j==1)insert(k); 29 if(j==2)del(k); 30 if(j==3)rank(k); 31 if(j==4)find(root,k); 32 if(j==5)las(k); 33 if(j==6)nex(k); 34 } 35 return 0; 36 } 37 void make_data(int&now,int value){ 38 data[++tot].val=value;data[tot].key=rand(); 39 data[tot].size=1; 40 data[tot].ch[0]=data[tot].ch[1]=0; 41 now=tot; 42 } 43 void up(int now){ 44 data[now].size=data[data[now].ch[0]].size+data[data[now].ch[1]].size+1; 45 } 46 void merge(int&now,int s,int b){ 47 if(!s||!b){ 48 now=s+b;return; 49 } 50 if(data[s].key<data[b].key) 51 now=s,merge(data[now].ch[1],data[s].ch[1],b); 52 else 53 now=b,merge(data[now].ch[0],s,data[b].ch[0]); 54 up(now); 55 } 56 void split(int now,int<r,int&rtr,int value){ 57 if(!now){ 58 ltr=rtr=0; 59 return; 60 } 61 if(data[now].val<=value) 62 ltr=now,split(data[now].ch[1],data[ltr].ch[1],rtr,value); 63 else 64 rtr=now,split(data[now].ch[0],ltr,data[rtr].ch[0],value); 65 up(now); 66 } 67 void insert(int value){ 68 int x=0,y=0,z=0; 69 make_data(z,value); 70 split(root,x,y,value); 71 merge(x,x,z); 72 merge(root,x,y); 73 } 74 void del(int value){ 75 int x=0,y=0,z=0; 76 split(root,x,y,value); 77 split(x,x,z,value-1); 78 merge(z,data[z].ch[0],data[z].ch[1]); 79 merge(x,x,z);merge(root,x,y); 80 } 81 void rank(int value){ 82 int x=0,y=0; 83 split(root,x,y,value-1); 84 printf("%d ",data[x].size+1); 85 merge(root,x,y); 86 } 87 void find(int now,int x){ 88 while(data[data[now].ch[0]].size+1!=x){ 89 if(data[data[now].ch[0]].size>=x) 90 now=data[now].ch[0]; 91 else 92 x-=(data[data[now].ch[0]].size+1),now=data[now].ch[1]; 93 } 94 printf("%d ",data[now].val); 95 } 96 int las(int value){ 97 int x=0,y=0; 98 split(root,x,y,value-1); 99 find(x,data[x].size); 100 merge(root,x,y); 101 } 102 int nex(int value){ 103 int x=0,y=0; 104 split(root,x,y,value); 105 find(y,1); 106 merge(root,x,y); 107 }