Сложная проблема программирования, с которой я не могу справиться

Во-первых, позвольте мне сказать, что это не домашнее задание (я студент A-Level, это не то, что мы решаем задачи (это намного сложнее)), а скорее проблема, которую я Я пытаюсь выяснить, чтобы улучшить мою логику программирования.

Я подумал о сценарии, в котором есть массив случайных целых чисел, например, 10 целых чисел. Пользователь введет число, до которого он хочет посчитать, и алгоритм попытается определить, какие числа необходимы для получения этой суммы. Например, если бы я хотел сделать сумму 44 из этого массива целых чисел:

myIntegers = array(1, 5, 9, 3, 7, 12, 36, 22, 19, 63);

Результат будет:

36 + 3 + 5 = 44

Или что-то вдоль этих линий. Надеюсь, я ясно выразился. В качестве дополнительного бонуса я хотел бы, чтобы алгоритм выбирал как можно меньше чисел для получения требуемой суммы или выдавал ошибку, если сумма не может быть составлена ​​с предоставленными числами.

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

Я не ищу полный код или полный алгоритм, мне просто нужно ваше мнение о том, как мне поступить, и, возможно, поделиться несколькими советами или чем-то еще. Я, вероятно, начну работать над этим сегодня вечером. :П

Как я уже сказал, не домашнее задание. Просто я хочу сделать что-то более продвинутое.

Спасибо за любую помощь, которую вы можете предложить. :)


person Phox    schedule 23.02.2010    source источник
comment
см. это: en.wikipedia.org/wiki/Subset_sum_problem   -  person codaddict    schedule 23.02.2010
comment
Было это на курсе CS. Все еще думаю, что это домашнее задание. ;)   -  person Arnis Lapsa    schedule 23.02.2010
comment
Поздравляем, вы придумали NP-полную задачу самостоятельно! :)   -  person Nikolai Fetissov    schedule 23.02.2010
comment
@Arnis: Ну, ты ошибаешься. ;)   -  person Phox    schedule 23.02.2010
comment
@Phox, к счастью - это вообще не имеет значения. :D   -  person Arnis Lapsa    schedule 23.02.2010
comment
лол @ быть намного сложнее, чем твои проблемы с железом. Это довольно стандартное задание для первого года обучения CS.   -  person Erix    schedule 23.02.2010
comment
Разве это не похоже на то, что делают лучшие машины для выдачи денег (такие как кассы самообслуживания в супермаркетах), когда рассчитывают, сколько сдачи вам нужно из того, что вы вложили? :)   -  person Chris Dennett    schedule 23.02.2010
comment
@Chris Хорошее сравнение, но валюты имеют значительное преимущество в том, что они «минимальны», то есть существует только один набор монет, который составляет любую заданную сумму, и таким образом их можно найти с помощью простого жадного алгоритма — начните с наибольшего номинала, добавляйте до тех пор, пока остаток не станет меньше номинала, затем перейдите к следующему наибольшему номиналу и повторите.   -  person Nick Johnson    schedule 25.02.2010
comment
@NickJohnson: Да, простой жадный алгоритм отлично работает для всех известных мне реальных валют. Но я бы не сказал, что есть только один набор монет, который в сумме составляет любую заданную сумму, поскольку четверть+никель = дайм+дайм+дайм.   -  person David Cary    schedule 19.12.2012


Ответы (10)


Вы рассматриваете проблему с рюкзаком.

Задача о рюкзаке или задача о рюкзаке — это задача комбинаторной оптимизации: для заданного набора предметов, каждый из которых имеет вес и ценность, определить количество каждого предмета, которое нужно включить в коллекцию, чтобы общий вес был меньше заданного предела и общая стоимость как можно больше. Он получил свое название от проблемы, с которой сталкивается тот, кто ограничен рюкзаком фиксированного размера и должен заполнить его самыми полезными предметами.


Изменить: ваш особый случай — проблема суммы подмножества

