Что не так со следующей реализацией алгоритма набивки ранца?

Правка 2: теперь не проходит проверку ({64, 1, 36, 81}, 82)
Правка 1: теперь исправляет проблемы из-за дельты > max(items )
Edit0: теперь обновлено, чтобы исправить бесконечную рекурсию из-за осциллирующей дельта-проблемы.


В этом видео об алгоритмах (около 2:52) профессор Скиена описывает задачу о рюкзаке как следует...

Задача о рюкзаке: при заданном наборе целых чисел S = {s1, s2,..., sN} и целевом числе T найдите подмножество S, которое дает в сумме точно к Т.

Затем он продолжает, что это одна из тех проблем, для которых не существует известного эффективного решения!

Но я все равно пытался, и вот моя попытка решения (и, похоже, это работает для чисел, которые я пробовал)...

from itertools import accumulate

#items   - All available items
#target  - The size of the knapsack
#returns - A subset of {items} whose sum adds upto target
#          if possible else returns None
def KnapsackItems(items, target):
    s = sum(items)
    if s < target:
        return None
    delta = s - target

    if delta == 0:
        return items

    if delta in items:
        result = items - {delta}
        return result

    if delta > max(items):
        sortedItems = list(sorted(items))
        deltas = list(map(lambda x: x - target, accumulate(sortedItems)))
        ul = [i for i,d in zip(sortedItems, deltas) if d <= i]
        return KnapsackItems(set(ul), target)
    else:
        U = {i for i in items if i < delta}

    V = KnapsackItems(U, delta)
    if V:
        result = items - V
        return result
    return None

А вот и тестовый набор...

def test(items, target):
    print("Items:", items)
    print("Target:", target)

    result = KnapsackItems(items, target)

    if result and not sum(result) == target:
        print("Result:", result)
        print("FAIL: sum of returned set does not match target ({})".format(target))
    elif result:
        print("Result:", result)
        print("Success (sum of returned set:{})".format(sum(result)))
    else:
        print("No solution could be found")

Примеры из видео...

test({1,2,5,9,10}, 22)
test({1,2,5,9,10}, 23) #No solution expected
test({1,2,3,4,5}, 11)
test({1,2}, 2)
test({4,3,2}, 5)
test({1, 3, 4, 7, 9}, 13)
test({6,7,8,3,14,5,15,2,4}, 29)
test({1,2,3,4,5,6,7},14)
test({64, 1, 36, 81}, 82)

Результат...

Items: {9, 10, 2, 5, 1}
Target: 22
Result: {9, 10, 2, 1}
Success (sum of returned set:22)

Items: {9, 10, 2, 5, 1}
Target: 23
No solution could be found

Items: {1, 2, 3, 4, 5}
Target: 11
Result: {1, 2, 3, 5}
Success (sum of returned set:11)

Items: {1, 2}
Target: 2
Result: {2}
Success (sum of returned set:2)

Items: {2, 3, 4}
Target: 5
Result: {2, 3}
Success (sum of returned set:5)

Items: {9, 3, 4, 1, 7}
Target: 13
Result: {9, 4}
Success (sum of returned set:13)

Items: {2, 3, 4, 5, 6, 7, 8, 14, 15}
Target: 29
Result: {14, 15}
Success (sum of returned set:29)

Items: {1, 2, 3, 4, 5, 6, 7}
Target: 14
Result: {2, 5, 7}
Success (sum of returned set:14)

Items: {64, 81, 36, 1}
Target: 82
No solution could be found

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


person Autodidact    schedule 22.11.2013    source источник
comment
Может лучше подойти для codereview.stackexchange.com   -  person CDspace    schedule 22.11.2013
comment
KnapsackItems({1, 3, 5}, 9) выдает ошибку переполнения стека   -  person sanity    schedule 22.11.2013
comment
Как и KnapsackItems({1, 3, 4, 7, 9}, 13)   -  person sanity    schedule 22.11.2013
comment
@sanity хороший улов! Посмотрим, смогу ли я это исправить.   -  person Autodidact    schedule 22.11.2013
comment
Я подозреваю, что основная проблема здесь заключается в том, что для алгоритма должен быть способ вернуться назад, т.е. попробовать что-то, но затем вернуться и попробовать что-то еще.   -  person sanity    schedule 22.11.2013
comment
Я думаю, что ни один из ваших тестовых примеров положительного решения никогда не дойдет до рекурсивного шага.   -  person Abhishek Bansal    schedule 22.11.2013
comment
KnapsackItems({1, 3, 5}, 9) терпит неудачу, потому что здесь дельта равна нулю. Мне нужно вернуть элементы, как в этом случае.   -  person Autodidact    schedule 22.11.2013
comment
У меня недостаточно опыта для анализа кода, но я хотел бы отметить, что на самом деле это не проблема рюкзака, а проблема суммы подмножества, более простая версия. Согласно Википедии, он по-прежнему является только NP-полным, что означает, что эффективный (полиномиальный) алгоритм неизвестен.   -  person Kendall Frey    schedule 22.11.2013
comment
@AbhishekBansal попробуйте тест ({1,2,3}, 2)   -  person Autodidact    schedule 22.11.2013
comment
@sanity Delta колеблется между 13 и 11 для KnapsackItems ({1, 3, 4, 7, 9}, 13). Нужно посмотреть, не смертельно ли это.   -  person Autodidact    schedule 22.11.2013
comment
@sanity У меня есть решение для KnapsackItems ({1, 3, 4, 7, 9}, 13).   -  person Autodidact    schedule 22.11.2013


