快速检查set是否是存储集的超集

时间:2012-02-19 20:50:46

标签: algorithm data-structures set complexity-theory time-complexity

问题

我获得了N个C布尔数组。我想将这些组织成一个数据结构,允许我尽可能快地执行以下操作:给定一个新数组,如果此数组是任何存储数组的“超集”,则返回true。对于超集,我的意思是:如果A [i]对于B [i]为真的每个i都为真,则A是B的超集。如果B [i]为假,那么A [i]可以是任何东西。

或者,就集合而不是数组而言:

将N个集合(每个都有C个可能的元素)存储到数据结构中,这样您就可以快速查找给定集合是否是任何存储集合的超集。

构建数据结构可能需要尽可能长的时间,但查找应该尽可能高效,并且数据结构不能占用太多空间。

某些背景

我认为这本身就是一个有趣的问题,但对于我真正想要解决的问题,您可以假设以下内容:

  • N = 10000
  • C = 1000
  • 存储的数组稀疏
  • 查找的数组是随机的(所以不稀疏)

到目前为止,我想出了什么

  1. 对于O(NC)查找:只需迭代所有数组。但这太慢了。

  2. 对于O(C)查询:我在这里有一个很长的描述,但正如Amit在评论中指出的那样,它基本上是BDD。虽然这具有很高的查找速度,但它具有指数数量的节点。 N和C如此之大,这需要太多空间。

  3. 我希望在这个O(N * C)和O(C)解决方案之间,可能有一个不需要指数空间的O(log(N)* C)解决方案。

    编辑:我提出的一个新想法

    • 对于O(sqrt(N)C)查找:将数组存储为prefix trie。查找数组A时,如果A [i] = 0,则转到相应的子树,但如果A [i] = 1,则访问两个子树。

      我的直觉告诉我,如果你假设存储的数组是随机的,那么这应该使查找O(sqrt(N)C)的(平均)复杂度成为可能。但是:1。他们不是,阵列稀疏。 2.这只是直觉,我无法证明。

    我会尝试这个新想法和BDD方法,看看哪两个最好用。

    但与此同时,这个问题不会经常发生吗?它没有名字吗?还没有以前的研究吗?我真的觉得我在这里重新发明轮子。

4 个答案:

答案 0 :(得分:5)

只是将一些背景信息添加到前缀trie解决方案,最近我发现了以下文件:

I.Savnik:快速子集和超集查询的索引数据结构CD-ARES, IFIP LNCS, 2013.

本文提出了set-trie数据结构(容器),它使用 trie数据结构为高效存储和查询集合提供支持,支持查找所有超集/子集的操作。来自集合集合的给定集合。

对于任何对实际实现感兴趣的 python 用户,我想出了一个python3包,部分基于上面的论文。它包含一个基于trie的集合容器,以及一个映射容器,其中键是集合。您可以在github上找到它。

答案 1 :(得分:3)

我认为前缀trie是一个很好的开始。

由于您的数组稀疏,我还会批量测试它们。如果(B1 ∪ B2) ⊂ A,则包括两者。因此,我们的想法是成对地对数组进行OR包,并重复直到只有一个“根”数组(它只需要两倍的空间)。它允许在之前的问题中回答“是”,这主要是有用的如果您不需要知道数组实际上是否包含

独立地,您可以为每个数组申请一个保持排序的哈希函数。

即:B ⊂ A ⇒ h(B) ≺ h(A)

ORing位在一起就是这样一个函数,但您也可以在数组的足够分区中对每个1位进行计数。在这里,您可以更快地消除候选者(对特定阵列回答“否”)。

答案 2 :(得分:2)

您可以通过首先将集合列表减少到“最小”集来简化问题:仅保留那些不是任何其他集的集的集。问题仍然存在,因为如果某些输入集A是您删除的某些集合B的超集,那么它也是{至少一个“最小”子集C的超集。 {1}}未被删除。这样做的好处是你倾向于消除大型集合,这使问题更便宜。

从那里我会使用某种ID3或C4.5算法。

答案 3 :(得分:0)

在trie解决方案和@mmihaltz提到的论文的基础上,还可以通过使用已经存在的高效python trie实现来实现一种找到子集的方法。下面,我使用软件包datrie。唯一的缺点是必须将键转换为字符串,这可以使用"".join(chr(i) for i in myset)完成。但是,这将元素范围限制在110000左右。

from datrie import BaseTrie, BaseState

def existsSubset(trie, setarr, trieState=None):

    if trieState is None:
        trieState = BaseState(trie)

    trieState2 = BaseState(trie)
    trieState.copy_to(trieState2)
    for i, elem in enumerate(setarr):
        if trieState2.walk(elem):
            if trieState2.is_terminal() or existsSubset(trie, setarr[i:], trieState2): 
                return True
            trieState.copy_to(trieState2)
    return False

trie可以像字典一样使用,但是必须在开头提供可能的元素范围:

alphabet = "".join(chr(i) for i in range(100))
trie = BaseTrie(alphabet)

for subset in sets:
   trie["".join(chr(i) for i in subset)] = 0 # the assigned value does not matter

请注意,上面的trie实现仅适用于大于(但不等于)0的键。否则,整数到字符的映射将无法正常工作。这个问题可以通过索引移位来解决。

可以找到{y3}}来实现也涉及元素转换的cython实现。