我需要一种算法来识别一组数字的所有可能组合,这些数字总和到其他数字。
例如,给定集合{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中完成,并且不太了解翻译答案。
我知道这个问题很难(请参阅其他帖子讨论),但我只需要一个强力解决方案,因为我处理的是相当小的集合。
答案 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