[模板]平衡树

平衡树介绍

【定义】 平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。[摘自百度]

用途】 平衡树(这里都是不持久化的基础版本)是一个用于解决动态序列中,排名,前驱,后继的一种数据结构。平均效率接近logn

关键词】数据结构 数 动态序列 logn

例题】您需要写一种数据结构,来维护一些数,其中需要提供以下操作:

插入x数

删除x数(若有多个相同的数,因只删除一个)

查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)

查询排名为x的数

求x的前驱(前驱定义为小于xx,且最大的数)

求x的后继(后继定义为大于xx,且最小的数)

Treap

【简介】treap,即tree+heap。是我所接触到最早的可以实现平衡树的结构。主要用旋转(spin)来进行操作。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int res=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        res=res*10+(ch-'0');
        ch=getchar();
    }
    return res*f;
}
int number_data_sets,order,x,parameter=528,tot,size[500005];
int number[500005],pos[500005],children[500005][2],root;
int Rand(){
    parameter=~parameter*(parameter|2)%1000086;
    return parameter;
}
void up(int i){
    size[i]=size[children[i][0]]+size[children[i][1]]+1;
}
void spin(int &i,int p){
    int t=children[i][p];
    children[i][p]=children[t][!p],children[t][!p]=i,up(i),up(t),i=t;
}
void Insert(int x,int &i){
    if(!i){i=++tot,size[i]=1,number[i]=x,pos[i]=Rand();return;}
    size[i]++;
    if(x<=number[i]){
        Insert(x,children[i][0]);
        if(pos[children[i][0]]<pos[i])spin(i,0);
    }
    else{
        Insert(x,children[i][1]);
        if(pos[children[i][1]]<pos[i])spin(i,1);
    }
}
void extrack(int x,int &i){
if(number[i]==x){ 
if(children[i][0]*children[i][1]==0){i=children[i][0]+children[i][1];return;}
        if(pos[children[i][0]]>pos[children[i][1]]){
            spin(i,1);extrack(x,children[i][0]);
        }
        else {spin(i,0);extrack(x,children[i][1]);}
    }
    else if(number[i]>x)extrack(x,children[i][0]);
    else extrack(x,children[i][1]);
    up(i);
}
int Rank(int x,int i){
    if(!i)return 1;
    if(number[i]>=x)return Rank(x,children[i][0]);
    return Rank(x,children[i][1])+size[children[i][0]]+1;
}
int frank(int x,int i){
    if(size[children[i][0]]==x-1)return number[i];
    if(size[children[i][0]]>=x)return frank(x,children[i][0]);
    return frank(x-size[children[i][0]]-1,children[i][1]);
}
int prefix(int x,int i){
    if(!i)return -2000000005;
    if(number[i]<x)return max(number[i],prefix(x,children[i][1]));
    else return prefix(x,children[i][0]);
}
int succeed(int x,int i){
    if(!i)return 2000000005;
    if(number[i]>x)return min(number[i],succeed(x,children[i][0]));
    else return succeed(x,children[i][1]);
}
int main(){
    number_data_sets=read();
    while(number_data_sets--) {
    order=read(),x=read();
        switch(order){
            case 1:Insert(x,root);break;
            case 2:extrack(x,root);break;
            case 3:printf("%d\n",Rank(x,root));break;
            case 4:printf("%d\n",frank(x,root));break;
            case 5:printf("%d\n",prefix(x,root));break;
            case 6:printf("%d\n",succeed(x,root));break;
        }
    }
}

代码解读】之前有提到过,treap=tree+heap。那么这里的tree为二叉查找树(Binary Search Tree),严格满足左儿子<=根<=右儿子。但由于我们是动态操作,在某些构造数据的状况下,树可能会退化成链,复杂度达到O(n)级别。为了避免这种情况发生,另一个要素heap就出现了。每一个节点都会被赋予一个随机值(pos),而treap要利用旋转来满足值得BST关系,也满足pos的BST关系。通过加入另一个因子来有效防止退化。

同类算法】这一类被我归为“旋转”类型的平衡树,还有Splay,AVL,SBT树等。它们则是运用子树大小的关系来实施硬核旋转。

FHQ tree

【简介】FHQ tree 是不同于treap的“非旋转”平衡树,核心操作为分裂(split)与合并(merge),好理解也好写,是我第一个会写的平衡树。

代码

