平衡二叉树之Treap树&Splay树

一般平衡二叉树就是根据二叉log时间复杂度插入搜索之类的,如果插入的数小比如十万左右,那么好像可以权值线段树就行,但是如果是int范围那么只能二叉树了,所以这应该就是平衡二叉树的一个重要用法。
Treap树就是平衡二叉树,每个点都有两个值,分别是优先级和键值,Treap树满足父节点的优先级大于两个子节点(如果有的话),而且满足父节点的左子节点的键值小于父节点,右子节点键值大于父节点,所以如果每个节点的优先级已经确定好了的话的话,那么这个Treap树的形态也是确定好的,而优先级这东西是靠随机函数赋值的,所以最坏情况下可能会导致Treap树成为一条链,但是在数学期望下Treap基本是平衡的,所以要相信自己的人品。

这个是书上的版本,挺简单的,但是功能不全

struct node{
 int size,rank,key;
 node *son[2];
 bool operator<(const node &a)const{return rank<a.rank;}
 int cmp(int x)const{
  if(x==key)
  return -1;
  return x<key?0:1;
 }
 void update(){
  size=1;
  if(son[0]!=NULL)
  size+=son[0]->size;
  if(son[1]!=NULL)
  size+=son[1]->size;
 }
}; 
void rotate(node* &o,int d){//d=0左旋 d=1右旋 
 node *k=o->son[d^1];
 o->son[d^1]=k->son[d];
 k->son[d]=o;
 o->update();
 k->update();
 o=k;
}
void insert(node* &o,int x){//插入一个键值,随机赋优先级 
 if(o==NULL){
  o=new node();
  o->son[0]=o->son[1]=NULL;
  o->rank=rand();
  o->key=x;
  o->size=1; 
 }
 else{
  int d=o->cmp(x);
  insert(o->son[d],x);
  o->update();
  if(o<o->son[d])
  rotate(o,d^1);
 }
}
int kth(node* o,int k){//查找第k大 
 if(o==NULL||k<=0||k>o->size)
 return -1;
 int s=o->son[1]==NULL?0:o->son[1];
 if(k==s+1)
 return o->key;
 else if(k<=s)
 return kth(o->son[1],k);
 else
 return kth(o->son[0],k-s-1);
}
int find(node* o,int k){//找k这个数是第几大 
 if(o==NULL)
 return -1;
 int d=o->cmp(k);
 if(d==-1){
  /*if(o->son[1]==NULL)
  return 1;
  if(o->son[1]->cmp(k)==-1)
  return find(o->son[1],k);
  else
  return o->son[1]->size+1;*/
  //这个是如果k这个数有多个,找最小的那一个,比如12333,查3就是3
  return o->son[1]==NULL?1:o->son[1]->size+1;
  //这个是随便返回k这个数是第几大,比如12333,可能会是3,是4,是5 
 }
 else if(d==0){
  int tmp=find(o->son[0],k);
  if(tmp==-1)
  return -1;
  else
  return o->son[1]==NULL?tmp+1:tmp+1+o->son[1]->size;
 }
 else{
  return find(o->son[d],k);
 }
}