Ответы (3)


Когда max(items) < target < sum - max(items) (я не знаю Python), тогда delta всегда будет больше, чем max(items), и никакие элементы никогда не будут удалены до рекурсивной проверки, и алгоритм никогда не завершится.

Отредактированная версия:

Теперь он терпит неудачу, когда max(items) не может быть частью решения (например, когда max(items) > target) и max(items) < delta. Пример: {2, 3, 4, 6}, 5. После первой итерации он становится {2, 3, 4}, 10, что возвращает None, в результате чего вызов верхнего уровня возвращает None, что неверно.

person Kendall Frey    schedule 22.11.2013
comment
На самом деле на первом этапе ни один из элементов не должен быть удален. Но будет ли это верно и для последовательных шагов? Потому что дельта меняется на каждом шагу. - person Abhishek Bansal; 22.11.2013
comment
@KendallFrey Да, похоже, это проблема с примером, приведенным здравомыслием. Предметы Рюкзака({1, 3, 4, 7, 9}, 13). Может быть, это фатальный недостаток (а может и нет), дайте мне посмотреть, смогу ли я это исправить. В этом случае дельта колеблется между 13 и 11. - person Autodidact; 22.11.2013
comment
@AbhishekBansal Да, когда это верно для первого шага, это будет верно и для будущих шагов. - person Kendall Frey; 22.11.2013
comment
@KendallFrey Ааа, да, я вижу, тогда дельта будет колебаться. - person Abhishek Bansal; 22.11.2013
comment
@KendallFrey, пожалуйста, смотрите обновленное решение. Это решает вашу проблему? - person Autodidact; 22.11.2013
comment
@SandeepDatta Обновлено. - person Kendall Frey; 22.11.2013
comment
@KendallFrey К сожалению, мне нужно все обдумать. Вернемся к вам с решением, если это возможно. - person Autodidact; 22.11.2013
comment
@SandeepDatta Если вам удастся решить это за полиномиальное время, вы станете очень, очень известным человеком. Вам придется хорошенько подумать. :) - person Kendall Frey; 22.11.2013
comment
@KendallFrey хахаха. Я знаю, что просто хочу приложить больше усилий, чтобы понять, почему это такая сложная проблема. - person Autodidact; 22.11.2013
comment
@KendallFrey кажется (как вы сказали), когда delta › max(items) размер проблемы не уменьшается, и, вероятно, поэтому решение за полиномиальное время невозможно. - person Autodidact; 22.11.2013
comment
@KendallFrey исправил KnapsackItems ({2, 3, 4, 6}, 5) :) - person Autodidact; 23.11.2013
comment
Я не могу заниматься отладкой вашего кода весь день, поэтому, думаю, я оставлю его как есть. - person Kendall Frey; 23.11.2013

KnapsackItems({6,7,8,3,14,5,15,2,4}, 29)

  File "C:\Program Files (x86)\Wing IDE 101 4.1\src\debug\tserver\_sandbox.py", line 95, in KnapsackItems
  File "C:\Program Files (x86)\Wing IDE 101 4.1\src\debug\tserver\_sandbox.py", line 95, in <setcomp>
builtins.RuntimeError: maximum recursion depth excee

Видимо вопрос на 1 миллион долларов решить не так просто :)

person JoeC    schedule 22.11.2013

Есть вероятность бесконечной рекурсии, как в следующем наборе данных.

For example,
{(1,2,3,4,5,6,7),14}
sum = 28.
delta = 14.

Итак, если sum = 2 * target и max(items) < target, то это вызовет бесконечную рекурсию.

person Abhishek Bansal    schedule 22.11.2013
comment
Я думаю, что главная проблема с этим ответом заключается в том, что ОП прав. Если какой-либо отдельный элемент, превышающий дельту, не входит в решение, оставшиеся элементы в сумме должны быть меньше целевого значения, и, таким образом, решение не является решением. - person Kendall Frey; 22.11.2013
comment
@AbhishekBansal Я исправил эту проблему. - person Autodidact; 22.11.2013