【问题标题】:Generate all of the unique combinations of numbers that add up to a certain number生成所有加起来为特定数字的唯一数字组合
【发布时间】:2009-12-29 15:29:50
【问题描述】:

我正在编写一个程序来尝试解决数学问题。我需要生成一个所有数字的唯一列表,这些数字加起来是另一个数字。例如,所有 4 个数字加起来为 5 的唯一组合是:

5 0 0 0
4 1 0 0 
3 2 0 0
3 1 1 0
2 2 1 0
2 1 1 1

这在 perl 中很容易暴力破解,但我正在使用 C 语言并希望找到更优雅的解决方案。

在 perl 中,我会在每列中生成数字 0-N 的所有可能组合,丢弃不加起来等于目标数字的数字,然后对每行中的数字进行排序并删除重复的行。

我整个上午都在尝试用 C 语言编写它,但似乎无法找到令人满意的解决方案。我需要它达到最大 N 约为 25。你们有什么想法吗?

这是我一直在尝试的一种示例(这会产生重复的组合):

// target is the number each row should sum to.
// Don't worry about overflows, I am only using small values for target
void example(int target)
{

  int row[4];

  for (int a=target; a>=0; a--) {
    row[0] = a;

    for (int b=target-a; b>=0; b--) {
      row[1] = b;

      for (int c=target-(a+b); c>=0; c--) {
        row[2] = c;

        row[3] = target-(a+b+c);

        printf ("%2d %2d %2d %2d   sum: %d\n", row[0],row[1],row[2],row[3],
                                          row[0]+row[1]+row[2]+row[3]);
      }
    }
  }
}

【问题讨论】:

  • 例子真的清楚吗?为什么2 2 1 0组合没有列出来?
  • 糟糕,我错过了一个示例组合,刚刚添加了它。
  • 你的数字有多大?最简单的解决方案可能是递归解决方案,但对于较大的数字,这可能会让您陷入本网站命名的那种问题。
  • 不大于约target = 25,列数固定为4

标签: c++ c math


【解决方案1】:

这称为partition problem,并讨论了hereherehere 的方法。

【讨论】:

  • 实际上,我认为这是一种特殊的分区问题,称为“子集和”问题,它有 much 更好的解决方案,一般来说。
  • 不,这不是子集和问题。对于包含面额为 1 的硬币的硬币组,总是有一个解决方案,这是关于使用最小数量的硬币找到解决方案。子集总和是关于给定一组整数和一个目标总和,找到总和等于目标总和的集合的子集。这些是非常不同的问题。子集和是 NP 完全的。
【解决方案2】:

如果列数是固定的,最简单的方法是让数字不递减。

void example(int target) {
  for (int a=0;a<=target;a++)
    for (int b=a;b<=target-a;b++)
      for (int c=b;c<=target-(a+b);c++) {
        int d = target-(a+b+c);
        if(d>=c) printf ("%2d %2d %2d %2d\n",d,c,b,a);
      }
}

对于更一般的情况,首选递归搜索。但不管怎样,这个问题的瓶颈是输出,不算。

【讨论】:

  • 使用非减法的好想法!
【解决方案3】:

请注意,您不需要任何 b>a 或任何 c>b。你可以在你的程序中这样说

for (b ...) {
    if (b > a) continue; /* continue bypasses the following code and
                          * return control to the loop */
    /* ... */
}

另外,你不想要任何负面的row[3]

【讨论】:

  • 我喜欢这个,又好又简单。您还需要检查 row[3] 不超过 c。我在第 [3] 行中的负值没有任何问题。
【解决方案4】:

按排序顺序生成列表,您不必担心删除重复项。

提示:a = (target .. 0), b = (a .. 0), c = (b .. 0), d = (c .. 0)

【讨论】:

  • 这也有效。它需要添加检查以确保组合加起来达到目标​​值。
【解决方案5】:

数字不大于 25,递归解决方案可能没问题。伪代码:

function get_summand_list(number)
{
    if number == 0: return empty list

    for first from number downto 1
    {
        combination = [first_number] + get_summand_list(number - first)
    }
    return all combinations
}

【讨论】:

  • 感谢您的回复,但我不太明白您要做什么。
【解决方案6】:

为了有效地解决这个问题(取模它是一个指数问题),您希望以一种非冗余的方式搜索空间,并修剪不可能的解决方案,或不能导致可能解决方案的部分解决方案,尽快。

最简单的方法是使用递归过程来做到这一点。请注意,如上面的海报所述,您始终可以按排序顺序生成列表,以避免生成重复。因此,您的选择必须始终大于(或小于)最后选择的元素。

此外,要选择的剩余 x min 元素的总和加上当前总数必须小于您的目标值。 IE。如果你选择了加起来为 20 的数字,你必须再选择 3 个元素,你必须让它加起来为 25,而唯一的选择是 1、2 和 3,那么你就不会能够完成列表(它将加起来为 26),因此您可以修剪该解决方案。

所以,在你的递归解决方案中,

  1. 检查您是否正在寻找可行的解决方案。如果没有,只需退出函数。

  2. 如果您完成了一个列表,并且它加起来达到了您的目标,请打印出该列表并退出。

  3. 对于每个有效的剩余选择,将其附加到您当前的解决方案,并使用当前解决方案和当前选择列表递归调用函数。 (您必须在 for 循环的下一次迭代中删除该选项)。

应该会生成它。

我以前写过很多次这种类型的代码,所以我很容易发布解决方案,但我认为这是家庭作业,所以我不打算这样做。然而,以上是对解决方案的相当完整的描述。让它变得简单的诀窍是使用递归解决方案 - 非递归解决方案将非常混乱,因为您需要使用堆栈等基本上模拟递归以获得有效的解决方案。

【讨论】:

  • 这不是家庭作业。我太老了,不适合上学,这只是为了好玩而完成的个人项目的一小部分。我最终可以自己解决这个问题,但对我来说,这不是有趣的部分。 :-) 谢谢你的描述,今天晚些时候我会尝试理解它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-24
  • 1970-01-01
  • 2018-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多