…之前仿佛讲得很水…现在稍微详细一些讲一下…
树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。
意思就是给每个节点分配一个随机权值,使得该数据结构在满足随机权值满足堆的性质的情况下,在保存数据满足BST性质…所以说既然是一棵BST,所以说我们可以通过维护BST性质来维护这棵平衡树…既然大家会堆…我们就rand权值给堆…
大概是这么个操作…
数据结构定义:
struct Treap{
int lson,rson,val,rnd,size,wei;
}treap[MAXN];
我们来看insert函数
insert(root,x);
操作是这样的:(请大家先不要管其中关于Rotate的操作[没错之前的单词打错了])
void insert(int &k,int x){
if(k==0){
k=++sz;treap[k].size=treap[k].wei=1;
treap[k].val=x;treap[k].rnd=rand_num();return;
}
treap[k].size++;
if(treap[k].val==x) treap[k].wei++;
else if(treap[k].val<x){
insert(treap[k].rson,x);
if(treap[treap[k].rson].rnd<treap[k].rnd)
Left_Ratote(k);
}else{
insert(treap[k].lson,x);
if(treap[treap[k].lson].rnd<treap[k].rnd)
Right_Ratote(k);
}
}
首先从根节点开始,向下进行查找,按照BST的性质,如果数据大了往右查找,小于往左查找,等于的话就给当前节点的wei权值++,wei权值存储的是有多少个相同的数据…
在往下查找的时候,把当前点的权重size++,表示他的多了一个…子孙
比如我们要insert一个权值为4的点,那么我们的查找方式是这样的
把这个点插入在val=4的地方的时候,进行rand,随机给其分配一个权值
inline int rand_num(){
static int seed=520;
return seed=int(seed*48271LL%2147483647);
}
如果访问到一个k==0的地方,就是一个空的节点了
treap[k].rnd=rand_num();
现在来讲解旋转,从代码可以看出,如果我们分配的节点的随机值不满足堆的性质的话,我们可以进行调整,旋转是什么呢…(好像大家都知道)
所以显然可以得知左旋右旋的规律,右旋就是把左儿子旋转到上面去,比如说图中的X是Y的父亲,这个时候右旋Y,右旋以顺时针旋转将Y旋转到X的上方,这个时候X就认Y做爹
…
左旋也是一个意思,只不过是把X逆时针旋转到Y上方,Y认X做爹
…
所以这个旋转的方式我们懂了…意义何在啊…
如果我们规定旋转前的BST长左边那个样子,我们会惊奇地发现,旋转到右边之后,竟然还是满足BST性质,所以我们进行旋转之后,BST的性质是不会改变的
那么我们为什么要旋转呢…
试想一棵BST,我们如果需要查询其中点的信息,期望复杂度是O(logn)啊…但是如果出现这种情况呢
现在我们进行如图右所示的操作,不断插入一些有单调性的值
现在我要查询值10的位置怎么办…显然已经退化成一条链了,BST性质好像没啥用?都O(n)的查询复杂度了
所以我们进行旋转,把一些节点进行旋转之后,保持一种矮矮胖胖的好身材,也就是我们熟悉的堆的模样,这样一来可以在数学期望上使得一棵BST尽量地平衡,也就是size balance
右旋操作:
void Right_Ratote(int &k){
int t=treap[k].lson;treap[k].lson=treap[t].rson;treap[t].rson=k;
treap[t].size=treap[k].size;update(k);k=t;
}
左旋操作:
void Left_Ratote(int &k){
int t=treap[k].rson;treap[k].rson=treap[t].lson;treap[t].lson=k;
treap[t].size=treap[k].size;update(k);k=t;
}
以及点信息维护pushup函数(旋转了之后size会发生变化)
void update(int k){
treap[k].size=treap[treap[k].lson].size+treap[treap[k].rson].size+treap[k].wei;
}
删除操作(BST的删除操作一般是最难的,除了splay(还有spaly233)的区间翻转,然而区间翻转只是利用了splay灵活的维护根的方式,而已经并不满足BST性质了)
void del(int &k,int x){
if(k==0) return;//如果寻址的时候寻到空址,说明treap里没有这个值,直接return即可
if(treap[k].val==x){//如果找到这个值
if(treap[k].wei>1){//如果这个值的数量大于1,则直接把数量减一即可
treap[k].wei--;
treap[k].size--;//改点的size也会减少
return;
}
if(treap[k].lson*treap[k].rson==0){//这句话的含义是,如果左子和右子有一个空
k=treap[k].lson+treap[k].rson;//就找出有的那个孩子的标号
}else if(treap[treap[k].lson].rnd<treap[treap[k].rson].rnd){//然后进行对rnd值的判定
Right_Ratote(k);//然后进行旋转操作
del(k,x);//并且递归地去删除
}else{
Left_Ratote(k);
del(k,x);
}
}else if(treap[k].val<x){//按照BST性质寻址
treap[k].size--;
del(treap[k].rson,x);//递归删除
}else{
treap[k].size--;
del(treap[k].lson,x);//至此删除删除的操作已经梳理完毕
}
}
接下来的几个函数大家应该看一遍就可以懂了…其实也可以自己敲一遍
查询rank
int query_rank(int k,int x){
if(!k) return 0;
if(treap[k].val==x) return treap[treap[k].lson].size+1;
else if(treap[k].val<x) return treap[treap[k].lson].size+treap[k].wei+query_rank(treap[k].rson,x);
else return query_rank(treap[k].lson,x);
}
查询某rank的值
int query_num(int k,int x){
if(!k) return 0;
if(x<=treap[treap[k].lson].size) return query_num(treap[k].lson,x);
else if(x>treap[treap[k].lson].size+treap[k].wei) return query_num(treap[k].rson,x-treap[treap[k].lson].size-treap[k].wei);
else return treap[k].val;
}
类似STL的lower_bound();操作
void query_LB(int k,int x){
if(!k) return;
if(treap[k].val<x){
ans=k;
query_LB(treap[k].rson,x);
}else{
query_LB(treap[k].lson,x);
}
}
类似STL的upper_bound();操作
void query_UB(int k,int x){
if(!k) return;
if(treap[k].val>x){
ans=k;
query_UB(treap[k].lson,x);
}else{
query_UB(treap[k].rson,x);
}
}
至此,treap的基本操作已经讲解完毕(当然还有一些奇奇怪怪的搞法,比如无旋treap,可以实现区间翻转[感到害怕])