子集Sum浮动Elimations

时间:2016-06-30 14:31:41

标签: php python algorithm dynamic-programming subset-sum

我很乐意得到一些帮助。我有以下问题: 我给出了一个数字列表和一个目标号码。

subset_sum([11.96,1,15.04,7.8,20,10,11.13,9,11,1.07,8.04,9], 20)

我需要找到一种算法,它会找到所有合并的数字,它们总和目标数量为20:20。 首先发现所有int等于20 接下来举例来说,最好的组合是:

  
      
  • 11.96 + 8.04
  •   
  • 1 + 10 + 9
  •   
  • 11.13 + 7.8 + 1.07
  •   
  • 9 + 11
  •   

剩余价值15.04。

我需要一个只使用1个值的算法,它可以使用1到n的值来总和目标数。

我在PHP中尝试了一些递归但是内存耗尽非常快(50k值),因此Python中的解决方案将有所帮助(时间/内存方面)。 我很乐意在这里提供一些指导。

一种可能的解决方案是:Finding all possible combinations of numbers to reach a given sum

唯一的区别是我需要在已使用的元素上放置一个标记,这样它就不会被使用两次,我可以减少可能的组合数量

感谢愿意提供帮助的人。

4 个答案:

答案 0 :(得分:0)

有很多方法可以考虑这个问题。 如果您进行递归,请务必先确定最终案例,然后继续执行该程序的其余部分。

这是我想到的第一件事。

<?php
subset_sum([11.96,1,15.04,7.8,20,10,11.13,9,11,1.07,8.04,9], 20);


function subset_sum($a,$s,$c = array())
{
        if($s<0)
                return;
        if($s!=0&&count($a)==0)
                return;
        if($s!=0)
        {
                foreach($a as $xd=>$xdd)
                {
                        unset($a[$xd]);
                        subset_sum($a,$s-$xdd,array_merge($c,array($xdd)));
                }
        }
        else
                print_r($c);

}
?>

答案 1 :(得分:0)

这是可能的解决方案,但它并不漂亮:

import itertools
import operator
from functools import reduce

def subset_num(array, num):
    subsets = reduce(operator.add, [list(itertools.combinations(array, r)) for r in range(1, 1 + len(array))])
    return [subset for subset in subsets if sum(subset) == num]


print(subset_num([11.96,1,15.04,7.8,20,10,11.13,9,11,1.07,8.04,9], 20))

输出:

[(20,), (11.96, 8.04), (9, 11), (11, 9), (1, 10, 9), (1, 10, 9), (7.8, 11.13, 1.07)]

答案 2 :(得分:0)

免责声明:这不是一个完整的解决方案,它只是一种帮助您构建可能的子集的方法。它并没有帮助您选择哪些一起(不多次使用相同的项目并获得最低的余数)。

使用动态编程,你可以构建所有加起来给定总和的子集,然后你需要通过它们找到最适合你的子集组合。

要构建此存档,您可以(我假设我们只处理非负数)将项目放在一列中,从上到下依次为每个元素计算所有子集加起来的总数或数字少于它,并且只包括您正在查看或更高的地方的列中的项目。当您构建子集时,您在其节点中放置子集的总和(可以是给定的总和或更小)和子集中包含的项。因此,为了计算项目[i]的子集,您只需要查看为项目[i-1]创建的子集。对于他们每个人有3个选项:

1)子集的总和是给定的总和---&gt;保持子集不变,然后移动到下一个。

2)子集的总和小于给定的总和但是如果将项[i]加到其上则大于它---&gt;保持子集不变,然后继续下一个。

3)子集的总和小于给定的总和,如果添加item [i],它仍然会小于或等于它---&gt;保留子集的一个副本,并创建另一个添加了项目[i]的副本(作为成员并添加到子集的总和)。

当您完成最后一项(item [n])时,请查看您创建的子集 - 每个子集在其节点中都有其总和,您可以看到哪些等于给定总和(哪些更小 - 你不再需要那些)。

