4

我被要求编写一个例程来确定产品配置器中可能组合的数量。

配置器非常简单。尽管它具有比这更多的功能,但它可以建模为几个“无线电组”(如 UI 控件),其中必须选择 n 个选项之一。

唯一可以使用的约束是规则,如果选择了一个选项,则不能选择另一个选项。

所以我想做的是计算可以配置的不同产品的数量,给定一组选项组和约束。

我采用了一种天真的方法来解决这个问题,使用包含-排除原则。但是据我所知,任何基于此方法的算法都应该在 O(2^n) 中运行,这将不起作用。当然,有几种可能的优化应该可以提供不错的运行时间,但仍然存在很容易构建的最坏情况。

这就是我现在所处的位置。有什么建议么?

更新

我意识到我还没有解释规则如何应用得足够好。

有几个组有选项。每个组中必须选择一个且只有一个选项。一个组中可以有一个或多个选项。

只有一种类型的约束。如果选择了某个组中的选项A,则无法选择其他组中的选项B。可以有任意数量的约束,没有限制有多少约束/规则适用于选项组或选项本身。

所以一个例子是:

第一组:
x1 x2 x3 x4 x5

第 2 组:
y1 y2 y3

第 3 组:
z1 z2 z3 z4

约束:
x1 <-> y2 *
x1 <-> z4
y2 <-> z2

*如果在 group1 中选择了 option x1,则不能选择 group 2 中的 option y2。

使用包含-排除,我将计算组合数为

组合 = C无规则- C r[1] - C r [2] - C r [3] + C r [1,2] + C r [1,3] + C r [2,3] - C r [1, 2,3]

在哪里

C无规则= 5 * 3 * 4

C r [a,b,c] = 违反规则 a、b 和 c 的组合数。

不幸的是,该方法需要 2^|rules| 计算。

4

11 回答 11

3

好的,我不能得到大约 2 ^ N,但我可以减少样本集。为此,我们将计算“组合约束”。组合约束是一个约束,如果左侧的所有选项都被选中,则右侧的任何选项都不能被选中,但基于左侧选项的其他约束可能不适用。

我们需要从一组约束中计算出一组所有可能的组合约束。虽然没有必要,但如果右手组小于左手组,我们将通过交换左右手来“修复”现有约束。这可能会减少一些组合约束,尽管可以使用更好的启发式进行交换。

我们还需要计算可以为每个组任意选择的选项的“最小集合”。这个最小集合是通过从可用选项列表中删除组合约束左侧出现的任何选项来计算的。

下面是一个算法,但我没有证明它可以正确计算 CC。我将证明,如果确实如此,那么它们可用于计算可能组合的数量。

  1. 固定约束,使左手的组小于或等于右手的组。
  2. 编写约束:

    1. 左手排序约束
    2. 依次,对于每个约束:

      1. 用相同的左手将约束与跟随它的所有约束折叠起来,将x1 <-> y1, x1 <-> y2...x1 <-> yN变成Set(x1) <-> Set(y1 ... yN)
      2. 如果:
        • x1 不在已折叠约束的右手边
        • x1 不属于左手任意元素的同一组
      3. 将折叠约束及其所有组合添加到折叠约束集
  3. 通过采用所有选项并删除固定约束左侧出现的选项来计算最小集。

现在您可以使用下面的公式计算组合的数量。让我们称 CC 为组合约束。那么组合的数量是:

C(Mininum Set) + CCC1 + ... + CCCn

在哪里:

  • C(最小集)是最小集可能的组合数。
  • CCCx 是可能的组合数,方法是采用最小集合,用该选项替换 CCx 左侧有选项的任何组,然后删除 CCx 右侧的任何选项。

请注意,该表达式纯粹是加法的。这意味着,要使该表达式产生预期结果,必须满足以下两个条件:

  1. 它的任何两个术语都不能包含相同的组合。
  2. 所有组合都必须由这些条款说明。
  3. 任何术语都不能产生无效的组合。

对于第一个证明,请注意没有两个不同的 CC 具有相同的左手。如果两个 CC 具有相同的左手但不同的右手,则意味着存在必须应用于其中一个 CC 的附加约束,或者将无效约束应用于另一个。

由于没有两个CC具有相同的左手,并且根据定义,最小集合不包含任何CC的左手,因此可以通过至少一个选项来区分任何两个CC . 因此,没有两个 CC 可以产生相同的组合。

