计算来自多个列表的项目对的组合,而不重复

时间:2009-06-11 19:49:16

标签: algorithm math combinatorics

鉴于我们有多个项目对列表的情况,例如:

  1. {12,13,14,23,24}
  2. {14,15,25}
  3. {16,17,25,26,36}
  4. 其中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}

7 个答案:

答案 0 :(得分:3)

问题是#P-complete。这比NP完全更难。它与查找SAT实例的令人满意的分配数一样困难。

减少来自Perfect matching。假设你有图G = {V, E},其中E,边集,是一对顶点列表(由边连接的那些对)。然后,通过|V|/2E副本对“项目对”的实例进行编码。换句话说,E的副本数量等于顶点数量的一半。现在,在您的情况下,“点击”将对应|V|/2边,没有重复的顶点,这意味着所有|V|顶点都被覆盖。这是完美匹配的定义。每一次完美的匹配都会很受欢迎 - 这是一个1-1的对应关系。

答案 1 :(得分:2)

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

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

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

我仍然觉得这是NP-Complete

答案 2 :(得分:1)

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

<强>更新

我想到了列表项目,因为它似乎使问题更难攻击 - 你必须检查一个项目的两个属性。所以我找了一种方法将对减少到一个标量项并找到了一种方法。

n符号集映射到第一个n素数的集合 - 我将调用此函数M。对于符号09,我们会获得以下映射,例如M(4) = 11

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

现在,我们可以使用映射X将一对(n, m)映射到nm映射的乘积。这会将货币对(2, 5)转换为X((2, 5)) = M(2) * M(5) = 5 * 13 = 65

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

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

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)

就我而言,这是减少。

  • m-n代表一对
  • 一行代表一个列表
  • 星号代表一个abitrary唯一符号

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完成的问题。我只是第二次复制变量了。

答案 3 :(得分:1)

我要说的是除了蛮力之外没有计算可以做的事情因为有一个函数需要进行评估,以决定是否可以使用集合B中的项目给定A中选择的项目。组合数学不会工作。

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

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

保存结果的关键是:当前列表和已使用的符号。对符号进行排序以生成密钥。我认为字典或字典数组在这里是有道理的。

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

您可以抛出哈希并使用树,但我的猜测是填充树需要很长时间。

答案 4 :(得分: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*);
}

答案 5 :(得分:0)

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

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

将第三个列表添加到混音时,请重复此过程。您最终会得到{142536,241536}(#= 2)。

复杂性降低到位,因为你在每次迭代中都丢掉了坏字符串。最坏的情况恰好仍然相同 - 在所有对都不同的情况下。

答案 6 :(得分:0)

这应该是一个应用constraint programming方法的好问题。在维基百科提供的软件包列表中,我将补充说我过去使用过Gecode有很好的经验; Gecode示例还提供了约束编程的基本教程。如果您想深入了解算法的工作原理,Constraint Processing是一本关于这一主题的好书。