正如我在开头写的那样 - 现在你需要弄清楚如何在其中任何一个之间没有共享成员的子集的最佳组合。 基本上你留下的问题类似于经典的背包问题,但有另一个限制(不是每一块石头都可以与其他石头一起使用)。也许限制实际上有帮助,我不确定。

更多关于动态编程在这种情况下的优势

动态编程而不是递归的基本思想是用占用内存空间来交换操作冗余。我的意思是说复杂问题的递归(通常是像我们这里一样的回溯背包式问题)通常最终计算相同的事情相当多,因为不同的计算分支没有彼此的概念& #39;操作和结果。动态编程可以保存结果,并在构建“更大”的过程中使用它们。结果,依靠之前的/&#34;较小的&#34;那些。因为堆栈的使用比递归更简单,所以你不会因为函数状态的维护而得到关于递归的内存问题,但是你需要处理大量的你存储的内存(有时你可以优化它)。

因此,例如在我们的问题中,尝试组合将加起来所需总和的子集,以项目A开头的分支和以项目B开头的分支不知道彼此的操作。让我们假设项目C和项目D一起加起来,但是他们中的任何一个单独添加到A或B都不会超过总和,并且A不会在解决方案中使用B(我们可以得到sum = 10,A = B = 4,C = D = 5并且没有总和最多为2的子集(因此A和B不能在同一组中))。试图找出A组的分支(在尝试并拒绝在其组中有B之后)添加C(A + C = 9)然后添加D,在这一点上将拒绝该组和引用(A + C + D = 14> sum = 10)。 B当然会发生同样的情况(A = B),因为找出B组的分支没有关于A处理A的事件的信息。事实上我们已经计算了C + D两次,还没有使用它(我们还要第三次计算它,以便意识到它们属于他们自己的一组)。

注意: 在写这个答案时环顾四周,我遇到了一种我不熟悉的技术,可能是一个更好的解决方案:memoization。取自wikipedia

  

memoization是一种优化技术,主要用于通过存储昂贵的函数调用的结果来加速计算机程序,并在再次出现相同的输入时返回缓存的结果。

答案 3 :(得分:0)

所以我有一个可能的解决方案:

    #compute difference between 2 list but keep duplicates
    def list_difference(a, b):
        count = Counter(a) # count items in a
        count.subtract(b)  # subtract items that are in b
        diff = []
        for x in a:
            if count[x] > 0:
               count[x] -= 1
               diff.append(x)
        return diff


    #return combination of numbers that match target   
    def subset_sum(numbers, target, partial=[]):
        s = sum(partial)
        # check if the partial sum is equals to target
        if s == target:
            print "--------------------------------------------sum_is(%s)=%s" % (partial, target)
            return partial
        else:
            if s >= target:
                return  # if we reach the number why bother to continue

            for i in range(len(numbers)):
                n = numbers[i]
                remaining = numbers[i+1:]
                rest = subset_sum(remaining, target, partial + [n])
                if type(rest) is list:
  #repeat until rest is > target and rest is not the same as previous
    def repeatUntil(subset, target):
       currSubset = []
       while sum(subset) > target and currSubset != subset:
        diff = subset_sum(subset, target)
        currSubset = subset
        subset = list_difference(subset, diff)
    return subset

输出:

--------------------------------------------sum_is([11.96, 8.04])=20
--------------------------------------------sum_is([1, 10, 9])=20
--------------------------------------------sum_is([7.8, 11.13, 1.07])=20
--------------------------------------------sum_is([20])=20
--------------------------------------------sum_is([9, 11])=20
[15.04]

不幸的是,此解决方案适用于小型列表。对于仍然试图以小块打破列表并计算的大清单,但答案并不完全正确。你可以在这里看到一个新线程: Finding unique combinations of numbers to reach a given sum