找到与另一个子集和匹配的最小子集和

时间:2011-06-22 17:44:13

标签: algorithm perl subset-sum

我有一个现实世界的问题(不是家庭作业!),需要找到集合A的子集的总和,它等于某个其他集合B的子集的总和。

一个非常相似的问题,并提供了有用的答案is here

考虑这个例子:

@a = qw(200 2000 2000 2000 4000);
@b = qw(528 565 800 1435 2000 2000 2872);

使用在该问题的接受答案中提供的the code,我得到以下输出:

sum(200 2000 4000) = sum(528 800 2000 2872)
sum(200 4000 4000) = sum(528 800 2000 2000 2872)
sum(200 4000) = sum(528 800 2872)
sum(200 2000 2000 2000 4000) = sum(528 800 2000 2000 2000 2872)

出于我的目的,我只想要使用输入集中最少元素的答案。在这个例子中,我只想要

sum(200 4000) = sum(528 800 2872)

因为所有其他答案的总和中还有2004000。也就是说,我正在寻找“最简单”的可能总和(在他们使用最少元素的意义上)。有人可以建议一个合理有效的方法吗? (蛮力是可以的,因为我的阵列不是那么大。)

此外,我应该注意,输出的第二行sum(200 4000 4000) ...不正确,因为4000仅在@a中出现一次。我恐怕我不太了解这个算法,看看为什么会发生这种情况。

非常感谢任何一种帮助!

4 个答案:

答案 0 :(得分:3)

这个问题是NP完全的,所以除非P = NP,否则你会陷入对输入大小的指数式工作。现在关于这类问题的巧妙之处在于,实际上有两种解决方法可以将指数放在问题的不同方面。

首先,如果您的总和没有太多元素,您可以通过搜索所有组合来强制解决此问题。这个方法对集合中元素的数量是指数级的,并且每个容器可以合理地使用20个左右的元素。之后会变得非常讨厌。

第二种选择是使用动态编程。与前面的方法不同,该算法在写出每个数字所需的位数方面是指数级的。您所做的是跟踪所有可能总和的集合以及生成它们所需的最小元素数量。这是您在答案中链接到的解决方案的简单修改。

这是一些python代码,它们可以生成所有可能相交的值:

    def gen_sum(a):
        r = { 0: [] }
        for x in a:
            for y, v in r.items():
                if not (x+y) in r or len(r[x+y]) > len(v) + 1:
                    r[x+y] = v + [x]
        return r

    def gen_sums(a, b):
        asum = gen_sum(a)
        bsum = gen_sum(b)
        return [ (k, v, bsum[k]) for k,v in asum.items() if k in bsum ][1:]

在你的样本数据上运行它,我得到了:

  

[(4000,[4000],[2000,2000]),(6000,[2000,4000],[565,1435,2000,2000]),(2000,[2000],[2000]), (4200,[200,4000],[528,800,2872]),(10200,[200,2000,2000,2000,4000],[528,565,800,1435,2000,2000,2872]), (8200,[200,2000,2000,4000],[528,800,2000,2000,2872]),(6200,[200,2000,4000],[528,800,2000,2872])]

编辑:修正了一个拼写错误,并且刚刚注意到那些神圣的垃圾已经很快回答了这个问题。

答案 1 :(得分:1)

您需要修改递归关系,而不仅仅是输出。考虑{1,2,20,23,42}{45}。原始算法将输出{1,2,42},{45}而不是{20,23},{45}。这是因为42被认为是最后一个,当它总和为45时,它将覆盖先前包含{20,23}的45的桶值

不是保留每个值的[set,sum],而是需要保持[minimum set,sum],然后在最后得到最小值。

我的perl很糟糕,但是像这样的事情

$a{$m+$a} = [min-length(@{$a{$m}},$a{$m+$a}[0]),$a];

其中min-length返回较小的集合

答案 2 :(得分:1)

这是一个更新的算法,它给出了所有的总和:

my @a = qw(200 2000 2000 2000 4000);
my @b = qw(528 565 800 1435 2000 2000 2872);

my %a = sums( @a );
my %b = sums( @b );

for my $m ( keys %a ) {
    print "sum(@{$a{$m}}) = sum(@{$b{$m}})\n" if exists $b{$m};
}

sub sums {
    my( @a ) = @_;

    return () unless @a;

    my %a;

    while( @a ) {
        $a = shift @a;

        for my $m ( keys %a ) {
            $a{$m+$a} = [@{$a{$m}},$a];
        }

        $a{$a} = [$a];
    }

    return %a;
}

你需要做的就是找到最短的一个,但其他人已经覆盖了那个:)

HTH,

答案 3 :(得分:0)

我对perl不太满意。但是,

for $m ( keys %a ) {
print "sum(@{$a{$m}}) = sum(@{$b{$m}})\n" if exists $b{$m};

}

修改此行以计算每个$ m的集合$ a和$ b中的元素数量。完成所有循环后,选择元素数量最少的那个。