选择一组数字以达到最小总数的算法

时间:2010-10-18 07:26:04

标签: php algorithm math

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

我想找到

的最佳组合
n[a] + n[b] + n[c] + ... + n[?] >= M

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

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

8 个答案:

答案 0 :(得分:4)

这看起来像是一个经典的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,{})

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

答案 1 :(得分:2)

我认为greedy algorithm方法可行。 你期望在集合中有多少个数字?如果它相当低,你可以尝试backtrack,但我不推荐它用于大型套装。

答案 2 :(得分:1)

这种问题称为二进制linear programming(整数线性编程的一种特殊情况)。它是着名的NP难题 - 即通常无法有效解决。

然而,存在良好的解决方案,包括商业和免费使用,例如您可以从程序中调用的开源解算器lpsolve

:编辑:老答案是下铺。我把这些因素搞糊涂了。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

我认为这是NP完全的(不可能找到一种快速的方法一般)。你问这个问题的方式让我想到用一个从M稳定增加的目标整数求解连续的Subset sum problems

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

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

如上所述,这与Knapsack problem

有关

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

maximum()函数可以通过二进制堆实现。复杂性是O(n.logn)。

答案 7 :(得分:-1)

贪婪的解决方案可行 伪:

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以提高效率。