An Efficient Index-Based Approach to Distributed Set Reachability on Small-World Graphs

Introduction

有向图中的集合可达性问题是图论和应用中最基本的问题之一[1]。 给定有向图 G 中的两组源顶点 S 和目标顶点 T,集合可达性查询旨在获取 s ∈ S 和 t ∈ T 的所有可达对 (s,t),其中 s 可以到达 G 中的 t。 可达性查询是许多基于图的应用程序和算法的构建块。 例如,社交网络社区之间的依赖性分析本质上涉及大量源顶点和目标顶点之间的可达性检查[1]。 此外,许多图分析算法[2],例如图中心性计算[3]和k跳邻域检测[4],也需要通过探索给定源顶点的所有可达顶点和边来系统地遍历整个数据图。

在很多实际场景中,都存在大量的大规模图。 例如,Facebook 的月活跃用户超过 25 亿。1 对于这些海量图,单台机器很难同时满足效率和内存的要求。 为了解决这些问题,有必要在分布式环境中设计理想的图处理技术,以提高效率和可扩展性[5]。 现有的分布式图处理平台,例如Pregel [6],已经被提出来处理海量图上的图问题。 具体来说,类Pregel系统是基于批量同步并行(BSP)模型设计的,如图1所示。这里,首先在每个进程中并行执行本地计算,然后在不同进程之间进行消息交换。 另外,类Pregel系统采用屏障同步机制来保证所有进程之间的消息同步,当没有消息传输时整个程序将被终止。 为了简单起见,每个进程对应一个分区。 考虑到 BSP 计算模型最适合迭代图计算 [5],我们的目标是在此类系统中实现高效的分布式集合可达性查询。

挑战。 在类似 Pregel 的系统中,数据图首先被划分为多个边和顶点不相交的子图,然后分布到相应的分区。 然后,设置的可达性查询将在所有子图中并行执行。 在分区数据图上有效处理此类任务的关键目标是:(1)避免每个分区内的冗余计算[1],(2)最小化不同分区之间的消息交换轮次[1],以及(3)改进 查询的并行性。

尽管许多研究工作致力于解决可达性查询[5]、[7]、[8]、[9]、[10]、[11]、[12]、[13]、[14],但这些方法不能 有效解决问题。 主要原因是大多数可达性查询的索引技术都部署在集中式环境中,很难扩展到分布式环境,因此只能解决上述第一个目标。 更重要的是,在处理否定查询时,2大多数方法都不能保证查询性能,因为它们必须遍历整个数据图并且不允许提前终止查询过程[15]。 考虑到给定的可达性查询集合中也存在许多不可达对,实际的查询性能将受到严重影响。

据我们所知,最先进的方法 DSR 通过构建边界图来处理分布式集可达性查询,该边界图收集并存储每个分区的边界顶点对的可达性信息。 基于这个索引,可以在不涉及其他分区的情况下解决每个顶点对的查询,从而有助于实现第二个目标。 然而,需要部署额外的中心化索引来降低第(1)点中提到的本地计算时间。 此外,基于边界图的索引被复制到所有分区,以减少第(2)点中提到的通信开销,从而不可避免地产生巨大的空间开销。 更重要的是,DSR无法有效解决否定查询,因为它仍然需要遍历整个数据图[15],从而阻碍了查询效率。 另外,DSR仅执行从源顶点到目标顶点的前向遍历查询,无法实现前面提到的高度并行性。

我们的方法。 经典的2跳索引[8]可以通过将任意两个可达顶点构造为1跳邻居或2跳邻居来有效地处理两种可达性查询。 然而,在海量图上构建 2 跳索引非常耗时。 为了提高查询效率,我们将 2 跳邻居信息扩展到分布式环境,并开发了一种称为多级 2 跳(ML2hop)的新型分布式索引方案。 具体来说,根据分布式环境中采用的分区策略定义了两种顶点,并进一步设计了两级索引结构。 与2跳索引相比,ML2hop的第一级可以在每个分区中并行执行,从而提高了构建效率。 此外,ML2hop的第二层是基于部分顶点构建的,这有助于减少空间开销。 基于ML2hop,提出了一种称为MLQA的双向查询算法,可以有效地解决正向和负向查询。 一般来说,MLQA 具有以下三个重要属性。

计算成本低。 基于每个分区中的第一级ML2hop,MLQA可以有效限制路径遍历的轮数,从而避免冗余计算。

沟通成本低。 基于ML2hop的第二级,MLQA仅涉及不同分区之间的单轮消息交换,这有助于减少通信开销。

高并行度。 MLQA基于ML2hop,采用双向查询策略,可以同时激活源顶点和目标顶点中的所有元素来执行消息传播,从而提高并行性。

此外,我们从理论上证明了任何集合可达性查询的查询轮次上限,这有效地减少了遍历数据图时的大量计算。

贡献。 我们的主要贡献总结如下。

我们提出了一种新的分布式索引 ML2hop,它可以在类似 Pregel 的系统中有效地构建,以限制单轮内所有不同分区之间的消息交换。

我们提出了一种双向查询算法 MLQA,采用 ML2hop 来回答分布式集可达性查询。

我们从理论上证明了基于 ML2hop 的集合可达性查询的查询轮次上限,从而避免了每个分区内的冗余本地计算并提高了查询性能。

我们在几个大规模实词图数据集上评估了 MLQA 的性能。 据报道,MLQA 的性能显着优于最先进的算法,加速速度高达两个数量级。

路线图。 本文的其余部分安排如下。 在第 2 节中,我们简要回顾了有关可达性查询和分布式系统的相关工作。 第 3 节定义了集合可达性问题,并介绍了现有的可达性查询的经典索引技术。 第 4 节提出了 ML2hop 索引结构和基于 ML2hop 的方法。 最后,第 5 节评估 ML2hop 的性能,第 6 节总结工作。

Related Work

2.1 中心化方法
在[8]中,科恩等人。 提出了经典的2跳索引来进行可达性查询,该索引也被应用于解决跨度可达性查询问题[16]。 金等人。 提出了可达性主干网,使得任何具有 ϵ 跳数的顶点对都可以通过本地搜索访问主干网,并且它们相应的访问顶点在主干网中连接。 程等人。 [7]通过多次折叠原始图的初始拓扑结构,设计了一种新颖的拓扑折叠(TF)结构。 在[12]中,周等人。 先后构造了传递约简(TR)图和等价约简(ER)图,可以有效消除冗余路径遍历。 在[11]中,开发了一种基于索引的算法来解决大型图上的标签约束可达性(LCR)查询。 该方法基于一种新的基于地标的索引,该索引选择少量的地标顶点来存储其可到达的顶点。

