【问题标题】:Algorithm to select a set of numbers to reach a minimum total选择一组数字以达到最小总数的算法
【发布时间】:2011-04-14 21:25:06
【问题描述】:

鉴于 一组数字n[1], n[2], n[3], .... n[x] 还有一个数字M

我想找到最好的组合

n[a] + n[b] + n[c] + ... + n[?] >= M

该组合应达到达到或超过 M 所需的最小值,没有其他组合可以提供更好的结果。

将在 PHP 中执行此操作,因此可以使用 PHP 库。如果没有,只需一个通用算法即可。谢谢!

【问题讨论】:

  • 我能想到的最好例子就是这样平衡。如果有超过 1 个组合产生相同的总价值,我也会选择产品数量最少的组合。
  • 最少要选择的产品数量是 sum{ n[?] + n[c] + ... } where sum > M. 我错了吗?
  • 它的总和 >= M,优先到总和和 M 之间差异最小的集合。如果两个集合的总和相同,则“产品”数量最少的集合获胜

标签: php algorithm math


【解决方案1】:

这看起来像一个经典的Dynamic Programming 问题(其他答案也提到了它与 0-1 背包和子集求和问题的相似性)。整个事情归结为一个简单的选择:对于列表中的每个元素,我们是否在求和中使用它。我们可以编写一个简单的递归函数来计算答案:

f(index,target_sum)=
     0     if target_sum<=0 (i.e. we don't need to add anymore)
     infinity   if target_sum>0 and index is past the length of n (i.e. we have run out of numbers to add)
     min( f(index+1,target_sum), f(index+1,target_sum-n[index])+n[index] )    otherwise (i.e. we explore two choices -  1. take the current number 2. skip over the current number and take their minimum)

由于此函数具有重叠的子问题(它会一遍又一遍地探索相同的子问题),因此最好使用缓存来记忆函数以保存之前已经计算过的值。

这是 Python 中的代码:

#! /usr/bin/env python

INF=10**9 # a large enough number of your choice

def min_sum(numbers,index,M, cache):
    if M<=0: # we have reached or gone past our target, no need to add any more
        return 0
    elif len(numbers)==index: # we have run out of numbers, solution not possible
        return INF
    elif (index,M) in cache: # have been here before, just return the value we found earlier
        return cache[(index,M)]
    else:
        answer=min(
            min_sum(numbers,index+1,M,cache), # skip over this value
            min_sum(numbers,index+1,M-numbers[index],cache)+numbers[index] # use this value
        )
        cache[(index,M)]=answer # store the answer so we can reuse it if needed 
        return answer 

if __name__=='__main__':
    data=[10,6,3,100]
    M=11

    print min_sum(data,0,M,{})

此解决方案仅返回最小总和,而不是用于生成它的实际元素。您可以轻松扩展该想法以将其添加到您的解决方案中。

【讨论】:

    【解决方案2】:

    我认为greedy algorithm 方法会起作用。 你希望这组数字有多少?如果它相当低,您可以尝试backtrack,但我不建议将它用于大型集合。

    【讨论】:

    • 谢谢。我会看一下。目前,我正在查看一个最多包含 136 个值的集合(未来可能会增长)
    • 你应该去回溯,但尝试使用一些技巧来改善执行时间,比如先对集合进行排序,然后再进入回溯。如果您需要帮助,请告诉我
    • 目前,回溯似乎是最好的方法......我需要再研究一下,看看它是否会起作用。谢谢
    • 它可以正常工作,给你正确的结果,但回溯存在问题:很慢。
    【解决方案3】:
    pseudocode:
    
    list<set> results;
    int m;
    list<int> a;
    
    // ...    
    
    a.sort();
    
    for each i in [0..a.size]
        f(i, empty_set);    
    
    // ...
    
    void f(int ind, set current_set)
    {
        current_set.add(a[ind]);
    
        if (current_set.sum > m)
        {
            results.add(current_set);
        }
        else
        {
            for (int i=ind + 1; i<a.size; ++i)
            {
                f(i, current_set);  // pass set by value
    
                // if previous call reached solution, no need to continue
                if (a[ind] + current_set.sum) > m
                    break;
            }
        }
    }
    
    // choose whatever "best" result you need from results, the one
    // with the lowest sum or lowest number of elements
    

    【讨论】:

    • 不幸的是,这可以工作并没有返回最佳结果。例如 M = 21,数字是 22, 15, 5, 3, 2 ,2, 1。如果我没记错的话,代码将返回 2 个结果 [22] 和 [15, 5, 3, 2, 2] .但最好的答案是 [15,5,3,2,1] 不会被考虑?如果我在这里错了,请纠正我。
    • 添加了一个小修复,但针对另一个问题。该解决方案应该可以工作,它有点考虑所有可能的解决方案,尽快切断错误的分支。因此,对于您的输入,它将返回 [1, 2, 2, 3, 5, 15] [1, 2, 3, 5, 15] [1, 3, 5, 15] [1, 5, 15] [2, 2, 3, 5, 15] 等。选择总和最少或元素数量最少的集合,或者您的“最佳”标准是什么。
    • 设法将其转换为代码。工作得很好。谢谢
    【解决方案4】:

    这类问题称为二进制linear programming(整数线性规划的一种特殊情况)。众所周知,它是 NP 难的——即通常无法有效解决。

    但是,商业和免费使用都存在很好的求解器,例如开源求解器lpsolve,您可以从程序中调用它。

    /编辑:旧答案是废话。我混淆了这些因素。

    【讨论】:

    • 我不认为这是一个线性规划问题,因为每个数字可以有 0 或 1,对于线性规划问题,您可以有分数倍数
    【解决方案5】:

    我认为这是 NP 完全的(一般来说找不到快速的方法)。你提出问题的方式让我想到用从 M 开始稳步增加的目标整数来解决连续的Subset sum problems

    在您的示例中,没有确定是否可以达到 M 的已知方法。只有蛮力解决方案会告诉您这是不可能的,然后您才能检查 M+1。

    但是,您可能希望查看 DP solution,因为这至少可以让您了解如何解决问题(尽管速度很慢)。还有一个approximate 解决方案,如果您的人数很少,这将是正确的。 使用这个。最后,值得指出的是,问题的大小是用位数来衡量的,而不是集合的大小(或总和)。

    如上所述,这与Knapsack problem有关。

    【讨论】:

      【解决方案6】:

      这个问题几乎完全是subset sum problem,它与背包问题有关但不同,并且是NP完全的。但是,如果所有数字都小于某个常数,则存在线性解,并且存在多项式时间近似。请参阅上面引用的维基百科页面。

      【讨论】:

        【解决方案7】:

        如果问题要求最少项目数,则贪心解决方案有效。但是maximum(..)函数要考虑到当M为负时maximum应该返回数字到0的距离。(abs()的值)。

        maximum() 函数可以通过二叉堆实现。复杂度为 O(n.logn)。

        【讨论】:

          【解决方案8】:

          贪婪的解决方案会奏效 一个伪:

          algo(input_set , target_sum, output_set )
             if input_set is NULL
                 error "target_sum can never be reached with all input_set"
                 return
             the_max = maximum(input_set)
             remove the_max from input_set
             if the_max > target_sum
                 algo(input_set, target_sum, ouptput_set )
                 return
             add the_max to output_set
             algo(input_set, target_sum - the_max, output_set )
          

          使用 output_set = NULL 调用。 排序 intput_set 以提高效率。

          【讨论】:

          • 不是操作问题的解决方案。
          • 我认为这个解决方案只有在我希望值总和正好等于 target_sum 时才有效。但是,就我而言,总数允许超出 target_sum。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-12
          • 1970-01-01
          • 2016-03-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多