【问题标题】:Subset Sum floats Elimations子集总和浮动淘汰赛
【发布时间】:2016-06-30 14:31:40
【问题描述】:

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

subset_sum([11.96,1,15.04,7.8,20,10,11.13,9,11,1.07,8.04,9], 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

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

感谢任何愿意提供帮助的人。

【问题讨论】:

  • 在网上搜索“动态规划子集总和”,玩得开心;)
  • 已经做到了。没有任何解决方案可以帮助我。你能提供一些链接吗?

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


【解决方案1】:

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

这是首先想到的。

<?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);

}
?>

【讨论】:

  • 另外,添加 ini_set('memory_limit','2048M');如果您的内存不足。
【解决方案2】:

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

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)]

【讨论】:

  • 用更大的数据集尝试了您的解决方案:分段错误(核心转储)
  • Python 中的分段错误?你说什么错误?
  • 回溯(最近一次调用最后):文件“test1.py”,第 11 行,在 58,20,259,20,260,20,261,2.36,262,11.41,...], 20 )) 文件“test1.py”,第 6 行,subset_num 子集 = reduce(operator.add, [list(itertools.combinations(array, r)) for r in range(1, 1 + len(array))])内存错误
【解决方案3】:

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

使用动态规划,您可以构建加起来等于给定总和的所有子集,然后您需要遍历它们并找到最适合您的子集组合。

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

1) 子集的总和是给定的总和 ---> 保持子集不变并移动到下一个。

2) 子集的总和小于给定的总和,但如果添加项目 [i] 则大于给定的总和 ---> 保持子集不变并继续下一个。

3) 子集的总和小于给定的总和,如果将项目 [i] 添加到它,它仍然会小于或等于它 ---> 保持子集的一个副本原样并创建另一个将项目 [i] 添加到其中(作为成员并添加到子集的总和中)。

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

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


在这种情况下更多关于动态规划的优势

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

因此,例如在我们的问题中,尝试组合一个子集以达到所需的总和,以项目 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 > 总和 = 10)。 B 当然也会发生同样的情况(A=B),因为找出 B 的组的分支没有关于刚刚处理 A 的分支发生了什么的信息。所以实际上我们已经计算了两次 C+D,并且没有甚至还使用过它(我们将第三次计算它以实现它们属于自己的一组)。


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

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

【讨论】:

    【解决方案4】:

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

        #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

    【讨论】:

      猜你喜欢
      • 2012-06-12
      • 2012-11-25
      • 2012-06-06
      • 2015-06-02
      • 2013-12-18
      • 1970-01-01
      • 2023-04-05
      • 2013-01-24
      • 2012-04-08
      相关资源
      最近更新 更多