一般来说,这些方法中的大多数旨在解决单源、单目标可达性查询。 在处理集合可达性查询时,这些方法总是需要首先将其分解为一系列单个可达性查询,然后才能获得最终的查询结果。 显然,这个过程效率很低,因为在图的路径遍历过程中存在很多重复计算。 另外,上述方法均不能直接部署到分布式环境中。

2.2 分布式方法
范等人。 [9]提出了具有性能保证的单源、单目标可达性查询的分布式算法。 同样,阿米娜等人。 [17]采用新的算法技术来回答标签约束的分布式集可达性查询。 这两种方法都需要从每个分区收集路径消息并构建依赖图以减少冗余计算。 然而,这种索引是根据给定的顶点对动态生成的,进一步采用简单的BFS或DFS策略来解决单个分区上的最终可达性计算。 因此,对这两种方法的讨论不属于我们的研究范围。

张等人。 [5]开发了一种顶点标签方案,称为PVL,它可以在分布式环境中高效构建。 该方法为每个顶点构建两个标签集,并提出多种剪枝策略来提高查询效率。 然而PVL缺乏性能保证,无法有效处理负查询。 此外,由于消息传输无法及时终止,剪枝策略的效果也会受到很大影响。 在[1]中,Gurajada 等人。 提出了一种基于图的索引结构来解决分布式集合可达性查询问题,这将在 3.3 节中讨论。

Preliminary

在本节中,我们首先定义集合可达性查询问题。 然后,我们说明了 2 跳标记方法和基于边界图的索引。 最后,我们介绍类 Pregel 系统。 表 1 总结了本文中常用的符号。

3.1 问题定义


令 G=(V,E) 为具有顶点集 V 和边集 E 的有向图,其中 n=|V| 且 m=|E| 分别表示 G 中的顶点数和边数。 对于每个顶点 v ∈ V,v 的内邻居和外邻居集表示为 Nin(v)={u|(u,v)εE} 和 Nout(v)={w|(v,w) ) ε E} ,分别。 另外,deg(v)=|Nin(v)|+|Nout(v)| 表示 v 的入度和出度之和。

可达性查询。 给定两个顶点 s,t∈V,pth(s,t)={v1,…,vk} 是一条路径,其中 s=v1, t=vk 且每条边 e(vi,vi+1)εE 且 i∈ [1,k−1]。 如果存在至少一条从 s 到 t 的路径,则两个顶点 s 和 t 是可达的,表示为 q(s,t)=true。 否则,这两个顶点将无法到达,并且 q(s,t)=false。 这两种查询分别被定义为肯定查询和否定查询。

图分区。 为了处理分布式环境中海量图的可达性查询q(s,t),数据图被划分为多个顶点不相交的子图,并随后存储在相应的分区中[18]、[19]、[20] 。 分区数据图由顶点导出的子图和一组切割边组成。 具体来说,Gi(Vi,Ei) 是 G(V,E) 的顶点导出子图,使得 Vi∈V 且 Ei={(u,v)|u∈Vi 且 v∈Vi}。 Ecut 表示跨越不同分区的一组切削刃。

A。 它被分为三个子图G1、G2和G3,它们被放置在相应的分区中。 这里,v3的内邻居和外邻居集合分别是Nin(v3)={v10}和Nout(v3)={v2,v4}。 给定一个顶点对 (v17,v3),有两条路径,包括 pth(v17,v3)={v17,v10,v3} 和 pth(v17,v3)={v17,v16,v15,v10,v3}。 因此,v17 可以到达 v3,并且我们有 q(v17,v3)=true。
设置可达性查询。 给定图 G、两组源顶点 S 和目标顶点 T,集合可达性查询表示为 SR(S,T),返回所有可达顶点对 (s,t),其中 s∈S、t∈T 和 q (s,t)=真。 除非另有说明,集合可达性查询的问题默认是在分布式环境中讨论的。

示例 2. 给定源顶点集合 S={v3,v5} 和目标顶点 T={v16,v19},集合可达性查询返回两个可达顶点对,即 (v3,v19) 和 (v5,v19)。

3.2 用于可达性查询的 2 跳标记


为了处理可达性查询,2 跳标记方法 [8] 是基于标记函数 L 构建的,该函数将每个顶点 v∈V 映射到标记集 Lout(v) 和 Lin(v)。 具体来说,对于每个顶点 u∈Lout(v) (或 Lin(v)),我们有 q(v,u)=true (或 q(u,v)=true)。 形式上,如果 L 满足下面的 2 跳覆盖约束,则 {Lout(v),Lin(v)|v∈V} 是 2 跳标记。

定义 1(2 跳索引)。