对于第二个证明,请注意,根据定义,CC 集合包含左侧选项的所有有效组合。

假设有一个组合既没有出现在最小集合中,也没有出现在 CC 集合中。如果此组合不包含任何左选项,则根据定义,它必须是最小集合中的组合。因此,它必须包含来自左手的选项。

由于 CC 集合包含左手的所有有效组合,因此有一个 CC 具有相同的左手选项。因此,该组合必须具有该 CC 的任何组合中未包含的选项。但唯一没有包含在该 CC 中的选项是出现在其他 CC 左侧的选项,以及必须通过约束从其中排除的选项。既然两者都不是,那么这种组合就不可能存在。

对于第三个证明,我们首先考虑最小集。最小集合不包含任何组左侧的任何选项。由于所有约束都在一个左手选项和一个右手选项之间,因此没有约束适用于最小集合。

现在让我们考虑CC。根据定义,CC 具有左手选项的有效组合。任何与左手不兼容的选项都必须出现在右手中,并且右手中的任何选项都必须从最小集合中删除。由于在最小集合上没有选项在它们之间开始不兼容,因此在 CC 上的任何组合中都不会存在不满足的约束。

至此,证明结束。

让我们看看评论中的一个例子是如何应用的:

G1: x1, x2, x3 
G2: y1, y2 
G3: z1, z2, z3 

R1: x1 <-> y2 
R2: x3 <-> y2 
R3: y1 <-> z1 
R4: y2 <-> z2 
R5: y2 <-> z3

CC1: {x1} <-> {y2}
CC2: {x3} <-> {y2}
CC3: {y1} <-> {z1}
CC4: {x1, y1} <-> {y2, z1}
CC5: {x3, y1} <-> {y2, z1}
CC6: {y2} <-> {z2, z3}

让我们简要思考一下不在列表中的组合组:

R1&R2: {x1, x3} <-> {y2} -- not in the list because x1 and x3 belongs to the same
                            group
R1&R5: {x1, y2} <-> {y2} -- not in the list because the left hand of R2, y2
                            appears in the right hand of R1

现在,让我们看看每个集合中可能有哪些选项:

Minimum Set: (x2), (), (z1, z2, z3)    
CC1: (x1), (), (z1, z2, z3) -- replace G1 with x1, remove y2 from G2
CC2: (x3), (), (z1, z2, z3) -- replace G1 with x3, remove y2 from G2
CC3: (x2), (y1), (z2, z3)   -- replace G2 with y1, remove z1 from G3
CC4: (x1), (y1), (z2, z3)   -- replace G1 with x1, G2 with y1, remove y2 and z1
CC5: (x3), (y1), (z2, z3)   -- replace G1 with x3, G2 with y1, remove y2 and z1
CC6: (x2), (y2), (z1)       -- replace G2 with y2, remove z2 and z3 from G3

现在,让我们添加一些东西:

C(Minimum Set) = 1 * 0 *3 = 0
CCC1 = 1 * 0 * 3 = 0
CCC2 = 1 * 0 * 3 = 0
CCC3 = 1 * 1 * 2 = 2
CCC4 = 1 * 1 * 2 = 2
CCC5 = 1 * 1 * 2 = 2
CCC6 = 1 * 1 * 1 = 1

C(Minimum Set) + CCC1 + CCC2  + CCC3 + CCC4 + CCC5 + CCC6
0              + 0    + 0     + 2    + 2    + 2    + 1    = 7

我将在这里添加一个进一步的想法。尽管 5 条规则只有 6 个 CCC,远少于预期的 32 个术语,但这些 CCC 是用 2^N 最坏时间计算的,因为对于每条规则,您必须将其与迄今为止创建的所有 CCC 进行比较和组合。您可能将它们视为二进制数,如果正在组合规则,则设置一个位,否则不设置。

但是,不兼容的组合会立即被丢弃,因此对于每个被组合的新规则,不会在已经被视为无效的组合上浪费时间。此外,通过预先对规则进行排序,同一组中的连续规则可能会被丢弃,甚至无需测试是否具有正确的数据结构的不兼容性。

正如这个特定示例所示,平均时间可能比 2^N 好得多。

替代算法和注意事项

