В конечном итоге вам понадобится математическое доказательство правильности. Ниже я приведу некоторые методы доказательства, но сначала, прежде чем углубляться в это, позвольте мне сэкономить немного времени: прежде чем искать доказательство, попробуйте случайное тестирование.
Случайное тестирование
В качестве первого шага я рекомендую использовать случайное тестирование для проверки вашего алгоритма. Удивительно, насколько это эффективно: по моему опыту, для жадных алгоритмов случайное тестирование кажется неоправданно эффективным. Потратьте 5 минут на написание своего алгоритма, и вы можете сэкономить час или два, пытаясь придумать доказательство.
Основная идея проста: реализовать свой алгоритм. Кроме того, реализуйте эталонный алгоритм, который, как вы знаете, является правильным (например, тот, который исчерпывающе пробует все возможности и выбирает лучшее). Хорошо, если ваш эталонный алгоритм асимптотически неэффективен, так как вы будете запускать его только на небольших проблемных экземплярах. Затем случайным образом сгенерируйте миллион маленьких проблемных экземпляров, запустите оба алгоритма для каждого и проверьте, дает ли алгоритм-кандидат правильный ответ в каждом случае.
Опытным путем, если ваш жадный алгоритм кандидата неверен, обычно вы часто обнаруживаете это во время случайного тестирования. Если это кажется правильным во всех тестовых случаях, то вам следует перейти к следующему шагу: придумать математическое доказательство правильности.
Математические доказательства правильности
Итак, нам нужно доказать, что наш жадный алгоритм верен: он выводит оптимальное решение (или, если есть несколько оптимальных решений, которые одинаково хороши, он выводит одно из них).
Основной принцип интуитивно понятен:
Принцип: если вы никогда не сделаете плохой выбор, все будет хорошо.
Жадные алгоритмы обычно включают в себя последовательность выбора. Основная стратегия доказательства состоит в том, что мы попытаемся доказать, что алгоритм никогда не делает неправильный выбор. Жадные алгоритмы не могут отказаться - как только они сделают выбор, они преданы и никогда не отменят этот выбор - поэтому важно, чтобы они никогда не делали плохой выбор.
Что считается хорошим выбором? Если есть единственное оптимальное решение, легко увидеть, что является хорошим выбором: любой выбор, идентичный тому, который сделан оптимальным решением. Другими словами, мы попытаемся доказать, что на любом этапе выполнения жадных алгоритмов последовательность выборов, сделанных алгоритмом, точно соответствует некоторому префиксу оптимального решения. Если существует несколько одинаково хороших оптимальных решений, хорошим выбором будет тот, который соответствует хотя бы одному из оптимумов. Другими словами, если последовательность выбора алгоритма до сих пор совпадает с префиксом одного из оптимальных решений, все в порядке (пока ничего не пошло не так).
Чтобы упростить жизнь и устранить отвлекающие факторы, давайте сосредоточимся на случае, когда нет связей: есть единственное, уникальное оптимальное решение. Все оборудование будет перенесено на тот случай, когда может быть несколько одинаково хороших оптимумов без каких-либо фундаментальных изменений, но вы должны быть немного осторожнее с техническими деталями. Начните с игнорирования этих деталей и сосредоточьте внимание на случае, когда оптимальное решение является уникальным; это поможет вам сосредоточиться на том, что важно.
Существует очень распространенный образец доказательства, который мы используем. Мы будем усердно работать, чтобы доказать следующее свойство алгоритма:
Утверждение: пусть будет решением, выводимым алгоритмом, и O будет оптимальным решением. Если S отличается от O , то мы можем подправить O , чтобы получить другое решение O * , который отличается от O и строго лучше , чем O .SОSООО*ОО
Обратите внимание, почему это полезно. Если утверждение верно, значит, алгоритм верен. Это в основном доказательство от противного. Либо такое же , как O , или она отличается. Если оно отличается, то мы можем найти другое решение O ∗, которое строго лучше, чем O - но это противоречие, поскольку мы определили O как оптимальное решение, и не может быть никакого решения, которое лучше этого. Поэтому мы вынуждены сделать вывод, что S не может отличаться от O ; S всегда должно быть равно OSОО*ООSОSОжадный алгоритм всегда выводит правильное решение. Если мы можем доказать утверждение выше, то мы доказали, что наш алгоритм правильный.
Хорошо. Так, как мы доказываем требование? Мы рассматриваем решение как вектор ( S 1 , … , S n ), который соответствует последовательности n выборов, сделанных алгоритмом, и аналогичным образом мы рассматриваем оптимальное решение O как вектор ( O 1 , … , о п ) , соответствующий последовательности вариантов , которые привели бы к O . Если S отличается от O , должен существовать некоторый индекс i, где S i ≠S( S1, … , SN)NО( O1, … , ОN)ОSОя ; мы сосредоточимся на самых маленьких, таких как я . Затем мы настроим O ,немногоизменив O в i- й позиции, чтобы она соответствовала S i , т.е. мы настроим оптимальное решение O , изменив i- й вариант на выбор, выбранный жадным алгоритмом, и затем мы покажем, что это приводит к еще лучшему решению. В частности, мы определим O ∗ как что-то вродеSя≠ OяяООяSяОяО*
О*= ( O1, O2, … , Оя - 1, Sя, Oя + 1, Oя + 2, … , ОN) ,
за исключением того, что нам часто приходится слегка изменять части чтобы поддерживать глобальную согласованность. Часть стратегии доказательства включает в себя некоторый ум в правильном определении O ∗ . Тогда основной смысл доказательства состоит в том, чтобы как-то использовать факты об алгоритме и задачу показать, что O ∗ строго лучше OОя + 1, Oя + 2, … , ОNO∗O∗O; вот где вам понадобится понимание проблем. В какой-то момент вам нужно погрузиться в детали вашей конкретной проблемы. Но это дает вам представление о структуре типичного доказательства корректности жадного алгоритма.
Простой пример: подмножество с максимальной суммой
Это может быть легче понять, если детально проработать простой пример. Давайте рассмотрим следующую проблему:
Входные данные: набор целых чисел, целое число k Выходные данные: набор S ⊆ U размера k , сумма которых максимально возможнаUk
S⊆Uk
Есть естественный жадный алгоритм для этой проблемы:
- Установите .S:=∅
- Для :
i:=1,2,…,k
- Пусть будет наибольшим числом в U, которое еще не было выбрано (то есть, i- е наибольшее число в U ). Добавить е I в S .xiUiUxiS
Случайное тестирование показывает, что это всегда дает оптимальное решение, поэтому давайте формально докажем, что этот алгоритм правильный. Обратите внимание, что оптимальное решение уникально, поэтому нам не нужно беспокоиться о связях. Давайте докажем утверждение, изложенное выше:
SU,kOS≠OO∗O
S≠Oixi∉OiS≠OS={x1,…,xk}ix1,…,xi−1∈OOO={x1,x2,…,xi−1,x′i,x′i+1,…,x′n}x1,…,xi−1,x′i,…,x′nx1,…,xixi>x′jj≥ixi>x′iO=O∪{xi}∖{x′i}O∗iOxiO∗Oxi−x′ixi−x′i>0O∗O■
OOOO
Ox′ixi