如果任何可达顶点对 (s,t) 满足 Lout(s)⋂Lin(t)≠∅,则标记函数 L 满足 2 跳覆盖约束。 因此,对于每个不可到达的顶点对 (s,t),我们有 Lout(s)⋂Lin(t)=∅。 请注意,当数据图中所有顶点对的可达性都可以通过该索引解决时,2跳索引是完整的。
例 3. 考虑到图 2a 中的 v10∈Lout(v16) 和 v10∈Lin(v2),我们有 q(v16,v2)=true,因为 Lout(v16)⋂Lin(v2)={v10}。
对于完整的2跳标记L,基于L的可达性查询定义为
Query(s,t,L)={true,false,如果Lout(s)⋂Lin(t)≠∅,否则。 (1)
查看源代码右键单击图以查看 MathML 和其他功能。

假设每个标签集中的2跳标签元素按照key的升序存储。 求解q(s,t)的过程需要检索Lout(s)和Lin(t)中的元素,时间复杂度为O(|Lout(s)|+|Lin(t)|)。 给定一个顶点对(u,v),当Lout(v)⋂Lin(u)=∅时,可以将标签元素u添加到Lout(v)中。 类似地,当Lin(v)⋂Lout(u)=∅时,可以将其添加到Lin(v)中。 然而,当两个顶点放置在不同的分区中时,获得标签集之间的交集并不容易。 这主要是因为标签集放置在不同的内存空间中。 以顶点对(v3,v6)为例。 由于Lout(v3)和Lin(v6)放置在G1和G2中,而G1和G2存储在不同的内存空间中,因此很难获取Lout(v3)和Lin(v6)的交集。

对于集合可达性查询SR(S,T),它可以分解为一系列可达性查询q(s,t),其中s∈S且t∈T。 然后,根据式(1)可以很容易地确定q(s,t)的查询结果。 需要注意的是,2跳索引解决大规模S和T的集合可达性查询问题非常耗时,因为所有查询都需要顺序执行。

排名策略。 排序策略已被广泛用于减少 2 跳 [21]、[22] 的索引时间。 考虑到度数较大的顶点可能与许多其他顶点有很强的连接[22],典型的排序策略是构造一个函数 r(v),该函数优先考虑度数较大的顶点,并利用原始 ID 来打破联系, 有助于加快指数建设。 对于两个给定的顶点 u 和 v,我们有 r(u)>r(v) 如果

deg(u)>deg(v) 或

deg(u)=deg(v)且ID(u)>ID(v)。

3.3 最先进的算法


处理分布式集合可达性查询 SR(S,T) 的一种简单方法是使用 s∈S 和 t∈T 来处理一系列单独的可达性查询 q(s,t)。 然而,这种方法无法有效地处理海量图中的负查询,因为它需要遍历整个数据图直到确认最终结果。 更重要的是,这种方法不能重用任何中间消息,从而导致冗余计算。

DSR[1]构建了特定于分区的边界图作为静态可达性索引,并将其存储在每个分区中。 基于这个索引,p(s)≠p(t)的可达性查询q(s,t)可以在两个不同的分区之间解决[1],从而降低通信成本。 尽管 DSR 提供了比简单方法更可行的解决方案,但它并非没有限制效率和可扩展性的缺点。

首先,需要采用现有的集中式索引技术来减少每个分区的本地计算,这会引入额外的空间开销。

其次,它不能有效地处理负查询对,因为大多数现有的中心化方案缺乏对此类查询的性能保证。

第三,由于DSR需要利用原始数据图和静态边界图索引来解决给定的可达性查询,因此可能会产生巨大的空间开销。

第四,它无法为每个设置的可达性查询提供计算轮数的理论上限。

3.4 类预凝胶图处理系统


Pregel-like系统是基于第1节中介绍的BSP模型设计的。给定一个数据图,所有顶点首先被划分为不同的分区,然后,Pregel-like系统以顶点作为执行单元执行一系列任务。 具体来说,用户定义的函数compute()是每个顶点在迭代序列中执行计算任务的关键,称为超级步。 在每个超级步骤中,每个活动顶点都会接收消息并调用compute()来执行用户指定的任务。 此外,当所有顶点都投票停止并且没有消息传输时,程序将终止。

在本文中,我们在 Blogel [23] 系统上实现了我们的方法。 主要原因在于Blogel采用了VB(Vertex and Block)计算模型,允许在称为块的粗粒度图形单元中顺序执行内存中算法,而无需任何消息传递。 这使得 Blogel 系统更适合可达性查询,因为它可以有效地处理倾斜度分布和高密度的数据图。

Multi-Level 2-Hop Labeling Index

在本节中,首先介绍了对分布式环境中可达路径的观察,这激励我们使用本文设计的 ML2hop 进行实验。 然后,我们详细描述ML2hop索引。 与边界图索引相比,基于ML2hop的查询技术提供了更高的效率,因为它大大减少了本地计算并提高了查询的并行性。 此外,我们提出了一种索引构建算法,并设计了一种基于 ML2hop 的查询算法来更好地回答集可达性查询。

观察。 在分布式环境中定义了两种有向路径,其中内部路径表示该路径中的每个顶点都放置在同一个分区中,边界路径表示所有顶点放置在至少两个不同的分区中。 如图2a所示,pth(v16,v10)={v16,v15,v10}和pth(v3,v5)={v3,v4,v5}分别是内部路径和边界路径。

对于每个 p(s)≠p(t) 的顶点对 (s,t),可达性查询 q(s,t) 可以基于 3.2 节中介绍的完整的 2 跳标记方案直接求解。 据观察,相应的路径 pth(s,t) 可以分解为三部分 pth(s,vn)、pth(vn,vm) 和 pth(vm,t),其中 pth(s,vn) 和 pth (vm,t) 是内部路径,pth(vn,vm) 是边界路径。 例如,pth(v10,v5)可以分解为pth(v10,v3)、pth(v4,v5)和pth(v3,v4)。 鉴于此,我们寻求将完整的2跳索引分为内部部分和边界部分,以减少索引构建的时间开销。

4.1 ML2hop结构


3.2节中介绍的2跳索引由于其令人满意的查询性能而被广泛应用于可达性查询中。 然而,对于大型图构建该索引非常耗时。 为了解决这个问题,我们构建了一个基于图分区和新排序策略的多级索引 ML2hop。 与2跳索引相比,ML2hop的构建效率更高,同时基于ML2hop的查询算法具有优异的查询效率。

4.1.1 新的排名策略


如上所述,它通过采用理想的排序策略有助于降低 2 跳索引的空间成本。 为了说明我们的新排名技术,我们首先定义两种顶点,如下所示。

定义 2(边界顶点和内部顶点)。

设VB和VI分别表示G(V,E)的两组边界顶点和内部顶点,fbv为每个顶点v的符号。那么,我们规定性质如下。
VB 和 VI 是两个不相交的子集,其中 VB⋂VI=∅ 且 V=VB∪VI。

对于 fbu=1 的每个顶点 u∈VB,至少存在一条边 (u,v)∈E,且 u 和 v 位于不同的分区。

对于 fbw=0 的每个顶点 w∈VI,所有邻居都位于放置 w 的分区中。

基于边界顶点和内部顶点的定义,我们进一步引入一种新的排序策略。 对于每个顶点对 (u,v),我们有 r(u)>r(v) 如果

fbu>fbv 或

fbu=fbv 且 deg(u)>deg(v) 或

fbu=fbv 且 deg(u)=deg(v) 且 ID(u)>ID(v)。

例4 如图2b所示,边界顶点集合为{v3,v4,v6,v7,v11,v12,v17,v21},其余顶点属于内部顶点。 这里,图2a中的所有顶点都根据提出的新排序技术进行排序,新顺序为v12,v17,v6,v3,v21,v7,v4,v11,v22,v19,v10,v8,v5,v20 、v16、v15、v14、v13、v9、v2、v23、v18、v1。
基于新的排序技术,对于 ML2hop 中 u∈L(v) 的每个顶点对 (u,v),我们规定 r(u)≥r(v)。 进一步规定,每个顶点将被添加到自己的内标签集和外标签集中,从而简化了查询算法。

一般来说,ML2hop是一种多级索引结构,分别与内部顶点和边界顶点相关联。 这里,可以利用并行构建在每个分区中的第一级ML2hop(称为内部2跳索引)来有效减少在构建第二级ML2hop(称为边界2跳)期间图路径的冗余遍历 指数)。