有一些关于 2-SAT 和 3-SAT 的讨论。在我看来,这是一个 2-SAT 问题,因为每个约束 a <-> b 实际上都是一个子句“!a || !b”。所以所有约束一起可以写成“(!x1 || !y2) && (!x1 || !z4) && (!y2 && !z3)”等。这意味着你可以从某种意义上“解决”它您可以找出每个选项是否有布尔赋值,这将变为 true。Aspall、Plass 和 Tarjan 对此有一个线性算法,这里有一个幻灯片演示。

但是知道约束是否可以解决并不是我们要问的。所问的是在保持 2-SAT 问题正确的情况下可以设置所有选项的方式的数量。

现在,有一些有效的算法可以计算满足 2-SAT 问题的方法的数量。例如,本文提出了一种在 1.2561 n中运行的算法。但即使这样也无济于事,因为我们需要知道解决方案是什么才能计算出满足该解决方案的组合数量。

根据维基百科,本文有一种算法可以有效地枚举所有解决方案,这正是我们想要的。但是,如果计数已经是指数级的,那么枚举也是如此。也许比 2 n好,但仍然是指数级的。

如果我们确实列举了 2-SAT 问题的所有解决方案,则每个组的组合数乘以 1 乘以未选择任何选项的每个组的自由选项数,即未出现在任何约束中的选项由解决方案。

例如,采用前一组组和约束。包括互斥在内的 2-SAT 问题是:

(!x1 || !y2) && (!x3 || !y2) && (!y1 || !z1) && (!y2 || !z2) && (!y2 || !z3) && (!x1 || !x3) && (!y1 || !y2) && (!z1 || !z2) && (!z1 || !z3) && (!z2 || !z3)

第一行是五个规则。第二行是约束规则中出现的同一组中所有选项的互斥。

这个 2-SAT 问题的解决方案是:

x1    x3    y1    y2    z1    z2    z3    Combinations
true  false true  false false true  false 1
true  false true  false false false true  1
true  false true  false false false false 0
true  false false false true  false false 0
true  false false false false true  false 0
true  false false false false false true  0
true  false false false false false false 0
false true  true  false false true  false 1
false true  true  false false false true  1
false true  true  false false false false 0
false true  false false true  false false 0
false true  false false false true  false 0
false true  false false false false true  0
false true  false false false false false 0
false false true  false false true  false 1
false false true  false false false true  1
false false true  false false false false 0
false false false true  true  false false 1
false false false true  false false false 0
false false false false true  false false 0
false false false false false true  false 0
false false false false false false true  0
false false false false false false false 0

在前两个解决方案中,没有没有选择选项的组,因此组合数为 1。第三个解决方案没有为组 G3 选择选项,因此我们将 1 乘以 0。在以“false”开头的行中false”,组 G1 没有选择选项,并且有一个免费选项:x2。因此,我们将它们乘以 1,如果没有为 G2 或 G3 选择选项,则乘以 0(在这种情况下,自由选项的数量为 0)。

现在,有一个问题是我如何强制在每个组中选择一个选项并且仍然声称是 2-SAT。如前所述,该问题有两个隐式约束:对于每个组,必须选择一个且只有一个选项。这两个约束可以写成:

    x 1 || x 2 || x 3(对于带有选项 x 1 .. x 3的组 x )
    (!x 1 || !x 2 ) && (!x 1 || !x 3 ) && (!x 2 || !x 3 )

后一个约束是 2-SAT,前一个约束是 3-SAT,适用于任何非平凡的情况。碰巧的是,我没有强制执行第一个约束,但是计数变为 0。计数算法应该是这样的:

  • 对于无约束组合,将每组中无约束选项的数量相乘。
  • 对于完全约束组合,添加以下计数的结果:
    • 对于每个解决方案,将每组中没有一个选项评估为“真”的无约束选项的数量相乘。

因此,对于其中至少有一个无约束选项的每个组,选择是隐式和匿名的。对于所有选项都是某个约束的一部分的每个组,如果未选择任何选项,则该组的计数变为 0,因此,该解决方案的组合数也变为 0。

这感觉就像从有效的 > 2-SAT 约束中欺骗问题。毕竟,如果这是可能的,那么 3-SAT 问题可以通过枚举其中 2-SAT 部分的解决方案来解决,然后丢弃所有不满足其 3-SAT 部分的解决方案。唉,我可以确定一个根本区别:

  • 问题的 2-SAT 部分未解决的所有谓词都不受任何进一步的约束。

