平衡树介绍
【定义】 平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。[摘自百度]
【用途】 平衡树(这里都是不持久化的基础版本)是一个用于解决动态序列中,排名,前驱,后继的一种数据结构。平均效率接近logn
【关键词】数据结构 数 动态序列 logn
【例题】您需要写一种数据结构,来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于xx,且最大的数)
求x的后继(后继定义为大于xx,且最小的数)
Treap
【简介】treap,即tree+heap。是我所接触到最早的可以实现平衡树的结构。主要用旋转(spin)来进行操作。
【代码】
#include
#include
#include
#include
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[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 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
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]=k)node_now=children[node_now][0];
else if(size_node[children[node_now][0]]+1
【代码解读】通过split将一颗树分成>k与<=k的两棵树,添加就直接当做添加一棵树,删除分出三个再合并两个就是了。在不断merge的过程中也在不断替换根,使其不怎么退化。
权值线段树
【简介】线段树,一个大家所熟知的老结构。这里是把它当桶来实现平衡树。
【代码】
#include
#include
#include
#include
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>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
#include
#include
#include
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=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小的数。不能按值删除。