Алгоритм выбора набора чисел для достижения минимальной суммы - PullRequest
7 голосов
/ 18 октября 2010

С учетом Набор чисел n[1], n[2], n[3], .... n[x] И число М

Я бы хотел найти лучшую комбинацию

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

Комбинация должна достичь минимума, необходимого для достижения или выхода за пределы М, при этом никакая другая комбинация не дает лучшего результата.

Будет делать это в PHP, поэтому использование PHP-библиотек в порядке. Если нет, то подойдет общий алгоритм. Спасибо!

Ответы [ 8 ]

4 голосов
/ 18 октября 2010

Это выглядит как классическая проблема динамического программирования (также указывается другими ответами, в которых упоминается ее сходство с задачами 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 голосов
/ 18 октября 2010

Я думаю, что жадный алгоритм будет работать. Сколько чисел вы ожидаете иметь в наборе? Если он достаточно низкий, вы можете попробовать backtrack , но я бы не рекомендовал его для больших наборов.

1 голос
/ 18 октября 2010
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
1 голос
/ 18 октября 2010

Этот тип задач называется бинарным линейным программированием (частный случай целочисленного линейного программирования). Это классно NP-трудно - то есть неэффективно, чтобы решить вообще.

Однако существуют хорошие решатели, как коммерческого, так и бесплатного использования, например, Open Source решатель lpsolve , который вы можете вызвать из вашей программы.

/ РЕДАКТИРОВАТЬ: Старый ответ был пустым. Я перепутал факторы.

0 голосов
/ 14 апреля 2011

Жадное решение работает, если вопрос задает минимальное количество предметов. Но функция максимума (..) должна учитывать, что при отрицательном значении M максимум должен возвращать расстояние от числа до 0. (abs () значения).

Функция Maximum () может быть реализована через двоичную кучу. Сложность O (n.logn).

0 голосов
/ 18 октября 2010

Эта проблема почти полностью совпадает с суммой подмножеств , которая связана с проблемой ранца, но отличается от нее и является NP завершенной. Однако существует линейное решение, если все числа меньше некоторой константы, и приближение полиномиального времени. См. Страницу википедии, на которую ссылаются выше.

0 голосов
/ 18 октября 2010

Будет работать жадное решение. Псевдо:

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 для эффективности.

0 голосов
/ 18 октября 2010

Я думаю, что это NP-полная (невозможно найти быстрый способ сделать это в общем ). То, как вы сформулировали вопрос, заставляет меня задуматься о решении последовательных задач с подмножеством сумм с целевым числом, которое постоянно увеличивается от M.

В вашем примере нет известного метода определения того, можно ли достичь М. Только грубое решение скажет вам, что это невозможно, и только тогда вы сможете проверить M + 1.

Однако вы можете посмотреть на решение DP , поскольку это, по крайней мере, даст вам представление о том, как решить проблему (хотя и медленно). Существует также приблизительное решение, которое будет правильным, если ваши цифры невелики. Используйте это. Наконец, стоит указать, что размер проблемы измеряется количеством бит, а не размером (или суммой) набора.

Как упоминалось выше, это связано с проблемой Рюкзак .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...