鉴于对子句的这种相当严格的约束,我们可以通过枚举 2-SAT 显式约束的解决方案来解决这个问题。

如果有人想进一步追求这一点,请继续。我对我建议的 2 n解决方案的改进感到满意。

于 2009-07-25T14:33:44.630 回答
2

如果您有N选项组,每个选项组都有Xi选项 ( 0<=i<N) 然后

X0*X1*...*X(N-1)

给你想要的答案。换句话说,乘以每组选项的大小。

于 2009-07-20T16:39:55.373 回答
1

如果您的n参数具有Ci每个参数和约束的可能值,m则配置数量的上限如下(忽略约束)。

N0 = C1 * C2 * ... * Cn

表单的单个约束ci == x => cj != y不允许以下数量的配置。

        N
Dk = -------
     Ci * Cj

因此,配置的数量是通过从忽略约束的上限中减去不允许的配置来获得的。

N = prod(Ci, i = 1...n) * (1 - sum(1 / (Cxj * Cyj), j = 1...m))

这里xj和是来自-th 约束yj的两个参数索引。j

例子

Parameters    n = 4

Parameter 1   C1 = 4   0 1 2 3 
Parameter 2   C2 = 3   4 5 6 
Parameter 3   C3 = 2   X Y
Parameter 4   C4 = 3   7 8 9

Constraints   m = 2

Constraint 1  c2 == 4 => c3 != X
Constraint 2  c3 == X => c4 != 9

N0 = 4 * 3 * 2 * 3 = 72

D1 = 72 / (3 * 2) = 12
D2 = 72 / (2 * 3) = 12

N = 72 - (12 + 12) = 48

更新

我认为这还不完全正确,因为它没有考虑约束的依赖关系。

于 2009-07-20T17:18:12.083 回答
1

对于一般情况,这里没有捷径。 它没有你想的那么糟糕。请参阅下面的“重新思考”。

2^n 真的那么糟糕吗?我们在这里讨论了多少排除规则?您实际上只需要为每个配置器执行一次,除非规则/选项集不断变化并需要动态重新计算。如果确实有大量规则,那么我不会寻求精确的解决方案——只考虑 k 阶交叉点并说“组合数至少/最多......”。可能还有其他筛选方法可以让您快速得出答案的界限。

另请记住:如果您只考虑您实际需要的排除项,那么 2^n 只是一个上限,对于任何实际情况,您的实际计算次数可能会少得多。也就是说,如果 C[1,2] 为零,则 C[1,2,...] 也是如此。考虑一下:对于每个约束,如果它们共享任何共同选项,则将约束集“聚集”在一起。很明显,您的实际运行时间将由最大“块”的大小(是的,可能与 n 一样大)定义。


重新思考:在大多数情况下,C[x,y] 将为零。一个约束只能与涉及不同组的其他约束重叠。换句话说,(x1<->y1)只能与(x1<->z1)或(y1<->z2)或其他东西重叠,而不是(x1<->y2)。同样,一组约束只能与一个新组重叠:(x1<->y1) 与 (y1<->z2) 的组合与 (x3<->z2) 没有交互作用(x 组已经固定在 x1)。您只需要考虑包含/排除,其中添加到组合中的每个规则都会将以前未触及的组添加到组合中。所以你实际上是 O(2 G ),其中 G 是组的数量(也可能是基于组大小的不同界限)。更易于管理!

于 2009-07-24T16:10:14.907 回答
1

编辑

这个算法是不正确的。我在另一篇文章中提出了另一个答案,在最坏的情况下仍然是 2^N,但否则可能会给出更好的结果。

这在所选示例中有效,因为 y2 是 x1 的排除集的一部分,并且两个第一个约束基于 x1。不过,我现在看到了需要做什么。它仍然接近 2^N,但有一些优化可以带来显着的收益。

要修复此算法,组合规则必须采用 set(ox) <-> set(oy) 的形式。为了组合它们,对于你用左手 oX 组合的每个约束,你还用你已经组合的每个规则来组合它,如果 oX 不是组合规则右侧的一部分,它的组也不是与组的左侧相同

对于完全独立的约束,这是 2^N。否则,您将通过以下方式减少 N:

  • 用一个共同的左手统一约束
  • 不计算互斥的规则组合,这分为:
    • 不合并同一组中选项的规则
    • 不组合规则,其中一个的左侧出现在另一个的右侧