#include <cstdio>
int read() {
    int res=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        res=(res<<3)+(res<<1)+(ch-'0');
        ch=getchar();
    }
    return res*f;
}
const int N=100010;
int size_node[N],children[N][2],number_tree[N],number_heap[N],total,root, number_data_sets,order,x;
void update(int node_now) {size_node[node_now]=size_node[children[node_now][0]]+size_node[children[node_now][1]]+1;}
void split(int node_now,int k,int &tree_accept,int &tree_unaccept) {
    if(!node_now){
        tree_accept=tree_unaccept=0;
        return;
    }
    if(number_tree[node_now]>k) {
        tree_unaccept=node_now;
        split(children[node_now][0],k,tree_accept,children[node_now][0]);
    } else {
        tree_accept=node_now;
        split(children[node_now][1],k,children[node_now][1],tree_unaccept);
    }
    update(node_now);
}
int node_add(int k) {number_heap[++total]=Rand(),number_tree[total]=k,size_node[total]=1;return total;}
int merge(int tree_one,int tree_two) {
    if(!tree_one||!tree_two)return tree_one+tree_two;
    if(number_heap[tree_one]<number_heap[tree_two]) {
        children[tree_one][1]=merge(children[tree_one][1],tree_two);
        update(tree_one);
        return tree_one;
    } else {
        children[tree_two][0]=merge(tree_one,children[tree_two][0]);
        update(tree_two);
        return tree_two;
    }
}
void Insert(int k) {
    int tree_one,tree_two;
    split(root,k,tree_one,tree_two);
root=merge(merge(tree_one,node_add(k)),tree_two);
}
void extrack(int k) {
    int tree_less,tree_unless,tree_k;
    split(root,k,tree_less,tree_unless);
    split(tree_less,k-1,tree_less,tree_k);
 tree_k=merge(children[tree_k][0],children[tree_k][1]);
    root=merge(tree_less,merge(tree_k,tree_unless));
}
void Rank(int k) {
    int tree_less,tree_unless;
    split(root,k-1,tree_less,tree_unless);
    printf("%d\n",size_node[tree_less]+1);
    root=merge(tree_less,tree_unless);
}
void frank(int node_now,int k) {
    while(1) {
if(size_node[children[node_now][0]]>=k)node_now=children[node_now][0];
        else if(size_node[children[node_now][0]]+1<k)k-=size_node[children[node_now][0]]+1,node_now=children[node_now][1];
        else {printf("%d\n",number_tree[node_now]);return;}
    }
}
void prefix(int k) {
    int tree_less,tree_unless;
    split(root,k-1,tree_less,tree_unless);
    frank(tree_less,size_node[tree_less]);
    root=merge(tree_less,tree_unless);
}
void succeed(int k) {
    int tree_less,tree_unless;
    split(root,k,tree_less,tree_unless);
    frank(tree_unless,1);
    root=merge(tree_less,tree_unless);
}
int main() {
    number_data_sets=read();
    while(number_data_sets--) {
        order=read(),x=read();
        if(order==1) Insert(x);
        else if(order==2) extrack(x);
        else if(order==3) Rank(x);
        else if(order==4) frank(root,x);
        else if(order==5) prefix(x);
        else succeed(x);
    }
    return 0;
}

代码解读】通过split将一颗树分成>k与<=k的两棵树,添加就直接当做添加一棵树,删除分出三个再合并两个就是了。在不断merge的过程中也在不断替换根,使其不怎么退化。

权值线段树

简介】线段树,一个大家所熟知的老结构。这里是把它当桶来实现平衡树。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int res=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        res=res*10+(ch-'0');
        ch=getchar();
    }
    return res*f;
}
int b[500005],a[500005],order[500005],t,tot,size[2000005];
void change(int k,int l,int r,int h,int v){
    if(l>h||h>r)return;
    size[k]+=v;
    if(l==r)return;
    int mid=(l+r)>>1;
    change(k<<1,l,mid,h,v);
    change((k<<1)+1,mid+1,r,h,v);
}
int Rank(int k,int l,int r,int h){
    if(l>h)return 0;
    if(r<h)return size[k];
    if(l==r)return 1;
    int mid=(l+r)>>1;
    return Rank(k<<1,l,mid,h)+Rank((k<<1)+1,mid+1,r,h);
}
int frank(int k,int l,int r,int h){
    if(l==r) return l;
    int mid=(l+r)>>1;
    if(size[k<<1]>=h) return frank(k<<1,l,mid,h);
    else return frank((k<<1)+1,mid+1,r,h-size[k<<1]);
}
int main(){
    t=read();
    for(int i=1;i<=t;++i){
        order[i]=read();
        a[i]=read();
        if(order[i]!=4)b[++tot]=a[i];
    }
    sort(b,b+tot);
    for(int i=1;i<=t;++i)if(order[i]!=4)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
    for(int i=1;i<=t;++i){
        switch(order[i]){
            case 1:change(1,1,tot,a[i],1);break;
            case 2:change(1,1,tot,a[i],-1);break;
            case 3:printf("%d\n",Rank(1,1,tot,a[i]));break;
            case 4:printf("%d\n",b[frank(1,1,tot,a[i])]);break;
            case 5:printf("%d\n",b[frank(1,1,tot,Rank(1,1,tot,a[i])-1)]);break;
            case 6:printf("%d\n",b[frank(1,1,tot,Rank(1,1,tot,a[i]+1))]);
        }
    }
}

