寻找具有高交集的集合的最快算法

时间:2010-04-23 08:34:38

标签: algorithm data-structures set intersection

我有大量的用户ID(整数),可能有数百万。这些用户都属于各种组(整数组),因此有大约1000万组。

为了简化我的示例并了解它的本质,我们假设所有组都包含20个用户ID。

我想找到交叉点为15或更大的所有整数集对。

我应该比较每一对吗? (如果我保留一个映射userID以设置成员资格的数据结构,则不需要这样做。)最快的方法是什么?也就是说,我的底层数据结构应该用于表示整数集?排序集,未分类---可以以某种方式散列帮助吗?我应该使用什么算法来计算集合交集)?我更喜欢与C / C ++(特别是STL)相关的答案,但也欢迎任何更一般的算法见解。

更新 另外,请注意我将在共享内存环境中并行运行,因此首选干净扩展到并行解决方案的想法。

另外,请注意绝大多数集合对的交集大小为0 ---这意味着使用将用户ID映射到集合的数据结构可能是有利的,以避免计算每对的交集套装。

4 个答案:

答案 0 :(得分:6)

我会完全按照您的建议行事:将用户映射到他们的群组。也就是说,我会为每个用户保留一个组ID列表。然后我会使用以下算法:

foreach group:
  map = new Map<Group, int>  // maps groups to count
  foreach user in group:
    foreach userGroup in user.groups:
      map[userGroup]++
      if( map[userGroup] == 15 && userGroup.id > group.id )
        largeIntersection( group, userGroup )

假设您拥有G个群组,每个群组平均包含U个用户,并且假设这些用户平均属于g个群组,那么这将在O( G*U*g )中运行。考虑到你的问题,这可能比在O(G*G*U)中运行的组的天真成对比较快得多。

答案 1 :(得分:4)

如果绝大多数交叉点为0,则表示非空交叉点的数量相对较小。试一试:

  • 在开始之前丢弃所有尺寸<15的
  • 从用户ID计算您的查询 - &gt;它所属的集合列表
  • 创建map<pair<userset, userset>, int>
  • 对于每个用户,增加(必要时创建后),该地图的n*(n-1)/2条目,其中n是用户所属的集合数。
  • 完成后,扫描地图以查找值大于15的条目。

它将使用比计算每个交叉点的简单方法更多的内存。事实上,它将与可行的方法相抗衡:如果平均每组与其他10个相交,可能在非常小的交叉点,那么地图需要50M条目,这开始是很多RAM。这也是缓慢不友好的。

它可能比进行所有集合交叉更快,因为O(n ^ 2)项与非空交叉点的数量和每个用户所属的组的数量有关,而不是与集。

由于巨型地图上的争用,并行化并非易事。但是,您可以将其分成每个线程的映射,并定期为一个线程提供一个新的空映射,并将结果添加到总结果中。然后,不同的线程在大多数时间完全独立运行,每个线程都有一个要处理的用户列表。

答案 2 :(得分:2)

  

我应该比较每一对吗? (如果我保留一个映射userID以设置成员资格的数据结构,则不需要这样做。)

为了计算交叉度,您仍然需要访问用户拥有的其他组,这仍然是立方体。您可以使用哈希表或其他稀疏数组进行计数,但对于每个用户所在的每对组,每个用户最多还是需要增量。如果G组中有N个用户,每个用户平均有S个用户每个用户所在的组和T组,如果你有一个索引,你有比较每对组的G G S / 2和N T T用户分组。 T = G S / N,所以N T T = G G S S / N;对于S = 20和数百万的N,应该有一个优势。不幸的是,对于交叉计数,您还需要至少G * G存储(对于4位非稀疏计数器,大约需要25 TB),并且必须确保结构可以并行递增。

对于1000万组中20万的一百万用户,非常接近用户在给定组中的概率是2e-6,并且两组将共享用户的概率是40e-6,所以25TB来了低至1GB的数据,因此对于普通大小的计算机上的稀疏阵列来说并非不可能。

然而,比较15个元素的20个元素的共同组合具有更明显的优化

  • 如果对组进行排序,则不需要工作存储,只需直接输出输入组之间的差异程度。
  • 大多数内存访问在连续内存区域中是线性的,结果仅取决于被比较的两个集合,而不是依赖于整个数据集的求和。随机访问主存储器比线性访问主存储器要慢得多。使用总线锁随机改变主存储器比访问高速缓存要慢几个数量级而不需要锁定总线(尽管如果每个核心有几个GB,则可以使用用户 - >组方法而无需进行任何同步)
  • 只需计算5个不同的元素;如果数据是随机的,那么大多数集合将是不相交的,因此访问的平均元素数量会更小。
  • 您可以通过将差异视为距离来快速折扣某些群组(如果A与B不同11,C与B不同,那么C与6不同于A,因此可以在不进行比较的情况下打折A和C直接)。由于大多数套装是完全不相交的,所以这不会让你获得太多。

还可以选择混合方法,使用user-&gt;组映射来修剪要进行的组比较组。这样做的好处是不需要递增共享数据结构:

  • 对于用户所在的每对组,将该对添加到列表中进行调查。
  • 对至少一个共同用户的组对列表进行排序。
  • 列表中每对出现的次数是他们共有的用户数。

使用合并排序,这几乎是每个并行化为纯流媒体单元。您将排序大约20 * 200 * 1000万/ 2 = 200亿组ID对(每组20个用户乘以每个用户所在的组数/ 2)。

答案 3 :(得分:1)

一种方法是将您的问题视为metric space 半径搜索问题,其中距离函数是不匹配条目的数量,半径为r = max(number of elements in sets) - number of equal。需要过滤找到的元素才能看到Set中有足够的值。因此,除非有人提出可以直接使用的度量函数,否则此解决方案会受到很多限制。

度量标准搜索的一种数据结构是BK-Tree,可用于字符串相似性搜索。

Candidates for your problem是VP树和M树。

当您搜索距离时,度量树的最坏情况是O(n ^ 2)&gt;在O(log n * n)中构建树并在O(n ^ 2)中搜索时,m(集合中元素的最大数量)。

除此之外,实际的运行时复杂性取决于在执行搜索时修剪度量树的子树的可能性。在度量树中,如果枢轴元素到搜索元素的距离大于枢轴元素的半径(至少是祖先到枢轴元素的最大距离),则可以跳过子树。如果您的条目集是相互分离的,则整个运行时将由度量树O(log n * n)的构建时间控制。