我不认为修复这个算法是值得的。它相当占用内存,并且与我的替代答案具有相同的顺序,后者要轻得多。

结束编辑

让我们扭转局面。这个算法怎么样:

  1. 根据需要修复规则以确保对于规则o1 <-> o2group(o1) < group(o2)
  2. 通过将所有规则折叠oX <-> o?成单个规则来计算“组合”规则oX <-> Set(o?)
  3. 通过从它们中删除每个规则的左侧选项来计算一组“干净”的组
  4. 通过用左选项本身替换左选项的组,并从其他组中减去规则右手的选项,从干净的集合中计算备用集合,每个组合规则一个。
  5. 对于每组组,通过乘以该组中每个组中的选项数来计算组合数。
  6. 将步骤 5 中的所有结果相加。

让我们看看这个在工作中:

Group 1:
x1 x2 x3 x4 x5

Group 2:
y1 y2 y3

Group 3:
z1 z2 z3 z4

Constraints (already fixed):
x1 <-> y2 *
x1 <-> z4
y2 <-> z2

Composed rules:
x1 <-> (y2, z4)
y2 <-> (z2)

Clean set of groups:
x2x3x4x5, y1y3, z1z2z3z4

Alternate sets:
x1, y1y3, z1z2z3 (first composed rule)
x2x3x4x5, y2, z1z3z4 (second composed rule)

Totals:
4 * 2 * 4 = 32
1 * 2 * 3 = 6
4 * 1 * 3 = 12

Total: 50

现在,也许这个算法是不正确的。现在,我无法清楚地思考以证明它是否正确——我已经太接近这个问题太久了。但是让我们检查一下这个例子:

c(no rules) = 60
c1 => 4
c2 => 3
c3 => 5
c12 => 1
c13 => 1
c23 => 0
c123 => 0

c(no rules) - c1 - c2 - c3 + c12 + c13 + c23 - c123 = 50

如果我的算法是正确的,它似乎是多项式的。再说一次,现在我想的不够清楚,我需要考虑系列中操纵的大 O。

这是它的Scala实现:

case class GroupOption(id: Int, option: Int)
case class Group(id: Int, options: Set[Int])
case class Rule(op1: GroupOption, op2: GroupOption)
case class ComposedRule(op: GroupOption, set: Set[GroupOption])

object ComputeCombinations {
  def fixRules(rules: Set[Rule]) = {
    rules map (rule => if (rule.op1.id > rule.op2.id) Rule(rule.op2, rule.op1) else rule)
  }

  def ruledOptions(id: Int, rules: Set[Rule]): Set[Int] = (
    rules 
    filter (rule => rule.op1.id == id)
    map (rule => rule.op1.option)
  )

  def cleanseSet(groups: Set[Group], rules: Set[Rule]) = {
    groups map (group => 
      Group(group.id, group.options -- ruledOptions(group.id, rules)))
  }

  def composeRules(rules: Set[Rule]): Set[ComposedRule] = Set(
    (
      rules.toList
      sort (_.op1.id < _.op1.id)
      foldLeft (List[ComposedRule]())
      ) { (list, rule) => list match {
        case ComposedRule(option, set) :: tail if option == rule.op1 =>
          ComposedRule(option, set + rule.op2) :: tail
        case _ => ComposedRule(rule.op1, Set(rule.op2)) :: list
      }} : _*
  )

  def subset(groups: Set[Group], composedRule: ComposedRule) = (
    groups
    filter (_.id != composedRule.op.id)
    map (group => Group(group.id, group.options -- 
                                  (composedRule.set 
                                   filter (_.id == group.id)
                                   map (_.option)
                                  )))
  )

  def subsets(groups: Set[Group], composedRules: Set[ComposedRule]) = (
    composedRules map (composedRule => subset(groups, composedRule))
  )

  def combinations(groups: Set[Group]) = (
    groups.toList map (_.options.size) reduceLeft (_*_)
  )

  def allCombinations(groups: Set[Group], rules: Set[Rule]) = {
    val fixedRules = fixRules(rules)
    val composedRules = composeRules(fixedRules)
    val cleanSet = cleanseSet(groups, fixedRules)
    val otherSets = subsets(cleanSet, composedRules)
    val allSets = otherSets + cleanSet
    val totalCombinations = allSets.toList map (set => combinations(set)) reduceLeft (_+_)
    totalCombinations
  }
}