person Otto Allmendinger    schedule 23.02.2010
comment
Вам не нравится, когда сюда приходят люди, которые спрашивают, как быстро решить NP-полные задачи? - person Poindexter; 23.02.2010
comment
Спасибо. Я думал, что где-то уже есть документация по этому вопросу, но не знал, что искать, чтобы найти их. :) - person Phox; 23.02.2010
comment
Кстати, тот же алгоритм решения проблемы используется на большинстве предприятий по упаковке пищевых продуктов, где до 10 контейнеров заполняют один с требуемым минимальным весом. Цель состоит в том, чтобы сделать это правильно или заполнить минимум больше, чем требуется. - person Drejc; 23.02.2010
comment
@Poindexter: В данном случае это слабо NP-полные проблемы. - person Rafał Dowgird; 24.02.2010

Подойдет ли сумма подмножества? ;]

person pablochan    schedule 23.02.2010

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

РЕДАКТИРОВАТЬ: Одна вещь, которую следует учитывать, это динамическое программирование.

person nevets1219    schedule 23.02.2010

Ваша проблема связана с проблемой суммы подмножества. Вы должны попробовать все возможные комбинации в худшем случае.

person swegi    schedule 23.02.2010

Боюсь, здесь нет коротких путей. В дополнение к тому, что говорили другие люди, о том, что это за проблема и т. д., вот несколько практических советов, которые послужат вам отправной точкой:

Я бы отсортировал массив и, учитывая входную сумму m, нашел бы первое число в массиве меньше m, назвал бы его n (это ваше первое возможное число для суммы) и начал бы с максимально возможного дополнения (m-n), работая свой путь вниз.

Если вы не найдете точного совпадения, выберите максимально возможное, назовите его o (теперь это ваше второе число), найдите 3-е число, начиная с (m-n-o), и снова двигайтесь вниз.

Если вы не найдете точного совпадения, начните со следующего числа n (индекс исходного n в индексе-1) и сделайте то же самое. Вы можете продолжать делать это, пока не найдете точное совпадение для двух чисел. Если совпадения суммы для двух чисел не найдено, запустите процесс снова, но расширите его, включив в него третье число. И так далее.

Это можно сделать рекурсивно. По крайней мере, этот подход гарантирует, что когда вы найдете совпадение, это будет совпадение с наименьшими возможными числами в наборе, образующем общую входную сумму.

Хотя потенциально, в худшем случае, вы в конечном итоге пройдёте через всё это.

Правка. Как правильно заметил Венр, мой первый подход был неправильным. Отредактированный подход, чтобы отразить это.

person Wim Hollebrandse    schedule 23.02.2010
comment
Этот подход не гарантирует, что вы найдете ответ с наименьшим количеством возможных элементов, рассмотрим набор { 1, 2, 4, 6, 7 } и желаемую сумму 10. При таком подходе вы получите 7 + 2 + 1 но ответ, который вы действительно хотите, это 6 + 4. - person Venr; 23.02.2010
comment
Да, хорошая мысль @Venr. Вот что бывает, когда просто думаешь на ура, не записывая несколько примеров. Тем не менее, я оставлю ответ здесь, поскольку он обеспечивает какой-то подход. - person Wim Hollebrandse; 23.02.2010

Для этой задачи существует очень эффективный рандомизированный алгоритм. Я знаю, что вы уже приняли ответ, но я все равно рад поделиться, я просто надеюсь, что люди все еще будут проверять этот вопрос :).

Let Used = list of numbers that you sum.
Let Unused = list of numbers that you DON'T sum.
Let tmpsum = 0.
Let S = desired sum you want to reach.

for ( each number x you read )
  toss a coin:
    if it's heads and tmpsum < S
      add x to Used
    else
      add x to Unused

while ( tmpsum != S )
  if tmpsum < S 
    MOVE one random number from Unused to Used
  else
    MOVE one random number from Used to Unused

print the Used list, containing the numbers you need to add to get S

Это будет намного быстрее, чем решение динамического программирования, особенно для случайных входных данных. Единственная проблема заключается в том, что вы не можете надежно определить отсутствие решения (вы можете позволить алгоритму работать несколько секунд, и если он не завершится, предположим, что решения нет) и что вы не можете быть уверены, что получите решение. с минимальным количеством выбранных элементов. Опять же, вы можете добавить некоторую логику, чтобы заставить алгоритм продолжать работать и пытаться найти решение с меньшим количеством элементов, пока не будут выполнены определенные условия остановки, но это сделает его медленнее. Однако, если вас интересует только работающее решение, и у вас МНОГО чисел, а желаемая сумма может быть ОЧЕНЬ большой, это, вероятно, лучше, чем алгоритм DP.

