首页 > 学院 > 开发设计 > 正文

van Emde Boas 树 数据结构说解

2019-11-06 07:35:13
字体:
来源:转载
供稿:网友

van Emde Boas 树的定义

直观上看,vEB 树保存了一个有序的集合,并支持以 O(lglgn) 的时间复杂度在 vEB 树上进行最小最大值查询、单值存在性查询、单值前驱后继查询、单值插入维护、单值删除维护的数据结构。为了保证时间复杂度,vEB 树上所有操作的时间函数递推式都满足:T(u)≤T(2⌈(lgu)/2⌉)+O(1)

特别的,当 u 为完全平方数时,上式简化为 T(u)≤T(u√)+O(1)

为了满足上述的时间函数递推式,vEB 树上每向下一层迭代,其规模都会由 u 变为 u√(为了方便起见,这里 u 设为完全平方数)。换句话说,vEB 树上规模为 u 的非叶结点拥有 u√ 棵规模为 u√ 的子树,记为 cluster[0 .. u√−1]。其中,把子树从 0 到 u√−1 编号,则编号为 i 的子树保存值 j,当且仅当其父节点的树保存值 i∗u√+j ;换句话说,若以一规模 u 的节点为根的树保存值 k,则其编号为 ⌊k/u√⌋ 的子树保存值 k mod u√

除此之外,vEB 树上的一个节点还保存了一棵子树 summary,在这棵子树中保存值 i,当且仅当其父节点的树中存在值 k 使得 ⌊k/u√⌋ = i。通过这个子树,使得在查询前驱后继时不用单独遍历每棵 cluster 子树,而可以查询 summary 子树来确定某元素的前驱后继位于哪棵 cluster 子树。这样保证了遍历 vEB 树上的每个节点都为 O(1) 的时间复杂度。

对于 vEB 树上的叶节点,其规模必然小于等于 2,无 cluster 子树和 summary 子树。对于 vEB 树上的每个节点,都保存 min 值和 max 值,代表以该节点为根的树上保存的最小值和最大值。这样使得可以用 O(1) 的时间复杂度判断以该节点为根的树未保存值、保存了一个值或保存了两个或以上的值(min = max = NIL 时未保存值,min = max ≠ NIL 时保存一个值,min ≠ max 时保存两个或以上的值)。而规模小于等于 2 的叶节点也可以只把值保存在 min 和 max 上。

在单值插入或删除维护中,若在空 cluster 子树中插入值或删除值使之成为空 cluster 子树时,相应的 summary 子树中也要插入或删除元素,使得进行了两次规模为 u√ 的调用,时间复杂度将会退化成 O(lgn)。解决方法是,使一个节点中 min 保存的值不在其 cluster 子树中出现即可(保存 min 值而不保存 max 值同理)。这样,当在一棵空 cluster 子树上插入元素时,只需修改空 cluster 子节点 min 值和 max 值而不在 cluster 子树上进行调用,并在相应的 summary 子树中插入元素;当在一棵保存了值的 cluster 子树上插入元素时,由于 summary 子树中已有该相应元素,故正常插入即可。同样,删除同理。这样,保证了单值插入和删除的时间复杂度。

对于van Emde Boas 树的定义代码如下:

#include"vector"using namespace std;#define NIL (-1)struct VANEMDEBOAS_TREE{ int max,min,sqr,u; VANEMDEBOAS_TREE *summary; vector<VANEMDEBOAS_TREE *> cluster;};

van Emde Boas 树的最值查询

由于 vEB 树的节点上保存了 min 和 max,故只需 O(1) 查询即可。

对于van Emde Boas 树的最值查询代码如下:

#define VEBT_Maximum(V) (V->max)#define VEBT_Minimum(V) (V->min)

van Emde Boas 树的单元素存在性查询

直接向深层迭代即可。

对于van Emde Boas 树的单元素存在性查询代码如下:

#define VEB_High(V,x) ((int)((x)/(V->sqr)))#define VEB_Index(V,x,y) ((x)*(V->sqr)+(y))#define VEB_Low(V,x) ((x)%(V->sqr))bool VEBT_Member(VANEMDEBOAS_TREE *V,int x){ if(V->min==x||V->max==x) return true; if(V->u<=2) return false; return VEBT_Member(V->cluster[VEB_High(V,x)],VEB_Low(V,x));}

van Emde Boas 树的单元素前驱后继查询

设元素 x,首先判断是否到达叶节点。对于非叶结点的情况,先判断其后继是否在子树 cluster[⌊x/u√⌋] 中。具体方法是取子树 cluster[⌊x/u√⌋] 中的最大值与 x mod u√ 判断大小。若是则继续在子树 cluster[⌊x/u√⌋] 中找 x mod u√ 的后继即可,若否则先在 summary 子树中找 ⌊x/u√⌋ 的后继,记为 succ_cluster,然后直接查询子树 cluster[succ_cluster] 中的最小值返回即可。

对于van Emde Boas 树的单元素后继查询代码如下:

int VEBT_Successor(VANEMDEBOAS_TREE *V,int x){ if(V->u<=2) if(V->max==1&&x==0) return 1; else return NIL; if(V->min!=NIL&&V->min>x) return V->min; int max_low=VEBT_Maximum(V->cluster[VEB_High(V,x)]); if(max_low!=NIL&&max_low>VEB_Low(V,x)) return VEB_Index(V,VEB_High(V,x),VEBT_Successor(V->cluster[VEB_High(V,x)],VEB_Low(V,x))); int succ_cluster=VEBT_Successor(V->summary,VEB_High(V,x)); if(succ_cluster==NIL) return NIL; return VEB_Index(V,succ_cluster,VEBT_Minimum(V->cluster[succ_cluster]));}

查询前驱与查询后继同理,只不过需要注意,若在子树 summary 查询 ⌊x/u√⌋ 的前驱不存在时,需要额外判断是否该节点的 min 为 x 的前驱,这是由于 min 不在子树 summary 和 cluster 出现,而在这之前判断会返回错误前驱的缘故。

对于van Emde Boas 树的单元素前驱查询代码如下:

int VEBT_PRedecessor(VANEMDEBOAS_TREE *V,int x){ if(V->u<=2) if(V->min==0&&x==1) return 0; else return NIL; if(V->max!=NIL&&V->max<x) return V->max; int min_low=VEBT_Minimum(V->cluster[VEB_High(V,x)]); if(min_low!=NIL&&min_low<VEB_Low(V,x)) return VEB_Index(V,VEB_High(V,x),VEBT_Predecessor(V->cluster[VEB_High(V,x)],VEB_Low(V,x))); int pred_cluster=VEBT_Predecessor(V->summary,VEB_High(V,x)); if(pred_cluster==NIL) if(V->min!=NIL&&V->min<x) return V->min; else return NIL; return VEB_Index(V,pred_cluster,VEBT_Maximum(V->cluster[pred_cluster]));}

van Emde Boas 树的单元素插入维护

当子树 cluster[⌊x/u√⌋] 为空时,在子树 summary 中插入 ⌊x/u√⌋ 并修改子树 cluster[⌊x/u√⌋] 的 min 和 max,否则在子树 cluster[⌊x/u√⌋] 中插入 x mod u√。过程中若 x 比 min 小则调换 x 和 min 值继续插入,最后判断是否更新 max 值。

对于van Emde Boas 树的单元素插入维护代码如下:

#define Exchange_Integer(x,y) /{ / int __tmp_x=x; / x=y,y=__tmp_x; /}void VEBT_Insert(VANEMDEBOAS_TREE *V,int x){ if(V->min!=NIL&&V->min!=x) { if(V->min>x) Exchange_Integer(V->min,x); if(V->u>2) { if(VEBT_Minimum(V->cluster[VEB_High(V,x)])==NIL) VEBT_Insert(V->summary,VEB_High(V,x)); VEBT_Insert(V->cluster[VEB_High(V,x)],VEB_Low(V,x)); } if(V->max<x) V->max=x; } else if(V->min!=x) V->max=V->min=x;}

