3

给定一个场景,我们有多个项目对列表,例如:

  1. {12,13,14,23,24}
  2. {14,15,25}
  3. {16,17,25,26,36}

其中 12 是一对项目“1”和“2”(因此 21 相当于 12),我们想要计算可以从每个列表中选择项目对的方式的数量,使得没有单个项目是重复。您必须从每个列表中选择一个,并且只选择一对。每个列表中的项目数和列表数是任意的,但您可以假设至少有两个列表,每个列表至少有一对项目。并且这些对是由有限字母表中的符号组成的,假设为数字 [1-9]。此外,列表既不能包含重复对 {12,12} 或 {12,21},也不能包含对称对 {11}。

更具体地说,在上面的示例中,如果我们从第一个列表中选择项目对 14,那么我们对第二个列表的唯一选择是 25,因为 14 和 15 包含“1”。因此,第三个列表中的唯一选择是 36,因为 16 和 17 包含“1”,而 25 和 26 包含“2”。

有没有人知道一种有效的方法来计算项目对的总组合,而无需遍历选择的每一个排列并询问“这是一个有效的选择吗?”,因为每个列表都可以包含数百对项目?


更新

在花了一些时间之后,我意识到当没有一个列表共享一个不同的对时,计算组合的数量是微不足道的。但是,只要在两个或多个列表之间共享不同的对,组合公式就不再适用。

截至目前,我一直在试图弄清楚是否有一种方法(使用组合数学而不是蛮力)来计算每个列表具有相同项目对的组合数量。例如:

  1. {12,23,34,45,67}
  2. {12,23,34,45,67}
  3. {12,23,34,45,67}
4

7 回答 7

3

问题是#P-complete。这甚至比 NP-complete 更难。这就像找到一个SAT实例令人满意的作业数量一样困难。

减少来自完美匹配。假设您有一个图G = {V, E},其中E边的集合是顶点对(由边连接的顶点对)的列表。然后通过|V|/2复制E. 换句话说,拥有的副本数E等于顶点数的一半。现在,在您的情况下,“命中”将对应于|V|/2没有重复顶点的边,这意味着所有|V|顶点都被覆盖。这是完美匹配的定义。每一次完美匹配都会大获成功——这是一个 1-1 的对应关系。

于 2009-06-12T15:59:31.037 回答
2

让我们说列表中的每个元素都是图中的一个节点。如果两个节点可以一起选择,则它们之间存在一条边(它们没有共同的符号)。同一个列表的两个节点之间没有边。如果我们有 n 个列表,问题是在该图中找到大小为 n 的团的数量。没有大于 n 个元素的团。鉴于找出是否存在至少一个这样的集团是 np-complete 我认为这个问题是 np-complete。见:http ://en.wikipedia.org/wiki/Clique_problem

正如所指出的,我们必须证明解决这个问题可以解决 Clique 问题,以证明这是 NP 完全的。如果我们可以计算所需集合的数量,即 n 大小的团的数量,那么我们就知道是否至少存在一个大小为 n 的团。不幸的是,如果没有大小为 n 的团,那么我们不知道是否存在大小为 k < n 的团。

另一个问题是我们是否可以表示这个问题中的任何图。我想是的,但我不确定。

我仍然觉得这是 NP-Complete

于 2009-06-11T20:53:17.937 回答
1

虽然这个问题看起来很简单,但它可能与 NP-complete Set Cover Problem相关。因此,可能没有有效的方法来检测有效组合,因此也没有有效的方法来计算它们。

更新

我考虑了列表项beeing pair,因为它似乎使问题更难攻击——你必须检查一个项目的两个属性。因此,我寻找一种方法将这对减少为一个标量项并找到了一种方法。

n将符号集映射到第一个n素数集 - 我将调用此函数M。例如,在符号的情况下,0我们9获得以下映射M(4) = 11

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} => {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}

现在我们可以(n, m)使用映射 X 将一对映射到 和 的映射的n乘积m。这会将这对(2, 5)变成X((2, 5)) = M(2) * M(5) = 5 * 13 = 65.

X((n, m)) = M(n) * M(m)

为什么这一切?如果我们有两个对(a, b),并且(c, d)来自两个列表,使用映射映射它们并将X它们x相乘y,我们得到乘积x * y = M(a) * M(b) * M(c) * M(d)- 四个素数的乘积。2w如果我们有列表,我们可以通过从每个列表中选择一对来将乘积扩展更多因子,并获得素数的乘积w。最后一个问题是这个产品告诉我们什么关于我们选择和相乘的配对?如果所选对形成有效选择,我们从不会两次选择一个符号,因此该产品两次不包含素数并且是无平方的。如果选择无效,则乘积至少包含一个素数两次并且不是无平方数。这是最后一个例子。