代码解读】在用它实现平衡树的时候,把它当成桶来看。每一个区间表示在这个区间中有多少个数。由于有负数和数据大,所以我们选择离散化一下。先读数据,再离线操作。

替罪羊树

简介】使用重构的方式来实现。是重量平衡树。运算很快。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int res=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        res=res*10+(ch-'0');
        ch=getchar();
    }
    return res*f;
}
int number_data_sets,order,x,tot,size[500005];
double alf=0.8;
int memory[500005],cur[500005],pool,poi,to_rebuild,true_size[500005];
int number[500005],node[500005],children[500005][2],root;
bool exist[500005];
bool isbad(int x){
    if(true_size[x]*alf<=(double)max(true_size[children[x][0]],true_size[children[x][1]]))return 1;
return 0;
}
inline void up(int x){
    size[x]=size[children[x][0]]+size[children[x][1]]+1;
    true_size[x]=true_size[children[x][0]]+true_size[children[x][1]]+1;
}
void findnode(int x){
    if(x){
        findnode(children[x][0]);
        if(exist[x])cur[++poi]=x;
        else memory[++pool]=x;
        findnode(children[x][1]);
    }
}
void build(int l,int r,int &x){
    int mid=(l+r)>>1;
    x=cur[mid];
    if(l==r){
        children[x][0]=children[x][1]=0;
        size[x]=true_size[x]=1;
        return;
    }
    if(l<mid)build(l,mid-1,children[x][0]);
    else children[x][0]=0;
    build(mid+1,r,children[x][1]);
    up(x);
}
void rebuild(int &x){
    poi=0;
    findnode(x);
    if(poi)build(1,poi,x);
    else x=0;
}
void Insert(int y,int &x){
    if(!x){
        x=memory[pool--];
        number[x]=y;
        exist[x]=1;size[x]=1;true_size[x]=1;
        children[x][0]=0;children[x][1]=0;
        return;
    }
    size[x]++,true_size[x]++;
    if(number[x]>=y)Insert(y,children[x][0]);
    else Insert(y,children[x][1]);
    if(isbad(x))rebuild(x);
}
int Rank(int k){
    int x=root;
    int ans=1;
    while(x){
        if(number[x]>=k)x=children[x][0];
        else{
            ans+=true_size[children[x][0]]+exist[x];
            x=children[x][1];
        }
    }
    return ans;
}
int frank(int k){
    int x=root;
    while(x){
        if(exist[x]&&true_size[children[x][0]]+1==k)return number[x];
        else if(true_size[children[x][0]]>=k)x=children[x][0];
        else{
            k-=true_size[children[x][0]]+exist[x];
            x=children[x][1];
        }
    }
    return 0;
}
void extrack(int k,int &x){
	if(exist[x]&&true_size[children[x][0]]+1==k){
		exist[x]=0;
		true_size[x]--;
		return;
	}
	true_size[x]--;
	if(true_size[children[x][0]]+exist[x]>=k)extrack(k,children[x][0]);
	else extrack(k-true_size[children[x][0]]-exist[x],children[x][1]);
}
int main(){
    for(int i=500004;i>=1;i--)memory[++pool]=i;
    number_data_sets=read();
    while(number_data_sets--) {
    order=read(),x=read();
        switch(order){
            case 1:Insert(x,root);break;
            case 2:{
            	extrack(Rank(x),root);
            	if(isbad(root))rebuild(root);
				break;
			}
            case 3:printf("%d\n",Rank(x));break;
            case 4:printf("%d\n",frank(x));break;
            case 5:printf("%d\n",frank(Rank(x)-1));break;
            case 6:{
            	printf("%d\n",frank(Rank(x+1)));
				break;
			}
        }
    }
}

代码解读】一言不合就把树拍扁成链再重构的方式十分简单好理解,其中运用了手写内存池的方法。注意删除时要删除第k小的数。不能按值删除。

posted @ 2020-01-20 14:42  clockwhite  阅读(245)  评论(1编辑  收藏  举报