以图2a的顶点对(v7,v21)为例。 假设我们首先构建ML2hop的第二层。 显然,所有边界顶点的消息都在从v7到v21的所有内部路径之间传输,例如pth(v7,v21)={v7,v8,v14,v22,v21}和pth(v7,v21)={v7, v8,v9,v22,v21},不可避免地导致路径遍历的计算量过多。 相比之下,如果提前构建第一层ML2hop,我们可以直接从v7向v21发送消息,以避免图路径的冗余遍历,这可以显着减少索引时间。

4.1.2 内部2跳索引


内部2跳索引,表示为LI,是所有内部顶点的标签的聚合。 具体来说,我们基于内部顶点的概念定义给定子图的内部 2 跳索引,如下所示。

定义 3(内部 2 跳索引)。

对于每个子图Gi(Vi,Ei),Gi的内部2跳索引是一个标记方案,其中每个内部顶点v∈Vi构造Gi的2跳索引。 对于每个标签 u∈Lout(v) 或 Lin(v),我们有 p(u)=p(v)。


定理 1.

给定一个子图 G* 和一个内部顶点对 (u,v),其中 p(u)=p(v) 且 r(u)>r(v),如果 u 可以在 G* 中到达 v,则存在 至少有一个顶点 w 满足
r(w)≥max{r(u),r(v)} 且 w∈G* 且

wεLout(u) 和 wεLin(v)。

证明。 根据定义 1,对于每个可达顶点对 (u,v),我们有 Lout(u)⋂Lin(v)≠∅。 采用新的排序策略来减少索引时间。 对于每个标签 w∈Lout(u)⋂Lin(v),我们有 r(w)≥max{r(u),r(v)}。
每个子图中内部2跳索引的构造可以并行执行,因为该过程是独立的,涉及的不同分区之间没有消息交换。 在构建每个子图的完整内部 2 跳索引 LI 后,对于每个内部顶点对 (u,v) 且 p(u)=p(v),我们可以得出 Query(u,v,LI)=true if Lout(u)⋂Lin(v)≠∅。 然而,当 Lout(u)⋂Lin(v)=∅ 时,我们不能得到 Query(u,v,LI)=false,因为数据图中可能存在从 u 到 v 的边界路径。

TABLE 2 Inner 2-Hop Index for All Inner Vertices in Fig. 2a

显示了图2a中所有内部顶点的内部2跳索引,其中inL和outL分别表示内标签和外标签。 对于可达性查询 q(v10,v2),我们有 q(v10,v2)=true,因为 Lout(v10)⋂Lin(v2)={v3}。
请注意,每个边界顶点的内部 2 跳索引也将在我们的方法中构建,因为同一子图中的每个顶点将被激活以构建此索引。 索引构建算法的更多细节如算法2所示。值得一提的是,每个边界顶点的内部2跳索引是高效构建ML2hop二级索引的关键。 详细说明如下所示。

4.1.3 边界2跳索引


边界2跳索引,记为LB,是所有边界顶点的完整2跳索引。 与内部2跳索引相比,在构建边界2跳索引的过程中,为了保证可达信息的准确性,不可避免地要遍历整个图。 为了提高索引构建的效率,设计了内路径来减少图遍历的计算量。 内部路径的定义如下。

定义 4(内部路径)。 给定子图Gi(Vi,Ei),Eip是内部路径的集合,其中每条边e(u,v)∈Eip满足
q(u,v)=true 其中 fbu=1 且 fbv=1,

max{r(t)|t∈pth(u,v)}=max{r(u),r(v)}。

Eip的提出是为了加速边界2跳索引的构建。 具体来说,它收集同一子图中每个可达边界顶点对的可达性信息,这加快了边界2跳索引构建过程中的图遍历速度。 需要注意的是,Eip可以基于内部2跳索引快速并行构建。

Fig. 3.

A boundary graph GB.

显示了具有 8 个顶点和 9 条边的边界图 GB(VB,EB),其中虚线和红线分别表示切割边和内部路径。 以G1为例。 由于 q(v17,v3)=true 并且 r(v17) 是属于 pth(v17,v3) 的顶点的所有排序值中最大的,因此构造内部路径 e(v17,v3)。 它通过遍历内部路径 pth(v17,v3)={v17,v10,v3} 而不是 pth(v17,v3)={v17,v16,v15,v10,v3} 来帮助减少索引时间。 类似地,其余内部路径,包括 e(v4,v6)、e(v4,v12) 和 e(v7,v21) 也已构建。


定义 5(边界 2 跳索引)。

对于给定的边界图GB(VB,EB),其中VB是边界顶点的集合,EB=Ecut∪Eip是切边和内部路径的并集,边界2跳索引是完整的2跳 所有边界顶点的索引。
定理 2. 给定一个边界图 GB,对于每个可达顶点对 (u,v),其中 u,v∈GB 且 r(u)>r(v),至少存在一个满足以下条件的顶点 w
r(w)≥max{r(u),r(v)} 且

wεLout(u) 和 wεLin(v)。

证明。 证明与定理1类似。


定义 6(ML2hop)。

给定一个有 k 个子图 {Gj}j=1,.,k 的直接图 G(V=VB∪VI,E),ML2hop 是一种标记方案,其中每个顶点 v∈V 与 Lin(v) 和 Lout 相关联 (v),从而满足以下特征:
对于每个标签 u∈Lin(v) (或 Lout(v)),其中 v∈VI,我们有 r(u)≥r(v),p(u)=p(v) 和 q(u,v)= true(或 q(v,u)=true)。

对于每个标签 w∈Lin(z) (或 Lout(z)),其中 z∈VB,我们有 r(w)≥r(z) 且 q(w,z)=true (或 q(z,w)= 真的)。

对于任何一对内部顶点 (u,v),且 p(u)=p(v),如果 u 可以到达 v,则 Q(u,v,LI) 返回 true。

