查找总和为某个所需数字的数字组合

时间:2010-07-29 13:59:50

标签: perl math

我需要一种算法来识别一组数字的所有可能组合,这些数字总和到其他数字。

例如,给定集合{2,3,4,7},我需要知道总和为x的所有可能子集。如果x == 12,则答案为{2,3,7};如果x ==7答案是{{3,4},{7}}(即两个可能的答案);如果x==8没有答案。请注意,正如这些示例所暗示的那样,集合中的数字无法重复使用。

这个问题was asked on this site a couple years ago但答案是在C#中,我需要在Perl中完成,并且不太了解翻译答案。

我知道这个问题很难(请参阅其他帖子讨论),但我只需要一个强力解决方案,因为我处理的是相当小的集合。

6 个答案:

答案 0 :(得分:4)

sub Solve
{
  my ($goal, $elements) = @_;

  # For extra speed, you can remove this next line
  # if @$elements is guaranteed to be already sorted:
  $elements = [ sort { $a <=> $b } @$elements ];

  my (@results, $RecursiveSolve, $nextValue);

  $RecursiveSolve = sub {
    my ($currentGoal, $included, $index) = @_;

    for ( ; $index < @$elements; ++$index) {
      $nextValue = $elements->[$index];
      # Since elements are sorted, there's no point in trying a
      # non-final element unless it's less than goal/2:
      if ($currentGoal > 2 * $nextValue) {
        $RecursiveSolve->($currentGoal - $nextValue,
                          [ @$included, $nextValue ],
                          $index + 1);
      } else {
        push @results, [ @$included, $nextValue ]
            if $currentGoal == $nextValue;
        return if $nextValue >= $currentGoal;
      }
    } # end for
  }; # end $RecursiveSolve

  $RecursiveSolve->($goal, [], 0);
  undef $RecursiveSolve; # Avoid memory leak from circular reference
  return @results;
} # end Solve


my @results = Solve(7, [2,3,4,7]);
print "@$_\n" for @results;

这开始是the C# version from the question you linked的一个相当直接的翻译,但我对它进行了简化(现在多了一点,并且还删除了一些不必要的变量分配,根据被排序的元素列表添加了一些优化,并重新安排条件以提高效率。)

我现在还添加了另一项重要优化。在考虑是否尝试使用不完成总和的元素时,如果元素大于或等于当前目标的一半则没有意义。 (我们添加的下一个数字会更大。)根据您尝试的设置,这可能会短路更多。 (您也可以尝试添加下一个元素而不是乘以2,但是您必须担心在列表末尾运行。)

答案 1 :(得分:1)

您可以使用Data::PowerSet模块生成元素列表的所有子集:

答案 2 :(得分:1)

使用Algorithm::Combinatorics。这样,您可以提前决定要考虑的子集大小,并将内存使用保持在最低限度。应用一些启发式方法尽早返回。

#!/usr/bin/perl

use strict; use warnings;
use List::Util qw( sum );
use Algorithm::Combinatorics qw( combinations );

my @x = (1 .. 10);
my $target_sum = 12;

{
    use integer;
    for my $n ( 1 .. @x ) {
        my $iter = combinations(\@x, $n);
        while ( my $set = $iter->next ) {
            print "@$set\n" if $target_sum == sum @$set;
        }
    }
}

这些数字确实很快爆发:要花费数千天才能完成40个元素集的所有子集。所以,你应该决定有趣的子集大小。

答案 3 :(得分:0)

粗略的算法如下:

有一个“解决”功能,它包含已包含的数字列表和尚未包含的数字列表。

  • 此功能将循环显示尚未包含的所有数字。
  • 如果在点击目标时添加该数字,则记录该组数字并继续,
  • 如果它小于目标,则递归调用带有您正在查看的数字修改的包含/排除列表的函数。
  • 其他只是转到循环中的下一步(因为如果你结束了,除非你允许添加更多数字,否则没有必要添加更多数字)
  • 您最初调用此功能时,其中包含的列表为空,而您尚未包含的列表包含完整的数字列表。

您可以对此进行优化,例如传递总和而不是每次重新计算。此外,如果您最初对列表进行排序,则可以根据以下事实进行优化:如果在列表中添加数字k使您超过目标,则添加k + 1也会使您超过目标。

希望这会给你一个足够好的开始。我的perl非常生锈。

虽然这是一个蛮力算法,但有一些快捷方式,所以它永远不会那么高效。

答案 4 :(得分:0)

这是'为我做功课'这个问题吗?

要做到这一点,确定性地需要N阶算法! (即(N-0)*(N-1)*(N-2)...)随着大量输入将变得非常慢。但算法非常简单:计算出集合中输入的每个可能序列,并尝试将序列中的输入相加。如果总和匹配,你有一个答案,保存结果并继续下一个序列。如果在任何点上总和大于目标,则放弃当前序列并继续前进到下一个序列。

您可以通过删除任何大于目标的输入来优化此项。另一种优化方法是在序列中获取第一个输入I并创建一个新序列S1,从目标T中扣除I以获得新目标T1,然后检查S1中是否存在T,如果存在则那么你'得到一个匹配,否则重复S1和T1的过程。订单仍然是N!虽然。

如果您需要使用大量数字进行此操作,那么我建议您阅读遗传算法。

下进行。

答案 5 :(得分:0)

前一段时间有人发布了一个类似的问题,而另一个人则展示了一个巧妙的shell技巧来回答它。这是一种shell技术,但我认为它不像我之前看到的那样整洁(所以我不赞成这种方法)。它很可爱,因为它利用了shell扩展:

for i in 0{,+2}{,+3}{,+4}{,+7}; do
  y=$(( $i )); # evaluate expression
  if [ $y -eq 7 ]; then
    echo $i = $y;
  fi;
done

输出:

0+7 = 7
0+3+4 = 7