P3369 【模板】普通平衡树
1.插入x数
2.删除x数(若有多个相同的数,因只删除一个)
3.查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
4.查询排名为x的数
5.求x的前驱(前驱定义为小于x,且最大的数)
6.求x的后继(后继定义为大于x,且最小的数)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N=101000;
const int INF=0x3f3f3f3f;
struct node{
 int rank,key,w,size;
 int son[2];
 node(int key=0):key(key){
  rank=rand()%100000;
  son[0]=son[1]=0;
  w=size=1;
 }
}tree[MAX_N];
int root,cnt=0;
void update(int k){
 tree[k].size=tree[tree[k].son[0]].size+tree[tree[k].son[1]].size+tree[k].w;
}
void rotate(int &k,int d){//d=0右旋
 int t=tree[k].son[d];
 tree[k].son[d]=tree[t].son[d^1];
 tree[t].son[d^1]=k;
 tree[t].size=tree[k].size;
 update(k);
 k=t;
}
void insert(int &k,int x){
 if(k==0){
  k=++cnt;
  tree[k].key=x;
  return;
 }
 if(tree[k].key==x){
  tree[k].w++;
  update(k);
  return;
 }
 /*int d=x>tree[k].key;
 int &t=tree[k].son[d];
 if(tree[t].rank<tree[k].rank)
 rotate(k,d);*/
 if(x<tree[k].key){
        int &t=tree[k].son[0];
        insert(t,x);
        if(tree[t].rank<tree[k].rank)
            rotate(k,0);
 }
 else{
        int &t=tree[k].son[1];
        insert(t,x);
        if(tree[t].rank<tree[k].rank)
            rotate(k,1);
 }
 tree[k].size++;
}
int remove(int &k,int x){
 if(k==0)
 return 0;
 if(x==tree[k].key){
  if(tree[k].w>1){
   tree[k].size--;
   tree[k].w--;
   return 1;
  }
  else{
   if(tree[k].son[0]==0){
    k=tree[k].son[1];
    return 1;
   }
   if(tree[k].son[1]==0){
    k=tree[k].son[0];
    return 1;
   }
   if(tree[k].son[0]&&tree[k].son[1]){
    if(tree[tree[k].son[0]].rank<tree[tree[k].son[1]].rank){
     rotate(k,0);
     tree[k].size--;
     return remove(tree[k].son[1],x);
    }
    else{
     rotate(k,1);
     tree[k].size--;
     return remove(tree[k].son[0],x);
    }
   }
  }
 }
 if(!remove(tree[k].son[x<tree[k].key?0:1],x))
 return 0;
 tree[k].size--;
 return 1;
}
int rank(int &k,int x){
 if(!k)
 return 0;
 int left_size=tree[tree[k].son[0]].size;
 if(x==tree[k].key)
 return left_size+1;
 if(x<tree[k].key)
 return rank(tree[k].son[0],x);
 else
 return rank(tree[k].son[1],x)+tree[k].w+left_size;
}
int kth(int &k,int x){
 if(!k)
 return 0;
 int left_size=tree[tree[k].son[0]].size;
 //cout<<tree[k].son[0]<<"!\n";
 if(left_size<x&&x<=left_size+tree[k].w)
 return tree[k].key;
 if(x<=left_size)
 return kth(tree[k].son[0],x);
    if(x>left_size+tree[k].w)
    return kth(tree[k].son[1],x-left_size-tree[k].w);
}
int pred(int &k,int x){
 if(!k)
 return -INF;
 if(x<=tree[k].key)
 return pred(tree[k].son[0],x);
 int res;
 res=pred(tree[k].son[1],x);
 return tree[k].key>res?tree[k].key:res;
}
int succ(int &k,int x){
 if(!k)
 return INF;
 //cout<<tree[k].son[1]<<"!\n";
 if(x>=tree[k].key)
 return succ(tree[k].son[1],x);
 int res;
 res=succ(tree[k].son[0],x);
 return tree[k].key<res?tree[k].key:res;
}
int main(void){
 int n,i,op,x;
 scanf("%d",&n);
 root=0;
 cnt=0;
 tree[0].key=0;
 tree[0].rank=0;
 tree[0].w=0;
 tree[0].size=0;
 for(i=1;i<=n;i++){
        //cout<<"root="<<root<<"\n";
  scanf("%d%d",&op,&x);
  if(op==1)
  insert(root,x);
  else if(op==2)
  remove(root,x);
  else if(op==3)
  printf("%d\n",rank(root,x));
  else if(op==4)
  printf("%d\n",kth(root,x));
  else if(op==5)
  printf("%d\n",pred(root,x));
  else if(op==6)
  printf("%d\n",succ(root,x));
 }
 return 0;
}

