有效的集合交集 - 决定交集是否大于k

时间:2011-04-27 09:44:45

标签: language-agnostic data-structures set intersection

我遇到了一个问题,我必须计算集合中所有对之间的交叉点。没有一个集小于一个小常数 k ,我只关心两个集合是否有一个大于 k -1元素的交集。我不需要实际的交叉点和确切的大小,是否大于 k -1。是否有一些聪明的预处理技巧或整齐的交集算法可以用来加快速度?

更多可以回答问题的信息:

  • 这些集代表一个大的,无向的稀疏图中的最大派系。集合的数量可以是数万或更多,但大多数集合可能很小。
  • 集已经排序每个集合的成员按递增顺序排列。实际上它们是排序列表 - 我从底层库中以这种方式接收它们以进行最大集团搜索。
  • 对于集合中元素的分布没有任何了解(即它们是否是紧密的团块)。
  • 大多数设置交叉点可能都是空的,所以理想的解决方案是一个聪明的数据结构,可以帮助我减少必须设置的交叉点的数量。

4 个答案:

答案 0 :(得分:5)

一种可能的优化,每个集合中包含的值范围越小,效果越有效:

  • 创建所有集合的列表,按其第k个最大元素排序(这很容易找到,因为您已经按顺序拥有每个集合的元素)。请将此列表称为L。
  • 对于任何两个集合A和B,如果A中的第k个最大元素小于B中的最小元素,则它们的交集不能包含k个元素。
  • 因此,对于每个集合,只计算其与L的相关部分中的集合的交集。

你可以使用相同的事实从计算任意两个集合的交集中提前退出 - 如果在其中一个集合中只剩下n-1个元素进行比较,并且到目前为止交叉点至多包含kn元素,那么停。上面的过程就是这个规则同时应用于L中的所有集合,其中n = k,在我们查看集合B的最小元素和A的第k个最大元素的位置。

答案 1 :(得分:5)

考虑一个mapping,其中所有大小为k的集合作为密钥以及集合中包含密钥作为子集的所有集合的列表的相应值。给定此映射,您不需要执行任何相交测试:对于每个键,列表中的所有对集将具有至少为k的大小的交集。这种方法可以多次生成同一对集合,因此需要进行检查。

映射很容易计算。对于集合中的每个集合,计算所有size-k子集并将原始集合附加到该密钥集的列表中。但这实际上更快吗?一般来说,没有。这种方法的性能取决于集合中集合的大小分布和k的值。在集合中有d个不同的元素,你可以选择多个d键,这可能非常大。

但是,基本思路可用于减少交叉点的数量。不使用大小为k的集合,而是使用固定大小为q的较小的密钥作为密钥。这些值再次列出了将密钥作为子集的所有集合。现在,从交叉列表中测试每对集合。因此,在q = 1的情况下,您只测试那些至少有一个共同元素的集合对,q = 2您只测试那些至少有两个共同元素的集合,依此类推。我认为,q的最佳值将取决于集合大小的分布。

对于有问题的集合,一个好的选择可能是q = 2。然后,键只是图形的边缘,为映射提供可预测的大小。由于大多数集合预计是不相交的,因此q = 2应该消除大量的比较而没有太多额外的开销。

答案 2 :(得分:2)

以下策略应该非常有效。我已经在很多场合使用了这种变化来交叉上升序列。

首先,我假设您有某种优先级队列可用(如果没有,滚动您自己的堆非常容易)。快速键/值查找(btree,hash,等等)。

话虽如此,这里是一个算法的伪代码,可以非常有效地完成你想要的。

# Initial setup
sets = array of all sets
intersection_count = key/value lookup with keys = (set_pos, set_pos) and values are counts.
p_queue = priority queue whose elements are (set[0], 0, set_pos), organized by set[0]

# helper function
def process_intersections(current_sets):
    for all pairs of current_sets:
        if pair in intersection_count:
            intersection_count[pair] += 1
        else:
            intersection_count[pair] = 1

# Find all intersections
current_sets = []
last_element = first element of first thing in p_queue
while p_queue is not empty:
    (element, ind, set_pos) = get top element from p_queue
    if element != last_element:
        process_intersections(current_sets)
        last_element = element
        current_sets = []
    current_sets.append(set_pos)
    ind += 1
    if ind < len(sets[set_pos]):
        add (sets[set_pos][ind], ind, set_pos) to p_queue
# Don't forget the last one!
process_intersections(current_sets)

final answer = []
for (pair, count) in intersection_count.iteritems():
    if k-1 < count:
        final_answer.append(pair)

运行时间为O(sum(sizes of sets) * log(number of sets) + count(times a point is in a pair of sets)。特别要注意的是,如果两个集合没有交集,则永远不要尝试将它们相交。

答案 3 :(得分:0)

如果您使用预测子集作为预均衡器,该怎么办?预排序,但使用子集交集作为阈值条件。如果子集交集> n%然后完成交集,否则放弃。 n然后变成你的舒适程度的倒数与假阳性的前景。

您还可以按先前计算的子集交点(m)进行排序,然后开始按m降序排序。因此,大概您的最高m个交叉点可能会在整个子集上超过您的k阈值,并且达到您的k阈值的可能性将持续下降。

这真的开始将问题视为NP-Complete。