检查子集是否包含给定子集列表的快速方法

fast way to check if subset contains a given list of subsets

本文关键字:子集 方法 列表 是否 包含 检查      更新时间:2023-10-16

我的问题如下

我有一个K元素的集合

这个集合的每个子集都由std::bitset的一个实例表示(第i位为真=子集中存在第i个元素)

我有一个输入子集I和一个子集列表S1…Sn

我想从S1返回项目…Sn,使得Si包含在I中(也就是说,每次Si有一个位为真,它在I中也必须为真)

显然,这可以在K*n内完成,通过对每个S子集独立地进行相同的检查。

然而,有没有一种通用的方法可以做得更好?我很确定这是可能的,因为在我的情况下,子集列表S1…Sn总是相同的,可以进行预处理。我确信可以将子集存储在特定的数据结构(树?),这样我就可以一次丢弃很多相同的东西,等等

example :
K = 5
I = [1,1,0,1,0]
S1 = [1,0,0,0,0]
S2 = [1,1,0,1,0]
S3 = [1,1,1,0,0]
the ouput should return S1,S2 (not S3!)

我有一个常数集S1,S2,...,Sn,并且在相同的集合上运行I的不同查询。

编辑:我所说的例子:例如,如果S1包含在S2中:检查S1是否包含在I中:如果没有,则S2不能包含在I中(不需要检查)如果S3是S1和S2的并集:如果S1和S2包含在I中,那么S3

构造一个二叉树T与所有S1...Sn,其中每一级k有两个子节点,取决于S是否有01在位置k。树的叶子都是你的S1...Sn

给定一个输入子集I,让我们取Ik(位置k的元素):如果Ik==0,则选择TK对应的0层的子树。如果Ik==1,则选择TK级别的两个子树。以这种方式在T上进行,直到到达所有的叶子。

在最坏的情况下,对给定的I进行O(n+k)操作

由于S1...Sn不会改变,构造树T是一次操作。

编辑:我回答得太仓促了。树T有不止n片叶子,它有2^k=m片叶子。但是我们可以移除不在S1...Sn中的叶子和死子树。这带来了O(2^k)的成本分析,但实际上我们将拥有更少的节点。现在分析变得更加困难,是否值得取决于mn之间的比率;

我提出了一种不同的分析方法:认为在k级上,我们在恒定时间内丢弃k级上具有无效位的所有子集S,但我们必须在每级O(n)子树中这样做。由于该操作重复了k次,因此最大成本将是O(kn),但实际上平均成本更低。

您可以使用倒排索引方法。虽然它不能提高最坏情况下的性能,但它可能会加快平均情况下的速度,特别是对于相对密集的查询向量。

对于每个j=1,2,…,k创建一个排序列表,如果jS_i中,则每个子集都在此列表中。它只在预处理中创建一次。

在您的示例中,它将类似于:

0 -> [S1,S2,S3]
1 -> [S2,S3]
2 -> [S3]
3 -> [S2]
4 -> []

现在,给定一个查询I,找出包含I的一个"down"位的所有集合。这与信息检索中的OR查询相同。这个查询的答案是结果中没有的子集。

在您的示例中,查询的是2 OR 4,查询倒排索引的结果是:S3,因此结果是S1,S2。


这基本上是搜索引擎所做的,如果查询包含的术语很少,与可能的数量相比,它是非常有效的。

用部分答案回答我的问题:

    从S1
  1. …然后我们构建一个子集树,使得根节点是空子集(bitset中全部为0),并且使得每个子节点都包含它的父子集
  2. 对于算法,从根节点开始:
      每个孩子的
    • :
      • 如果该节点的子集包含在I中,则添加该子集并以该节点为根再次调用算法
      • 否则,转到下一个子树(此子树的子树永远不会被处理)

现在的问题是,如何从1)最优地构建树?它具有最大深度和最小"宽度"。例如,在我的示例中,"坏"树是S1、S2和S3是根节点的子节点。一个"好的"树应该是根节点只有S1作为子节点,而根在S1的树有S2和S3作为子节点。我不知道如何构建这棵树,但是