van Emde Boas 树的单元素删除维护

当子树 cluster[⌊x/u√⌋] 中只有一个元素时,在子树 summary 中删除 ⌊x/u√⌋ 并删除子树 cluster[⌊x/u√⌋] 的 min 和 max 值,否则在子树 cluster[⌊x/u√⌋] 中删除 x mod u√。 过程中若 x 等于 min 更新 min 值继续删除新 min 值,最后判断是否更新 max 值。查询新最值的方法是,在子树 summary 中查询最值,然后在相应的子树 cluster 中查询最值,进行运算即可。

对于van Emde Boas 树的单元素删除维护代码如下:

void VEBT_Delete(VANEMDEBOAS_TREE *V,int x){ if(V->max!=V->min&&V->u>2) { if(V->min==x) x=VEB_Index(V,VEBT_Minimum(V->summary),VEBT_Minimum(V->cluster[VEBT_Minimum(V->summary)])), V->min=x; VEBT_Delete(V->cluster[VEB_High(V,x)],VEB_Low(V,x)); if(VEBT_Minimum(V->cluster[VEB_High(V,x)])==NIL) { VEBT_Delete(V->summary,VEB_High(V,x)); if(V->max==x) if(VEBT_Maximum(V->summary)!=NIL) V->max=VEB_Index(V,VEBT_Maximum(V->summary),VEBT_Maximum(V->cluster[VEBT_Maximum(V->summary)])); else V->max=V->min; } else if(V->max==x) V->max=VEB_Index(V,VEB_High(V,x),VEBT_Maximum(V->cluster[VEB_High(V,x)])); } else if(V->max!=V->min&&V->u==2) V->max=V->min=1-x; else if(V->min==x) V->max=V->min=NIL;}

van Emde Boas 树的构建

以上的论述,都只是论述了 u 为完全平方数的情况。在实际应用中,对于一个规模为 u 的节点,有 ⌊u/⌈u√⌉⌋ 棵规模为 ⌈u√⌉ 的 cluster 子树和一棵规模为 ⌈u/⌈u√⌉⌉ 的summary 子树,如果 ⌈u√⌉ 不能整除 u,则还存在一棵编号为 ⌊u/⌈u√⌉⌋,规模为 u mod ⌈u√⌉ 的 cluster 子树。

由于 vEB 树上每个节点的 cluster 子树数目不同,故用朴素的指针数组难以在最简空间下表示。故在笔者的代码中,用到了 STL 中的 vector 容器,以表示不定长数组。由于使用 STL 容器不可避免的会显著地增大时间常数,所以笔者使用 STL 完全处于代码的整洁性考虑,在例如算法竞赛的实际应用中应尽量避免使用这种耗费大量时间的方法。

对于van Emde Boas 树的构建代码如下:

#include"math.h"void VEBT_Create(VANEMDEBOAS_TREE *V,int u){ V->max=V->min=NIL; V->u=u; if(u>2) { V->sqr=(int)sqrt(u); V->sqr=V->sqr*V->sqr<u?V->sqr+1:V->sqr; int x=u/V->sqr; for(int i=0;i<x;i++) V->cluster.push_back(new VANEMDEBOAS_TREE), VEBT_Create(V->cluster[i],V->sqr); if(u%V->sqr>0) V->cluster.push_back(new VANEMDEBOAS_TREE), VEBT_Create(V->cluster[x++],u%V->sqr); V->cluster.push_back(NULL); V->summary=new VANEMDEBOAS_TREE; VEBT_Create(V->summary,x); } else V->summary=NULL;}

van Emde Boas 树的总结

vEB 树虽然是一种高效查询维护的数据结构,但是由于其构建时间耗费较大,故其不宜应用于查询维护次数较少的领域。从整体上看,vEB 树对于维护一个区间的高效性还是值得被应用的。


上一篇:二进制中1的个数

下一篇:string_compress

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表