object TestCombinations {
  val groups = Set(Group(1, Set(1, 2, 3, 4, 5)),
                   Group(2, Set(1, 2, 3)),
                   Group(3, Set(1, 2, 3, 4)))
  val rules = Set(Rule(GroupOption(1, 1), GroupOption(2, 2)),
                  Rule(GroupOption(1, 1), GroupOption(3, 4)),
                  Rule(GroupOption(2, 2), GroupOption(3, 2)))
  def test = ComputeCombinations.allCombinations(groups, rules)
}
于 2009-07-25T03:03:30.443 回答
1

这可能不是一个直接有用的答案,所以请随意忽略它......但是;我自己目前没有使用类似的系统;坦率地说,除了一些琐碎的例子,我不确定尝试计算有效组合的数量是否有用。例如,我目前正在研究的模型有(例如)18000 个候选项目,分布在 80 多个选择中,一些单选/一些多选。除了最小的模型,知道这个数字没有任何好处,因为你永远不会把它写成一个完整的真值表;你几乎是被迫的按需运行规则(即删除不再有效的任何内容,根据需要自动选择任何内容,并检查是否没有违反规则)。这不一定是问题;我当前的代码在约 450 毫秒内处理此模型(作为 Web 服务),其中大部分实际上是处理用于输入/输出的 xml 所花费的时间。如果输入/输出不是 xml,我认为它会是 ~150 毫秒,这很酷。

我很想说服我的雇主开源引擎;不过,这是另一天的战斗。

于 2009-07-26T08:01:21.523 回答
0

不会只是 x^n,其中 n 是选项数,x 是每个选项的选项数?

于 2009-07-20T16:37:25.550 回答
0

我认为扎克的想法是正确的。查看组合数的表达式,您会发现二阶项 Cr[i,j] 远小于 C[k] 项。想象一个立方体,其中每个轴都是一组选项。立方体中的每个点代表一个特定的选项组合。一阶 C[k] 校正不包括立方体两个表面之间的选项线。仅当两条这样的线在立方体空间中的一个点(选项组合)相遇时,才会发生二阶校正 C[i,j]。所以不管你有多少组,高阶修正总是越来越小。如果你坚持

组合 = C(无规则) - Cr[1] - Cr[2] - Cr[3]

您最终会得到组合数量的下限。既然您知道一阶校正的大小,并考虑上面立方体的观察结果,您甚至可以估计二阶校正的数量级。这将取决于组的数量。然后,您的算法可以决定是继续执行更高的订单还是停止。

于 2009-07-24T18:39:44.217 回答
0

评论丹尼尔的帖子

你的算法看起来不错,但我无法说服自己它真的有效,所以我安装了 scala 并进行了一些测试。不幸的是,我没有得到正确的结果。

例如考虑这种情况:

Group 1:
a1 a2 a3 a4 a5

Group 2:
b1 b2 b3

Group 3:
c1 c2 c3 c4

Group 4:
d1 d2 d3 d4 d5

Rules:
a1 <-> b2
a1 <-> c2
b2 <-> c2
b2 <-> d1
a2 <-> d2

我用这个配置配置了我的基本筛算法,得到了以下结果(227种组合):

没有规则=> 300
规则:[1] => 20
规则:[2] => 15
规则:[3] => 25
规则:[4] => 20
规则:[5] => 12
订单:1 => 208(差异:-92)
规则:[1, 2] => 5
规则:[1, 3] => 5
规则:[2, 3] => 5
规则:[1, 4] => 4
规则:[2, 4] => 1
规则:[3, 4] => 5
规则:[1, 5] => 0
规则:[2, 5] => 0
规则:[3, 5] => 1
规则:[4, 5] => 0
订单:2 => 234(差异:26)
规则:[1, 2, 3] => 5
规则:[1, 2, 4] => 1
规则:[1, 3, 4] => 1
规则:[2, 3, 4] => 1
规则:[1, 2, 5] => 0
规则:[1, 3, 5] => 0
规则:[2,3,5] => 0
规则:[1, 4, 5] => 0
规则:[2, 4, 5] => 0
规则:[3, 4, 5] => 0
订单:3 => 226(差异:-8)
规则:[1, 2, 3, 4] => 1
规则:[1, 2, 3, 5] => 0
规则: [1, 2, 4, 5] => 0
规则: [1, 3, 4, 5] => 0
规则:[2, 3, 4, 5] => 0
订单:4 => 227(差异:1)
规则:[1, 2, 3, 4, 5] => 0
订单:5 => 227(差异:0)

