【问题标题】:fast way to check if subset contains a given list of subsets检查子集是否包含给定子集列表的快速方法
【发布时间】:2015-09-09 08:57:20
【问题描述】:

我的问题如下

我有一组K元素

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

我有一个输入子集 I,以及一个子集列表 S1...Sn

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

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

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

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 也是如此

【问题讨论】:

  • 不确定我是否关注I want to return the items from S1...Sn, such that Si is included in I.,如果它与我有任何共同点,你想返回 Si 吗?正式地,您在寻找{S_i | S_i [intersection] I != {} } 吗?也许添加一个例子会澄清你到底在追求什么。
  • 知道了,还有关于集合S1,..,Sn任何排序顺序的知识吗?否则,您的 O(K*n) 基本上是线性时间,我认为如果不实际读取所有输入就不可能做到这一点,除非您可以使用一些命令。 (如果你有常量集 S1,...,Sn 和 I 不断变化,你也许可以改进它)
  • 子集是否有序? S2, S3 是否会被视为您的示例的有效输出?
  • 子集 S1...Sn 没有特定的顺序。当我谈论比线性时间更好时,我排除了处理初始集合和构建数据结构(一劳永逸)所需的时间,我正在考虑运行算法时的复杂性和不同的 I,和相同的 S1...Sn
  • 所以你正在寻找{S_i with (S_i & (~I)) == 0}。 (& 位与和~ 位非)。

标签: c++ algorithm stl complexity-theory


【解决方案1】:

构造一个包含所有S1...Sn 的二叉树T,其中每个级别k 有两个儿子节点,具体取决于Sk 位置是否有01。树的叶子都是你的S1...Sn

给定一个输入子集I 让我们采用Ik(位置k 的元素):如果Ik==0 你选择T 在级别K 对应于0 的子树。如果Ik==1 你选择两者T 在级别 K 的子树。以这种方式在 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),但平均而言实际上更少。

【讨论】:

  • 不确定复杂性分析。如果你“选择两个子树”,你需要继续选择他们的孩子,假设Ik=[1,1,1..,1]。在这种情况下,在根级别,您需要检查 1 个节点。在下一级,2 个节点,在下一级 4 个节点(依此类推)。这基本上总结为检查从根到每个叶子的完整路径。有n 叶子,路径长度为k - 所以最坏情况下的复杂性仍然是O(nK)。现在,我并不是说这是一个坏主意,或者它可以比O(nk) 做得更好,只是纠正这种方法的最坏情况。
  • 您甚至可以使用vector。但实际上,您只是将每个可能的结果存储在内存中。
  • 一棵有N个叶子k层(k=log(n))的二叉树一共有2n-1个节点(包括叶子)。每个节点最多选择一次。
  • 我认为这仍然是 n*k 时间,但这个解决方案的优点是它会跳过大量的情况,在 I 和 S1...Sn 有很多 0 (这对我来说是最常见的情况)
【解决方案2】:

您可以使用inverted index 方法。虽然它不会提高最坏情况的性能,但它可能会加快平均情况的速度,尤其是对于相对密集的查询向量。

为每个 j=1,2,...,k 创建一个排序列表,如果 jS_i 中,则每个子集都在此列表中。这仅在预处理中创建一次。

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

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

现在,给定一个查询I 查找所有包含I 的“向下”位之一的集合。这与信息检索中的 OR 查询相同。此查询的答案是结果中没有的子集。其余的是。

在你的例子中,查询是2 OR 4,查询倒排索引的结果是:S3,所以结果是S1,S2。


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

【讨论】:

  • 好吧,如果我没记错的话,时间将与 N*(I 中 0 的数量)成正比,对吧?所以这在很多情况下确实会加速
  • 它实际上更像(N-R)*(number of 0 in I) + R,其中R 是结果集的大小(这仍然不严格,因为它假设所有 N-R 集都在每个列表中)。就像我说的那样,不会改善最坏的情况 - 但它可以让许多其他人加快速度。
【解决方案3】:

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

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

现在的问题是,如何以最佳方式从 1) 构建树?即具有最大深度和最小“宽度” 例如,在我的示例中,“坏”树将是 S1、S2 和 S3 是根节点的子节点。 “好”树是根节点只有 S1 作为子节点,而以 S1 为根的树有 S2 和 S3 作为子节点。 但是我不知道如何构建这棵树

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-20
    • 1970-01-01
    • 2019-01-14
    • 2015-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多