对于任何一对边界顶点 (w,z),如果 w 可以到达 z,则 Q(w,z,LB) 返回 true。 否则,返回 false。

空间复杂性。 假设 |VI,i| 表示子图 Gi(Vi,Ei) 中内部顶点的数量。 对于每个内部顶点,它将最多记录 |Vi|−1 个顶点。 类似地,对于边界图GB(VB,EB)中的每个顶点,它将最多存储|VB|-1个顶点。 因此,最坏情况下的空间复杂度为O(Σki=1|VI,i|⋅|Vi|+|VB|2)。

4.2 ML2hop构建


ML2hop 构建的整个框架如算法 1 所示,该算法将一组子图 {G0,..,Gk} 和一个排序函数 r 作为输入,然后为每个顶点 v 输出 Lin(v) 和 Lout(v) 五、

算法1. ML2hop构建算法


输入:一组子图 G(V,E)={G0,..,Gk},一个排序函数 r(⋅)。

输出:每个顶点 v∈V 的 Lin(v) 和 Lout(v)。

1
根据排序策略对顶点重新排序

2
执行算法2并行构造内部2跳索引

3
并行执行算法3

4
每个边界顶点vb并行清除内部2跳索引的标签

5
将边界图 GB(VB,EB=Ecut∪Eip) 复制到单个分区

6
执行算法2构造边界2跳索引

首先,我们根据排序策略对顶点重新排序。 其次,在每个子图中并行构建内部 2 跳索引。 第三,Eip是基于每个子图Gi对应的内部2跳索引并行构建的。 最后,将新的边界图GB(VB,EB=Ecut∪Eip)复制到单个分区,并构造GB的边界2跳索引。

4.2.1 指数构建算法


为了有效地构建内部 2 跳索引,我们提出了 ICA 算法,该算法顺序激活顶点并执行修剪的 BFS 策略。 伪代码如算法2所示。请注意,顶点顺序取决于所采用的排序策略。 首先,将活动顶点 v 添加到 Lin(v) 和 Lout(v) 中(第 2 行)。 然后,依次执行前向和后向遍历(第3行和第4行),分别更新每个顶点的内标签集和外标签集。 最后,将插入的新索引条目添加到内标签集合(或外标签集合)中。 因此,提出了两种剪枝策略来加速索引构建的效率,如下所示。

策略 1. 如果顶点 w 的索引项已在前几轮中计算过,则跳过该顶点 w(第 12 行)。

策略 2. 如果现有索引元素已经可以报告从 vk 到 adjV 的可达性(第 14 行),则修剪新索引条目。

算法2. 索引构建算法(ICA)


输入:直接图 G(V,E)。

输出:Ln=⋃i=1,…,n{Lout(i)∪Lin(i)}。

1
foreach k=1,2,…,n 做

2
Lout(vk).insert(vk); 林(vk).插入(vk);

3
L*k = PrunedBFS (G.adjList, vk, Lk−1, 1)

4
Lk = PrunedBFS (G.adjListr, vk, L*k, 0)

5
返回值

6
7
procedure PruneBFS (AdjacentList Adj, 顶点 vk, 索引 Lk−1, int flag)

8
队列Q={vk};

9
当 Q≠∅ 时

10
v=Q.pop()

11
foreach 顶点 adjV ∈ Adj[v] 做

12
如果 adjV ∈ [v0,vk−1] 则

13
继续; ▹修剪策略1

14
如果 Query(vk, adjV, flag) = true 那么

15
继续; ▹修剪策略2

16
IndexUpdate(adjV, vk, 标志)

17 号
Q.插入(adjV)

18
返回Lk

19
20
过程 IndexUpdate(顶点 u,顶点 v,int 标志)

21
当flag=1时Ls=Lout(u)

22
当 flag=0 时 Ls = Lin(u)

23
ls.insert(v) 按升序排列

定理3.基于ICA算法建立的索引是一个完整的2跳标记方案。


证明。 对于给定的数据图G(V,E),ICA对所有顶点进行前向和后向遍历,并构建2跳索引。 对于每个可达顶点对 (u,v),我们有 Lout(u)⋂Lin(v)≠∅。 根据定义1,可以证明基于ICA算法构建的索引是一个完整的2跳标记方案。


定理 4. ICA 的时间复杂度为 O(δ2⋅m),其中 δ 表示图中所有顶点的最大标签大小。


证明。 给定一个有向图 G={V,E},对于每个活动顶点 v∈V,一个新的标签条目 u 将被记录在 ML2hop(第 16 行)中,并扩展到其邻居(第 17 行)。 执行Query()函数来决定是否更新标签(第14行),时间复杂度为O(δ)。 在最坏的情况下,该函数的调用次数为 Σv∈VΣr∈L(v)|N(v)|≤δ⋅m。 因此,ICA的时间复杂度为O(δ2⋅m)。

4.2.2 路径构建算法


如上所述,我们引入内部路径来加速边界2跳索引构建。 PBA的伪代码如算法3所示。具体来说,PBA输入一组子图{G0,...,Gk}以及每个子图的完整2跳索引。 然后,它输出每个子图中的内部路径。 对于每个边界顶点 v∈V 和标签 w∈Lout(v) (和 w∈Lin(v)),PBA 构建内部路径 e(v,w) (和 e(w,v))。

在建立了内部路径集合Eip之后,很容易构造边界图GB(VB,EB=Ecut∪Eip),因为VB和Ecut都属于原始数据图。 然后,在边界图上重新执行ICA来构建边界2跳索引,并且在没有消息传输时ML2hop索引构建也完成。 我们进一步说明ICA如何构造图3中边界图的边界2跳索引如下。

算法3.路径构建算法(PBA)


输入:有向图 G(V,E) 和 G 的完整 2 跳索引。

输出:一组内部路径Eip。

1
初始化一组内部路径Eip

2
foreach边界顶点v∈V do

3
foreach 标签 w∈Lout(v) do

4
将 e(v,w) 添加到 Eip

5
foreach 标签 u∈Lin(v) do

6
将 e(u,v) 添加到 Eip

7
返回EIP

实施例7.在表3中

,每一行对应一个顶点的部分标签,而每一列记录有关每个超级步的增量标签。 最后两列记录的数据是图3中边界图的完整2跳索引。
当superstep=0时,v12首先被激活并添加到Lin(v12)和Lout(v12)中。 然后依次执行前向和后向遍历来更新其他顶点的索引。 类似地,依次激活剩余的顶点以执行相同的操作。 当 superstep=8 时,程序终止,因为没有活动顶点。