Splay树可以把某个节点向上旋转到指定位置,特别是可以旋转到根的位置,所以形态不是固定的,而且分裂和合并非常方便。
P3369 【模板】普通平衡树

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=101000;
int root,cnt;
int be[MAX_N];
int pre[MAX_N],size[MAX_N],son[MAX_N][2],w[MAX_N];
void update(int k){
 size[k]=size[son[k][0]]+size[son[k][1]]+w[k];
}
int get(int k){
 return son[pre[k]][1]==k;
}
void rotate(int x,int &t){
 int y=pre[x],z=pre[y],d=get(x),d1=get(y);
 if(y==t)
 t=x;
 else
 son[z][d1]=x;
 son[y][d]=son[x][d^1];
 pre[son[x][d^1]]=y;
 son[x][d^1]=y;
 pre[y]=x;
 pre[x]=z;
 update(y);
 update(x);
}
void splay(int x,int &t){
 for(int y=pre[x],z=pre[y];x!=t;rotate(x,t),y=pre[x],z=pre[y])
 if(y!=t)
 rotate(get(x)==get(y)?y:x,t);
}
int search(int k){
 int p=root;
 while(p&&k!=be[p])
 p=son[p][be[p]<k];
 return p;
}
int pred(int k){
 int t=son[k][0];
 splay(k,root);
 while(t&&son[t][1])
 t=son[t][1];
 return t;
}
int succ(int k){
 int t=son[k][1];
 splay(k,root);
 while(t&&son[t][0])
 t=son[t][0];
 return t;
}
void remove(int k){
 if(!k)
 return;
 splay(k,root);
 if(w[k]>1){
  w[k]--;
  size[k]--;
  return;
 }
 int l=son[k][0],r=son[k][1],now;
 now=l;
 pre[l]=pre[r]=son[k][0]=son[k][1]=0;
 root=l;
 while(now&&son[now][1])
 now=son[now][1];
 if(!now){
  root=r;
  return;
 }
 splay(now,root);
 son[now][1]=r;
 if(r)
 pre[r]=now;
 update(root);
}
int grank(int k){
 splay(k,root);
 return size[son[k][0]]+1;
}
int kth(int o,int k){
 int left_size=size[son[o][0]];
 if(k<=left_size+w[o]&&k>=left_size+1)
 return o;
 if(left_size>k-w[o])
 return kth(son[o][0],k);
 return kth(son[o][1],k-left_size-w[o]);
}
void insert(int k){
 if(!root){
        //cout<<"a1\n";
  root=++cnt;
  be[root]=k;
  size[root]=w[root]=1;
  son[root][0]=son[root][1]=pre[root]=0;
 }
 else if(search(k)){
     //cout<<"a2\n";
  int t=search(k);
  splay(t,root);
  size[t]++;
  w[t]++;
 }
 else{
     //cout<<"a3\n";
  int x=root,y;
  while(1){
   y=son[x][be[x]<k];
   if(!y){
    y=++cnt;
    be[y]=k;
    size[y]=w[y]=1;
    son[x][be[x]<k]=y;
    son[y][0]=son[y][1]=0;
    pre[y]=x;
    break;
   }
   x=y;
  }
  splay(y,root);
 }
}
int main(void){
 int m,i,op,x;
 scanf("%d",&m);
 for(i=0;i<m;i++){
  scanf("%d%d",&op,&x);
  if(op==1)
        insert(x);
  else if(op==2)
  remove(x);
  else if(op==3)
  printf("%d\n",grank(search(x)));
  else if(op==4)
  printf("%d\n",be[kth(root,x)]);
  else if(op==5){
   insert(x);
   printf("%d\n",be[pred(search(x))]);
   remove(search(x));
  }
  else if(op==6){
   insert(x);
   printf("%d\n",be[succ(search(x))]);
   remove(search(x));
  }
 }
 return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Splay Tree和Treap是两种常用的自平衡二叉查找,它们在数据结构和算法中都有应用。它们的区别主要体现在平衡策略和随机性的使用上: 1. **平衡策略**: - **Splay Tree**: Splay Tree采用的是"最近最频繁访问"(Most Recently Used, MRU)的原则。每当查找、插入或删除一个元素后,会调整使其根节点指向这个操作的直接结果,从而使得最近频繁访问的节点更接近根。这种操作是动态的,不需要预先知道访问模式。 - **Treap**: Treap是一种结合了二叉查找和堆的数据结构,它的平衡是基于随机优先级。每个节点除了有一个键值用于排序外,还有一个随机优先级。这样保证了在平均情况下,查找、插入和删除的时间复杂度接近于二叉查找(O(log n)),而随机性确保了在某些特定情况下能较快地达到平衡。 2. **插入和删除性能**: - Splay Tree插入和删除后,由于其动态调整,通常能保持局部平衡,但全局平衡可能不是最优的。 - Treap的平衡依赖于随机优先级,虽然不能保证每次操作后的全局平衡,但期望的平均性能良好。 3. **内存消耗**: - Treap因为包含随机优先级,需要额外存储优先级信息,这可能会稍微增加内存消耗。 4. **实现复杂性**: - Splay Tree的实现相对简单,因为其调整规则明确,但需要对旋转操作有深入理解。 - Treap的实现稍微复杂一些,因为它需要维护两个属性(键值和优先级),并且在插入和删除时需要处理随机优先级的更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值