Еще одним преимуществом этого подхода является то, что он также будет работать для отрицательных и рациональных чисел без каких-либо изменений, что неверно для решения DP, потому что решение DP включает в себя использование частичных сумм в качестве индексов массива, а индексы могут быть только натуральными числами. Вы, конечно, можете использовать хеш-таблицы, например, но это сделает решение DP еще медленнее.

person IVlad    schedule 23.02.2010

Я не знаю точно, как называется эта задача, но кажется, что это что-то вроде http://en.wikipedia.org/wiki/Knapsack_problem.

person User Friendly    schedule 23.02.2010

Хех, я разыграю карту «неполная спецификация» (никто не говорил, что числа не могут появляться более одного раза!) и сведу это к проблеме «внесение сдачи». Отсортируйте числа в порядке убывания, найдите первое число меньше желаемой суммы, затем вычтите его из суммы (деление и остатки могут ускорить это). Повторяйте до тех пор, пока сумма не будет равна 0 или пока не будет найдено число, меньшее суммы.

Для полноты вам нужно будет отслеживать количество слагаемых в каждой сумме и, конечно же, генерировать дополнительные последовательности, отслеживая первое используемое число, пропуская его и повторяя процесс с дополнительными числами. Это решит проблему (7 + 2 + 1) вместо (6 + 4).

person TMN    schedule 23.02.2010

Повторяя ответ других: это проблема суммы подмножества. Это может быть эффективно решено с помощью метода динамического программирования.

Еще не упомянуто следующее: проблема псевдо-P (или NP-Complete в слабом смысле).

Существование алгоритма (основанного на динамическом программировании), полиномиального по S (где S — сумма) и n (количество элементов), доказывает это утверждение.

С Уважением.

person Slava    schedule 23.02.2010

Хорошо, я написал программу на C++ для решения вышеуказанной проблемы. Алгоритм прост :-)

Прежде всего, расположите любой массив, который у вас есть, в порядке убывания (я жестко закодировал массив в убывающей форме, но вы можете применить любой из алгоритмов сортировки).

Далее я взял три стопки n, pos и sum. В первом хранится число, для которого нужно найти возможную комбинацию суммы, во втором хранится индекс массива, с которого следует начать поиск, в третьем хранятся элементы, сложение которых даст вам введенное число.

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

Позвольте мне показать пример: - найти 44 в {63,36,22,19,12,9,7,5,3,1}

первые 36 будут помещены в сумме (наибольшее число меньше 44) 44-36=8 будет помещено в n(следующее число для поиска) 7 будет помещено в сумме 8-7=1 будет помещено в n 1 будет подтолкнул в сумме

таким образом, 44=36+7+1 :-)

#include <iostream>
#include<conio.h>
using namespace std;

int found=0;
void func(int n[],int pos[],int sum[],int arr[],int &topN,int &topP,int &topS)
{
int i=pos[topP],temp;
while(i<=9)
{
    if(arr[i]<=n[topN])
    {
        pos[topP]=i;
        topS++;
        sum[topS]=arr[i];
        temp=n[topN]-arr[i];
        if(temp==0)
            {
                found=1;
                break;
        }
topN++;
        n[topN]=temp;
        temp=pos[topP]+1;
        topP++;
        pos[topP]=temp;
        break;
    }
    i++;
}
if(i==10)
{
    topP=topP-1;
    topN=topN-1;
    pos[topP]+=1;
    topS=topS-1;
    if(topP!=-1)
    func(n,pos,sum,arr,topN,topP,topS);
}
else if(found!=1)
func(n,pos,sum,arr,topN,topP,topS);
}

main()
{
int x,n[100],pos[100],sum[100],arr[10]={63,36,22,19,12,9,7,5,3,1},topN=-1,topP=-1,topS=-1;
cout<<"Enter a number: ";
cin>>x;
topN=topN+1;
n[topN]=x;
topP=topP+1;
pos[topP]=0;
func(n,pos,sum,arr,topN,topP,topS);
if(found==0)
    cout<<"Not found any combination";
else{
cout<<"\n"<<sum[0];
for(int i=1;i<=topS;i++)
    cout<<" + "<<sum[i];
}
getch();
}

Вы можете скопировать код и вставить его в свою IDE, отлично работает :-)

person Gourav Bhattacharya    schedule 23.10.2014