这里,图2a所示的G的ML2hop索引是表2所示的内部2跳索引和表3所示的边界2跳索引的组合。

4.3 基于ML2hop的查询算法


基于ML2hop索引,每组可达性查询都可以在没有原始数据图的情况下得到回答。 然而,执行前向遍历(或后向遍历)操作来回答可达性查询是不合适的。 以图3中的顶点对(v4,v21)为例。 根据表3所示的边界2跳索引,我们有q(v4,v21)=true,即Lout(v4)⋂Lin(v21)={v6}。 原则上,当从v4到v21执行前向遍历操作时,由于v21∉Lout(v6),v4的消息无法从v6传输到v21。 因此,我们提出了一种采用ML2hop来回答集合可达性查询的双向查询算法MLQA。 具体来说,我们首先将所有顶点对初始化为“不可达”。 当查询过程完成后,可达顶点对的状态将被修改为“可达”。 其余顶点对仍然“无法到达”。 MLQA的整个过程也是以顶点为执行单元,总共三个超级步骤来执行。 伪代码如算法 4 所示。

算法4.MLQ算法(MLQA)


输入:数据图的ML2hop索引、一组源顶点S、一组目标顶点T、minrs、minrd。

输出:所有可到达的对。

1
foreach 顶点 v∈V do

2
如果 superstep=0 则

3
如果 v ∈ S 则

4
路由(v).插入(v)

5
将 〈1,v〉 发送到每个顶点 w∈Lout(v),其中 p(v)=p(w)

6
如果 v ∈ T 则

7
凛(v).插入(v)

8
将 〈0,v〉 发送到每个顶点 z∈Lin(v),其中 p(v)=p(z)

9
否则如果 superstep=1 那么

10
foreach msg=〈标签,m〉做

11
MessageReceive(标签, m);

12
如果 v 是边界顶点那么

13
foreach vOut∈Rout(v) 那么

14
发送 msg1=〈1,vOut〉 到每个顶点 w∈Lout(v),其中 r(w)≥minrd 且 p(v)≠p(w)

15
foreach vIn∈Rin(v) 做

16
发送 msg0=〈0,vIn〉 到每个顶点 z∈Lin(v),其中 r(z)≥minrs 且 p(v)≠p(z)

17 号
别的

18
foreach msg=〈标签,m〉做

19
MessageReceive(标签, m);

20
如果 Rout(v)≠∅ 且 Rin(v)≠∅ 那么

21
返回Rout(v)×Rin(v)

22
函数 MessageReceive(int flag, int vid)

23
如果标志=0则

24
凛(v).插入(vid)

25
别的

26
路由(v).插入(vid)

当 superstep=0 时,MLQA 对 S 和 T 中的每个顶点执行两个操作。具体来说,如果 v∈S,MLQA 会将 v 添加到 Rout(v) 中,并将消息 msg1=〈1,v〉 发送到 Lout( 五)。 类似地,如果 v∈T,MLQA 将 v 插入到 Rin(v) 中,并将消息 msg0=〈0,v〉 发送到 Lin(v) 中的所有顶点。 (Rout(v) 和 Rin(v) 用于接收来自邻居的消息。)请注意,对于从 v 接收消息的每个顶点 w,它应该满足以下属性:

w∈Lout(v) 且 p(w)=p(v);

w∈Lin(v) 且 p(w)=p(v)。

同样,当 superstep=1 时,MLQA 对 Rout().size()>0 或 Rin().size()>0 的所有边界顶点进行两次操作。 具体来说,对于每个活动顶点 v∈VB,MLQA 根据收到的消息更新 Rout(v) 和 Rin(v)。 然后,Rout(v)和Rin(v)中的所有消息分别传输到Lout(v)和Lin(v)中的顶点。 同时,对于从v接收消息的每个顶点z,它应该满足如下属性:

z∈Lout(v),r(z)≥minrd 且 p(z)≠p(v);

z∈Lin(v),r(z)≥minrs 且 p(z)≠p(v)。

在后续的超级步骤中,当边界顶点 v 接收到消息时,MLQA 进一步更新 Rout(v) 和 Rin(v)。 然后,所有顶点返回查询结果Rout()×Rin(),其中Rout(⋅)≠∅且Rin(⋅)≠∅。

复杂。 这里,我们只考虑每个顶点都包含在源顶点和目标顶点集合中的最坏情况。 给定完整的ML2hop索引G={V,E},每个活动顶点v∈V执行MessageReceive()来更新接收到的消息集合,时间复杂度为O(1)。 显然,该操作调用的次数为: Σv∈V(|Lin(v)|+|Lout(v)|)=|L(v)|。 因此,MLQA在最坏情况下的时间复杂度为O(|L(v)|)。

定理5.对于任意集合的可达性查询SR(S,T),最多需要3个超步才能得到最终的查询结果。


证明。 为了评估MLQA的正确性,我们有Iter=Lout(s)⋂Lin(t),然后分析单个可达性查询q(s,t)的查询过程,可以分为以下三种情况。
情况1:s,t∈VB。 如果s和t都是边界顶点,我们可以直接根据边界2跳索引得到查询结果,如下所示。

∃m∈Iter 且 p(s)=p(t)=p(m)。 当 superstep=0 时,MLQA 将顶点 s 和 t 分别发送到 Rout(m) 和 Rin(m)(第 5 行和第 8 行)。 那么当superstep=1时,可以在m中得到q(s,t)的查询结果。 请注意,此操作不涉及消息交换。

∀m∈Iter,其中 p(m)≠p(s) 或 p(m)≠p(t)。 考虑到 p(m)≠p(s),当 superstep=1 时,MLQA 将 s 发送到 Rout(m)(第 14 行)。 那么,当superstep=2时,可以从m得到q(s,t)的查询结果。 请注意,此操作仅涉及单轮消息交换。

迭代=∅。 当 superstep=2 时,程序可以终止,并且没有顶点可以返回 q(s,t)=true。

示例 8. 考虑三个查询 q1(v17,v3)、q2(v3,v21) 和 q3(v11,v7)。

我们有 Iter1={v17}、Iter2={v6} 和 Iter3=∅。 那么查询结果分别为q1=true、q2=true、q3=false。
情况2:s,t∈VI。 如果s和t都是内部顶点,我们需要考虑以下情况。

