【问题标题】:Partition and Composition (combinatorics) implementation in C++C++ 中的分区和组合(组合)实现
【发布时间】:2013-07-04 04:13:03
【问题描述】:

给定一个大小为MN 的矩阵,我们希望用整数值 (>=0) 填充每一行,使其总和为某个值。

请注意MN 的维度是使用特定公式预先计算的,因此可以保证在给定所需条件(即下面的 sum_val)的情况下匹配填充。

这是在 R 中的 Partition library 下实现的。

library(partitions)

# In this example, we impose condition 
# that each rows must sum up to 2 in total
# And each row has 5 columns
sum_val <- 2
n <- 5
#The above two parameters are predefined.

t(as.matrix(compositions(sum_val, n)))
      [,1] [,2] [,3] [,4] [,5]
 [1,]    2    0    0    0    0
 [2,]    1    1    0    0    0
 [3,]    0    2    0    0    0
 [4,]    1    0    1    0    0
 [5,]    0    1    1    0    0
 [6,]    0    0    2    0    0
 [7,]    1    0    0    1    0
 [8,]    0    1    0    1    0
 [9,]    0    0    1    1    0
[10,]    0    0    0    2    0
[11,]    1    0    0    0    1
[12,]    0    1    0    0    1
[13,]    0    0    1    0    1
[14,]    0    0    0    1    1
[15,]    0    0    0    0    2

C++ 中有没有现成的实现?

【问题讨论】:

  • 你的意思是你需要满足你的约束的随机值?试试this
  • @LeeDanielCrocker:不只是任何随机数组合。它必须是详尽的。
  • 啊,所以你想要每个分区,随机顺序,还是某个预定义的顺序?
  • @LeeDanielCrocker:每个分区,都不需要顺序。
  • 文森特说得对,如下。可以通过取连续差分从 n-1 个组合中计算出 n 个分区。

标签: c++ r math combinatorics


【解决方案1】:

递归版本

这是一个递归解决方案。您有一个序列a,您可以在其中跟踪您已经设置的数字。每个递归调用都会在循环中为这些元素之一分配有效数字,然后再为列表的其余部分递归调用该函数。

void recurse(std::vector<int>& a, int pos, int remaining) {
  if (remaining == 0) { print(a); return; }
  if (pos == a.size()) { return; }
  for (int i = remaining; i >= 0; --i) {
    a[pos] = i;
    recurse(a, pos + 1, remaining - i);
  }
}

void print_partitions(int sum_val, int n) {
  std::vector<int> a(n);
  recurse(a, 0, sum_val);
}

http://ideone.com/oJNvmu 可见运行的概念证明。

迭代版本

您在下面的评论表明存在性能问题。虽然 I/O 很可能会占用您的大部分性能,但这里有一个迭代解决方案,可以避免递归方法的函数调用开销。

void print_partitions(int sum_val, int n) {
  int pos = 0, last = n - 1;
  int a[n]; // dynamic stack-allocated arrays are a gcc extension
  for (int i = 1; i != n; ++i)
    a[i] = 0;
  a[0] = sum_val;
  while (true) {
    for (int i = 0; i != last; ++i)
        printf("%3d ", a[i]);
    printf("%3d\n", a[last]);
    if (pos != last) {
      --a[pos];
      ++pos;
      a[pos] = 1;
    }
    else {
      if (a[last] == sum_val)
        return;
      for (--pos; a[pos] == 0; --pos);
      --a[pos];
      int tmp = 1 + a[last];
      ++pos;
      a[last] = 0;
      a[pos] = tmp;
    }
  }
}

总体思路和打印顺序与递归方法相同。与其维护计数器remaining,不如立即将所有令牌(或您正在分区的任何令牌)放到它们所属的位置,以便打印下一个分区。 pos 始终是最后一个非零字段。如果这不是最后一个,那么您通过从pos 获取一个令牌并将其移动到之后的位置来获得下一个分区。如果它是最后一个,那么你从最后一个位置取出所有标记,找到之前最后一个非零位置并从那里取出一个标记,然后将所有这些标记转储到你拿单的那个位置之后的位置令牌。

演示在http://ideone.com/N3lSbQ运行。

【讨论】:

  • @neversaint:起初我认为(和其他一些人一样)您要求的是 random 分区。现在我知道了,我已经修改了我的答案。所以这是一篇与我原来的完全不同的帖子。希望对您有所帮助。
  • 你救了我的命!非常感谢!我从你的帖子中学到了很多。
  • 有什么办法可以加快算法的速度吗?我试过sum_val=150n=5。 R 代码采用 real 0m3.931s 和 C++ 0m23.437s
  • @neversaint:一些想法:您可以通过优化进行编译。您可以延迟输出,直到获得所有数据。您可以尝试不同的方式来打印结果。您可以捕获pos == a.size()-1 的情况并将剩余计数分配给最后一个字段,从而避免那里的循环。您可以用循环替换递归函数调用,尽管这会更难阅读。如果n 始终为 5,您可以像文森特建议的那样对其进行硬编码。您可以查看 R 实现并复制它。
  • @neversaint:我还添加了一个迭代解决方案。但它看起来并没有那么快。您的时间测量是否包括打印结果?为您提到的案例格式化和打印 22.533.126 行将需要一些时间,如果 R 能在这方面做得更好,我会感到惊讶。但我想你不需要打印这些东西,而是用它们做一些其他的计算。这里的原始计算是0.082s,而不是12.681s,输出到wc
【解决方案2】:

您可以自己实现它: 这样的分区由 6 个整数 0 &lt;= x[0] &lt;= x[1] &lt;= x[2] &lt;= x[3] &lt;= 2 定义; 对应行中的值只是x[0]-0x[1]-x[0]x[2]-x[1] 等的差异。 如果列数 (5) 是固定的,则有 4 个嵌套循环; 不是的,你可以递归地制定问题。

【讨论】:

    猜你喜欢
    • 2012-08-06
    • 2015-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多