【问题标题】:Find smallest subset sum matching another subset sum找到与另一个子集和匹配的最小子集和
【发布时间】:2011-06-22 17:44:13
【问题描述】:

我有一个现实问题(不是家庭作业!),需要找到集合 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 中只出现一次。恐怕我对算法的理解不够深入,无法理解为什么会发生这种情况。

我们将不胜感激!

【问题讨论】:

  • ?为什么sum(2000) = sum(2000) 不是正确答案?为什么sum(4000)=sum(2000 2000) 不是正确答案?
  • @mob - 你是对的,他们应该是。另一个问题中给出的算法似乎不太正确。 (也许这个问题是正确的,但我看不出有什么区别。)你的两个答案也是正确的,我希望算法也能提供这些。谢谢。
  • @itzy:感谢您再次注意到我的代码存在的问题!我希望您的问题得到您满意的解决。类似于我的回答中的动态编程(除了正确的......)应该已经解决了你的问题。保重!

标签: algorithm perl subset-sum


【解决方案1】:

这个问题是 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])]

编辑:修正了一个错字,而且刚刚注意到很多人已经很快回答了这个问题。

【讨论】:

  • 你可以只保留最低限度,你不需要所有的集合 - 只需要最低限度。在桶 k,您不需要知道集合中项目的实际值,这就是伪多边形算法完全起作用的原因。在最坏的情况下,保留所有的集合会导致时间和空间呈指数增长。
  • 更准确地说,通过保留所有的集合,在最坏的情况下它不是psuedo-poly,如果你只是保持最小,它仍然是psuedo-poly
  • @spinning_plate:你怎么看?这仅使用 O(n * (|a|+|b|)) 空间,其中 n 是最大可能和的大小,|a|, |b|分别是 a 和 b 的长度。这仍然应该是 n 上的伪多项式。至于时间复杂度,它每个元素迭代一次数组,所以时间复杂度应该不大于 O(n * (|a| + |b|)^2),这又是伪多边形。
  • 作为附录,如果你仔细观察它,你会发现它只跟踪最小集,所以我不太清楚你的意思。
  • 感谢您的代码。我的 Python 比我的 Perl 还要糟糕,但我会尝试将它移植到 Perl。如果这对您来说很容易,我将非常感谢您的帮助!不过非常感谢。
【解决方案2】:

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

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,

保罗

【讨论】:

  • 感谢您的帮助。看起来这段代码仍然给出 sum(200 4000 4000) 作为答案之一,但 4000 在输入集中只出现一次。 2000+2000 是否以某种方式保存为 4000?
  • @irzy:你是对的;那是因为列表中有 2 x 2000 ......现在不知道如何处理:(
【解决方案3】:

您需要修改递归关系,而不仅仅是输出。考虑{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 返回较小的集合

【讨论】:

  • 您的意思是 {1,2,20,23,42} 和 {43}?
【解决方案4】:

我不擅长 perl。但是,

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

}

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

【讨论】:

    猜你喜欢
    • 2022-11-13
    • 1970-01-01
    • 1970-01-01
    • 2022-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-19
    • 1970-01-01
    相关资源
    最近更新 更多