∃m∈Iter 且 p(s)=p(t)=p(m)。 同样,当superstep=1时,我们可以使用内部2跳索引来获取查询结果,并且这个过程也不涉及消息交换。

s 可以到达 t,且 Iter=∅。 根据ML2Hop的定义,每个内部顶点及其标签被放置在同一分区中。 当superstep=0时,顶点s和t将分别发送到Lout(s)和Lin(t)中的所有元素。 显然,这就变成了案例1中提到的查询问题,而且这种查询问题也涉及到单轮消息交换。

s 无法到达 t。 类似地,它首先转换为边界顶点对之间的查询问题。 然后根据边界2跳索引即可得到结果。

示例 9. 考虑三个查询 q1(v10,v2)、q2(v10,v5) 和 q3(v2,v5)。

在这里,由于 Iter1={v3},我们有 q1=true。 对于q2(v10,v5),v10和v5首先分别传输到v3和v4。 然后,如情况 1 中分析的那样,v3 可以到达 v4,因此得到 q2=true。类似地,我们有 q3=false,因为 Iter3=∅ 并且 Lout(v2) 中没有边界顶点。
情况 3:s∈VI ∧ t∈VB(或反之亦然)。 由于这两种情况比较一致,所以我们只讨论第一种情况,如下所示。

s 可以到达 t。 根据定义3,每个边界顶点标签m∈Lout(s)满足p(m)=p(s),并且当superstep=1时顶点m可以接收s。 然后,它变成了查询问题q(m,t),这已经在案例1中得到了证明。

s 无法到达 t。 这个证明与案例2类似。

例10.

对于q1(v10,v6),v10作为v3∈Lout(v10)传输到v3,而v6不传输到其他顶点。 然后,我们有 q1=true,因为 v3 可以到达案例 1 中所示的 v6。
如上所述,对于任何单个可达性查询,MLQA 最多可以在三个超级步内获得准确的结果。 这个结论一般可以推广到集合可达性查询问题。 在下面的部分中,我们将说明 MLQA 如何利用 ML2hop 索引获取图 2a 中的集合可达性查询的结果。

示例 11.

考虑集合可达性查询 (S,T),其中 S={v2,v4,v8} 和 T={v19,v21}。 当superstep=1时,每个顶点u∈S(或t∈T)被传输到顶点v∈Lout(u)(或z∈Lin(t)),其中p(u)= p(v)(或p(t) )=p(z))。 例如,〈1,v4〉和〈1,v8〉分别传输到{v6,v12}和{v21,v22}。 同样,v12也可以接收消息〈0,v19〉。
当superstep=2时,更新的消息从激活的顶点开始在不同的分区之间进一步传输。 例如,v21接收消息〈1,v8〉,并向v6和v17发送〈0,v21〉。

当superstep=3时,每个顶点进一步接收消息并更新标签集以获得最终的查询结果。 我们可以从 v12、v6 和 v21 分别得到可达对 (v4,v19)、(v4,v21) 和 (v8,v21)。

4.4 关于边插入的讨论


许多数据图是动态的,是由于顶点和边的频繁更新而产生的。 因此,研究索引维护技术是值得的。 正如[21]中所讨论的,在边缘删除的情况下更新索引更加复杂。 在[1]中,一旦删除边,就需要检查数据图的强连通分量并重新构建基于图的索引。 与边插入相比,ML2hop在边删除时很难估计受影响的顶点并更新相应的索引。

在这里,我们讨论在边插入下更新 ML2hop 的相应操作,在许多应用中,边插入比边删除更频繁。 为了简化整个过程,我们只考虑导致可达性变化的插入边。

对于每条插入的边e(u,v),我们分析三种情况如下。

情况1:u,v∈VB。 在这种情况下,边界2跳索引基于边界图进行更新,并且这种变化对每个子图中的内部2跳索引没有影响。

具体来说,当superstep=0时,Lin(u)和Lout(v)中的所有元素都基于新插入的边e(u,v)进行传播。 在随后的超级步中,每个激活的边界顶点都会触发对所有接收到的消息的标签的重新检查,并且程序继续执行,直到没有消息传输为止。 请注意,可以根据边界2跳索引轻松构建边界图。

情况 2:∃m∈{u,v},其中 m∈VI 且 p(u)=p(v)。 在这种情况下,我们假设 {u,v} 中至少存在一个内部顶点。

这里,内部 2 跳索引首先在放置 u 和 v 的子图中更新。 整个过程类似于情况1所示的边界2跳索引的更新。需要注意的是,当基于e(u,v)的插入为任意不可达边界顶点对构造新的可达路径时,需要 ML2hop进一步更新边界2跳索引。

情况 3:u∈VI 且 v∈VB,其中 p(u)≠p(v)(或反之亦然)。 在这种情况下,我们观察到顶点 u 变成了边界顶点。 我们将u的排名值重置为所有边界顶点中最小的一个,因为这样可以保持ML2hop的特性,同时尽可能减少不断更新带来的开销。 假设wεNout(u),zεNin(u),具体操作如下所示。

内部 2 跳索引更新。 在这一部分中,我们首先清除Lout(u)和Lin(u)中记录的除自身之外的索引信息。 然后,当superstep=0时,每个顶点对(u,w)(或(z,u))被激活以相互传输消息。 后续超级步的操作与案例2类似。

内部路径更新。 在这一部分中,我们根据u的内部2跳索引和其他边界顶点的边界2跳索引重建每个子图中的内部路径。 具体操作如算法3所示。

边界 2 跳索引更新。 在这一部分中,我们根据边界图进一步更新边界2跳索引。 与情况1类似,当superstep=0时,活动顶点是边界图中的u及其所有邻居。 后续超级步的操作与案例1类似。

Experiments


本节介绍了我们为评估我们的方法的性能而进行的广泛实验。 第 5.1 节介绍了我们的实验设置,第 5.2 节评估了实验结果。

5.1 实验设置


数据集。 表 4 显示了从斯坦福大学大型网络数据集集合 3 和网络存储库 4 下载的几个数据集。

表 4 真实世界图的描述

算法。 我们提出的ML2hop索引与静态边界图索引DSR[1]和最先进的分布式索引PVL[5]进行比较,如下所示。

ML2hop。 具有剪枝策略的分布式多级 2 跳标记索引。