X((2, 5)) = 5 * 13 = 65
X((3, 6)) = 7 * 17 = 119
X((3, 4)) = 7 * 11 = 77

选择2536产生65 * 119 = 7735 = 5 * 7 * 13 * 17并且是自由的,因此有效。选择3634产生119 * 77 = 9163 = 7² * 11 * 17并且不是自由的,因此无效。

还要注意这很好地保留了对称性 - X((m, n)) = X((n, m)) - 并禁止对称对,因为X((m, m)) = M(m) * M(m)它不是自由的。

我不知道这是否会有所帮助,但现在你知道了,可以考虑一下......^^


这是将 3-SAT 问题简化为该问题的第一部分。3-SET 问题如下。

(!A | B | C) & (B | !C | !D) & (A | !B)

这是我得到的减少。

  • mn 代表一对
  • 一行代表一个列表
  • 星号代表任意的唯一符号

A1-A1'    !A1-!A1'     => Select A true or false
B1-B1'    !B1-!B1'     => Select B true or false
C1-C1'    !C1-!C1'     => Select C true or false
D1-D1'    !D1-!D1'     => Select D true or false

A1-*   !B1-*   !C1-*   => !A | B | C

A2-!A1'   !A2-A1'      => Create a copy of A
B2-!B1'   !B2-B1'      => Create a copy of B
C2-!C1'   !C2-C1'      => Create a copy of C
D2-!D1'   !D2-D1'      => Create a copy of D

!B2-*  C2-*    D2-*    => B | !C | !D

(How to perform a second copy of the four variables???)

!A3-*  B3-*

如果我(或其他人)可以完成这个减少并展示如何在一般情况下做到这一点,这将证明问题 NP 完全。我只是坚持第二次复制变量。

于 2009-06-11T20:30:03.517 回答
1

我要说的是,除了蛮力之外,您无法进行任何计算,因为必须评估一个函数来决定是否可以使用集合 B 中的项目给定集合 A 中选择的项目。简单的组合数学不会工作。

您可以使用记忆和散列将计算速度提高 1 到 2 个数量级。

记忆是记住类似蛮力路径的先前结果。如果您在列表 n 中并且您已经使用了符号 x、y、z 并且之前遇到过这种情况,那么您将从剩余列表中添加相同数量的可能组合。如何使用 x,y,z 列出 n并不重要。因此,如果有缓存结果,请使用缓存结果,或者继续计算到下一个列表并检查那里。如果您使用蛮力递归算法来计算结果,但缓存结果,则效果很好。

保存结果的关键是:当前列表,以及已经使用的符号。对符号进行排序以制作钥匙。我认为字典或字典数组在这里是有意义的。

使用散列来减少每个列表中需要搜索的对数。对于每个列表,如果已经使用了一定数量的符号,则对可用的对进行哈希处理。根据您要使用的内存量和要花费预计算的时间来选择要在哈希中使用的已消耗符号的数量。我认为使用 1-2 个符号会很好。按其中的项目数对这些哈希进行排序......升序,然后保留前 n 个。我说把剩下的扔掉,因为如果散列只减少了你的工作量,它可能不值得保留(如果有更多的散列,找到散列需要更长的时间)。因此,当您浏览列表时,您可以快速扫描列表的哈希,以查看您是否在哈希中使用了符号。如果你有,然后使用出现的第一个哈希来扫描列表。第一个哈希将包含最少的扫描对。如果你真的很方便,你可以随时构建这些哈希值,而不是浪费时间来做这件事。

您也许可以折腾散列并使用树,但我的猜测是填充树将需要很长时间。

于 2009-06-12T20:12:47.260 回答
1

如果您想生成所有组合,约束编程是一种不错的方法。只是为了尝试一下,我使用Gecode(3.2.2 版)编写了一个模型来解决您的问题。给出的两个例子很容易解决,但其他例子可能更难解决。在任何情况下,它都应该比生成和测试要好。

/*
 *  Main authors:
 *     Mikael Zayenz Lagerkvist <lagerkvist@gecode.org>
 *
 *  Copyright:
 *     Mikael Zayenz Lagerkvist, 2009
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
#include <gecode/driver.hh>
#include <gecode/int.hh>
#include <gecode/minimodel.hh>

using namespace Gecode;

namespace {
  /// List of specifications
  extern const int* specs[];
  /// Number of specifications
  extern const unsigned int n_specs;
}


/**
 * \brief Selecting pairs
 *
 * Given a set of lists of pairs of values, select a pair from each
 * list so that no value is selected more than once.
 *
 */