***组合:227***

但是在scala中使用此代码:

  val groups = Set(Group(1, Set(1, 2, 3, 4, 5)),
                   组(2,组(1、2、3)),
                   组(3,组(1、2、3、4)),
                   组(4,组(1、2、3、4、5)))

  val rules = Set(Rule(GroupOption(1, 1), GroupOption(2, 2)),
                  规则(GroupOption(1, 1), GroupOption(3, 2)),
                  规则(GroupOption(2, 2), GroupOption(3, 2)),
                  规则(GroupOption(2, 2), GroupOption(4, 1)),
                  规则(GroupOption(1, 2), GroupOption(4, 2)))

我得到了答案258

我检查了筛法中的计算,它们似乎是正确的。也许可以修复您的算法?我真的无法指出哪里出了问题。

于 2009-07-25T13:17:56.073 回答
0

你的问题是相当不可行的。

  • 计算解决方案的数量是#P-complete,即使您将每组单选框限制为两个选项
  • 检查是否有任何与约束一致的选项选择是 NP 完全的
  • 如果您将每组单选框限制为两个选项(2SAT),则可以非常快速地检查是否有任何与约束一致的选项选择

所以一般不要指望多项式算法;这种算法的存在意味着P=NP。

你可以做什么:

  • 使用近似算法。根据我链接的维基百科文章,他们经常怀疑他们。
  • 使用 SAT 求解器http://en.wikipedia.org/wiki/SAT_solver或相关的计数工具(不幸的是我不知道);人们已经创建了许多启发式方法,并且程序通常会比您自制的解决方案快得多。甚至还有 SAT 比赛,所以这个领域目前扩展得非常快。
  • 检查您是否需要这样的一般性。也许你的问题有一个额外的假设,这会改变它的复杂性。

证明:

  1. 计算解决方案的数量很容易证明是#P,因此将 2SAT 减少到这个就足够了。以一些 2SAT 实例为例,例如

(p1 or not p2) and (p2 or not p3)

允许用户选择 p1、p2、p3 的值。您可以轻松地形成约束,迫使其成为可解决的。因此,可能的配置数 = p1、p2、p3 的可能分配数,使布尔公式为真。

  1. 您可以轻松检查是否允许某些选项选择,所以这是 NP,因此将 3SAT 减少到此就足够了。以一些 3SAT 实例为例,例如

(p1 or p2 or not p3) and (p2 or not p1 or p4)

给出选项:

组 p1 p1true p1false

组 p2 p2false p2true

组 p3 p3false p3true

组 p4 p4false p4true

组子句_1 c1a c1b c1c

组子句_2 c2a c2b c2c

Clause_1 将控制第一个子句:(p1 or p2 or not p3)。准确地说,如果选择了 p1true,c1a 将是可检查的,如果选择了 p2true,c1b 将是可检查的,如果选择了 p3false,c1c 将是可检查的。所以约束是:

p1false <-> c1a

p2false <-> c1b

p3true <-> c1c

与条款_2相同,约束是

p2false <-> c2a

p1true <-> c2b

p4false <-> c2c

如果用户能够选择所有答案(因此配置数 > 0),他将证明变量 p1、...、p4 的某些估值使 3SAT 实例为真。相反,如果用户无法选择与假设一致的答案(可用配置的数量 = 0),则 3SAT 实例将无法满足。因此,知道答案是否 > 0 意味着知道 3SAT 实例是否可解。

当然,这种减少是多项式时间的。证明结束。

如果您对答案可能为 0 的事实不满意:如果您忽略此类配置器,它仍然是 NP-hard。(您可以为所有组添加一些“虚假”选项,如果未选择“虚假”,则允许以指数方式增加选择。这解释起来更复杂,所以我会根据要求来做。)

于 2009-07-26T21:23:11.817 回答
0

这在 sdcvvc 上面的出色答案中简要提到了,但是蒙特卡洛近似值是否足够好?生成 N 个随机配置(其中选择 N 以使您的实验的能力足够高:我不知道如何帮助您),然后测试其中有多少与您的约束兼容。将该比例外推到配置空间的其余部分。

于 2009-07-28T08:47:00.677 回答