数字SR。 具有广度优先搜索(BFS)策略的基于静态图的可达性索引。

PVL。 可达性查询的可扩展分布式索引。

这里,采用KaHIP [24],一种边缘切割划分方法来评估平衡划分的重要性,因为它可以用最少数量的切割边缘产生顶点平衡划分结果。 此外,KaHIP可以通过调整平衡参数β来改变划分结果的局部性。

对于DSR,我们使用ML2hop来加速边界图的构建。 对于PVL,我们将每个顶点的标签大小限制为3。此外,我们分别用DSRQA和PVLQA代替基于DSR和PVL的查询算法。

查询集。 对于每个数据图,我们选择相同数量的正查询和负查询,其中源顶点和目标顶点放置在每个分区中。 报告了每种方法的平均实验结果。

所有算法均采用 C++ 实现,并使用 GCC 4.8.5 和 O2 级优化进行编译。 所有实验均在Blogel系统上进行,该系统部署在配备16个计算节点的集群上。 每个计算节点均配备 Intel 四核 Xeon E5540 2.53GHz 和 32 GB 内存。 计算节点之间的通信通过MPI来实现。 此外,我们默认将所有数据集的分区数固定为 16。

5.2 绩效评估


我们首先分析所有方法的时间成本和空间开销。 然后,我们评估 MLQA 的查询性能。

Exp-1:索引时间。 表 5 显示了三种算法的索引时间。 据观察,DSR 需要最短的索引时间,因为它可以基于内部 2 跳索引进行很大程度上优化。 相比之下,构建PVL非常耗时,因为每个顶点必须收集固定数量的标签,导致大量计算。 如表5所示,数据图中记录的标签远少于顶点,这可以证明ML2hop的索引时间是合理的。 考虑到需要进一步构建边界2跳索引,ML2hop的索引时间比DSR稍长。

Exp-2:空间成本。 表 5 列出了执行设置的可达性查询时三种方法的总空间开销。 发现DSR的空间成本最高,因为它需要将边界图保留到所有分区中,并同时保留一部分原始数据图。 与DSR相比,PVL的空间成本相对较小,从而减少了总的空间开销。 相比之下,ML2hop 的空间成本最小。 这主要是因为查询任务可以通过单独采用完整的ML2hop索引来解决。 因此,查询任务执行过程中无需保留原始数据图。 而且,每个顶点记录的平均标签数量远小于顶点总数。

Exp-3:查询时间。 表6a显示了三种方法在正查询和负查询上的平均查询时间,其中顶点对的规模设置为16×16。 这里,“*”表示该方法无法在该数据集上得到结果。 显然,MLQA 在所有数据集上的查询时间都远远少于其他算法。 具体来说,MLQA 的性能显着优于 DSRQA 和 PVLQA,加速分别高达两个和三个数量级。 MLQA 还可以在肯定查询和否定查询上提供最佳性能。

表 5 不同数据集上的性能比较

表 6 查询时间(秒)

如果不采取有效的剪枝策略来加速查询过程,PVLQA 无法在集合可达性查询上达到可比或更好的效果。 主要原因是来自每个源顶点的消息不能简单地用所采用的剪枝策略终止。 与PVLQA相比,DSRQA仅在不同分区之间进行单轮消息交换。 然而,DSRQA在执行本地计算时需要遍历所有相关路径,从而降低了查询效率。 相比之下,MLQA 采用双向查询技术来同时激活所有源顶点和目标顶点。 更重要的是,我们从理论上证明了 MLQA 中集合可达性查询的上限,这在很大程度上最小化了本地路径探索并减少了冗余计算。

表6b显示MLQA和DSRQA的查询时间随着顶点对的增加而增加。 考虑到所有数据集上的实验结果所反映的趋势是一致的,我们只给出U5和IT上的实验结果。 与DSRQA相比,MLQA获取查询结果的耗时更少。 此外,我们观察到MLQA更有能力处理具有大规模源顶点和目标顶点的集合可达性查询任务。

Exp-4:沟通成本。 表 7a 显示了三种查询算法的平均通信成本。 这里,“*”表示该方法无法在该数据集上得到结果。 请注意,MLQA 的通信开销分别减少了 5 个和 4 个数量级,显着优于 DSRQA 和 PVLQA。 具体来说,PVLQA的目标是基于剪枝策略提前终止任务的计算,从而减少通信开销。 然而,所提出的剪枝策略在这方面对集合可达性查询没有显着影响。 与PVLQA相比,DSRQA仅执行单轮消息交换。 然而,由于涉及太多不同的分区,毫无疑问需要更多的边界顶点来进行消息交换,从而导致显着更高的通信开销。 与 DSRQA 不同,MLQA 可以避免第一个超级步骤中的通信开销。

表 7 通信成本 (MB)

表 7b 显示了 MLQA 和 DSRQA 的通信成本。 考虑到所有数据集上的实验结果所反映的趋势是一致的,因此仅给出U5和IT上的实验结果。 一般来说,通信成本随着顶点对的增加而增加,并且MLQA的通信成本远小于DSRQA。

Exp-5:分区策略。 这部分检查分区策略对索引时间的影响,其中分区数设置为16。这里,“*”表示该方法无法在此数据集上得到结果。 采用的两种划分方法分别是Hash方法和KaHIP方法。 对于KaHIP,划分结果的局部性随着β的增加而逐渐增强。 需要注意的是,“内部”部分的时间成本是内部2跳索引构建和内部路径重建的累积。 “绑定”部分的时间成本仅涉及边界2跳索引的构建。

如表8所示,在大多数数据集上,“内部”部分的时间开销随着β的增加而逐渐增加。 具体来说,由于数据分布不平衡,“内部”部分的时间成本增加。 相比之下,构建边界 2 跳索引所需的时间更少,因为边界图将随着分区结果局部性的增加而被压缩。 对于哈希策略,每个顶点被随机分配到相应的分区,从而损害了子图的数据局部性。 此外,它会产生大量的切割边和边界顶点,从而严重影响ML2hop的构建效率。

表 8 分区策略的索引时间

Conclusion

在本文中,我们构建了一种新颖的分布式标记方案 ML2hop,用于有向图上的集合可达性查询。 首先,我们提出了一种有效的分布式索引构建算法来构建 ML2hop。 其次,我们从理论上证明了查询轮数的上限,并设计了一种双向查询算法来回答集合可达性查询。 实验结果表明了我们提出的算法的效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值