class SelectPairs : public Script {
protected:
  /// Specification
  const int* spec;
  /// The values from all selected pairs
  IntVarArray s;
public:
  /// The actual problem
  SelectPairs(const SizeOptions& opt)
    : spec(specs[opt.size()]),
      s(*this,spec[0] * 2,Int::Limits::min, Int::Limits::max) {

    int pos = 1; // Position read from spec
    // For all lists
    for (int i = 0; i < spec[0]; ++i) {
      int npairs = spec[pos++];
      // Construct TupleSet for pairs from list i
      TupleSet ts;
      for (int p = 0; p < npairs; ++p) {
        IntArgs tuple(2);
        tuple[0] = spec[pos++];
        tuple[1] = spec[pos++];
        ts.add(tuple);
      }
      ts.finalize();

      // <s[2i],s[2i+1]> must be from list i
      IntVarArgs pair(2);
      pair[0] = s[2*i]; pair[1] = s[2*i + 1];
      extensional(*this, pair, ts);
    }

    // All values must be pairwise distinct
    distinct(*this, s, opt.icl());

    // Select values for the variables
    branch(*this, s, INT_VAR_SIZE_MIN, INT_VAL_MIN);
  }

  /// Constructor for cloning \a s
  SelectPairs(bool share, SelectPairs& sp) 
    : Script(share,sp), spec(sp.spec) {
    s.update(*this, share, sp.s);
  }

  /// Perform copying during cloning
  virtual Space*
  copy(bool share) {
    return new SelectPairs(share,*this);
  }

  /// Print solution
  virtual void
  print(std::ostream& os) const {
    os << "\t";
    for (int i = 0; i < spec[0]; ++i) {
      os << "(" << s[2*i] << "," << s[2*i+1] << ") ";
      if ((i+1) % 10 == 0)
        os << std::endl << "\t";
    }
    if (spec[0] % 10)
      os << std::endl;
  }
};

/** \brief Main-function
 *  \relates SelectPairs
 */
int
main(int argc, char* argv[]) {
  SizeOptions opt("SelectPairs");
  opt.iterations(500);
  opt.size(0);
  opt.parse(argc,argv);
  if (opt.size() >= n_specs) {
    std::cerr << "Error: size must be between 0 and "
              << n_specs-1 << std::endl;
    return 1;
  }
  Script::run<SelectPairs,DFS,SizeOptions>(opt);
  return 0;
}

namespace {
  const int s0[] = {
    // Number of lists
    3,
    // Lists (number of pairs, pair0, pair1, ...)
    5,  1,2,  1,3,  1,4,  2,3,  2,4,
    3,  1,4,  1,5,  2,5,
    5,  1,6,  1,7,  2,5,  2,6,  3,6
  };

  const int s1[] = {
    // Number of lists
    3,
    // Lists (number of pairs, pair0, pair1, ...)
    5,  1,2,  2,3,  3,4,  4,5,  6,7,
    5,  1,2,  2,3,  3,4,  4,5,  6,7,
    5,  1,2,  2,3,  3,4,  4,5,  6,7
  };

  const int *specs[] = {s0, s1};
  const unsigned n_specs = sizeof(specs)/sizeof(int*);
}
于 2009-12-16T13:23:39.067 回答
0

首先尝试.. 这是一种算法,其平均复杂度比蛮力降低了。本质上,您在每次迭代中创建长度增加的字符串。这可能不是最好的解决方案,但我们会等待最好的解决方案出现...... :)

从列表 1 开始。该列表中的所有条目都是长度为 2 (#=5) 的有效解决方案接下来,当您引入列表 2 时。记录所有长度为 4 的有效解决方案,最终为 {1425,2314,2315 , 2415} (#=4)。

当您将第三个列表添加到组合中时,重复该过程。您最终会得到 {142536, 241536} (#=2)。

降低复杂性是因为您在每次迭代中都丢弃了错误的字符串。最坏的情况恰好仍然是相同的——在所有对都不同的情况下。

于 2009-06-11T20:31:13.480 回答
0

这感觉像是一个应用约束规划方法的好问题。在 Wikipedia 提供的软件包列表中,我要补充一点,我过去曾有过使用Gecode的良好经验;Gecode 示例还提供了约束编程的基本教程。 如果您想更深入地了解算法的工作原理,《约束处理》是一本关于该主题的好书。

于 2009-06-11T20:47:35.690 回答