|
Лекции по структурам и алгоритмам обработки данных (СиАОД) 64. Динамическое программирование Динамическое программирование – это особый способ оптимизации, специально приспособленный к так называемым "многошаговым” операциям, в частности к задачам перебора вариантов. Предположим, что эффективность операции определяется функцией стоимости, которая складывается из стоимостей на отдельных шагах, то естьF (a1, a2, …, am) = С(a1) + С(a2) +…+ С(am), где С(ai) – функция, определенная для всех ai. Показатели такого вида называют аддитивными. Требуется найти такой набор (a1, a2, …, am), чтобы стоимость была минимальной (максимальной). Рассмотрим примеры многошаговых операций.
Требуется спланировать управление на все m лет, чтобы суммарные расходы на эксплуатацию, ремонт и приобретение новых аппаратов были минимальными. Управление состоит в выборе одного из трех решений в начале каждого года. Обозначим их цифрами 1, 2 и 3. Стоимость складывается из годовых затрат. Решение представляет из себя комбинацию чисел 1, 2 и 3. Например, (3, 3, 2, 2, 2, 1, 3, …) означает: первые 2 года эксплуатировать аппарат без ремонта, последующие 3 года производить ремонт, в начале шестого года продать, купив новый, затем снова эксплуатировать без ремонта и т. д.
Суммарный доход представляет собой сумму доходов на отдельных шагах (годах). На каждом i-ом шаге предприятиям выделяются некоторые средства ai1, ai2, …, aik (первый индекс – номер шага, второй – номер предприятия), то есть ai = (a i1, a i2, …, a ik). Таким образом, элементы ai представляют собой вектора. В динамическом программировании элемент ai на каждом шаге выбирается с учетом последствий в будущем на следующих шагах, то есть так, чтобы была минимальна сумма стоимостей на всех оставшихся до конца шагах плюс стоимость на данном шаге. Однако из этого правила есть исключение. Среди всех шагов есть один, который может планироваться отдельно, без планов на будущее. Это, очевидно, последний шаг. Поэтому процесс динамического программирования обычно разворачивается от конца к началу: прежде всего планируется последний, m-й шаг. Но как его планировать, если неизвестно, чем кончился предпоследний? Планируя последний шаг, нужно рассмотреть все возможности того, чем кончился предпоследний (m-1)-й шаг, и для каждой такой возможности найти наилучший вариант последнего шага. В динамическом программировании это называют условным оптимальным управлением, так как оно выбирается из условия, что предпоследний шаг закончился определенным образом. Затем находят условное оптимальное управление на (m-2)-м шаге и т. д., пока не доходят до первого шага, на котором находится уже истинное оптимальное управление, минимизирующее функцию стоимости. Сейчас двигаясь от начала к концу, на каждом i-ом шаге находят наилучшие значения ai. Таким образом, многошаговый процесс проходится дважды: первый раз от конца к началу, в результате чего находятся условные оптимальные управления и соответствующие условные оптимальные значения за оставшийся "хвост” процесса, а второй раз от начала к концу, когда остается "вспомнить” уже готовые рекомендации и найти оптимальные значения элементов ai. Первый этап несравненно сложнее и длительнее второго, который почти не требует дополнительных вычислений. Проиллюстрируем метод динамического программирования следующей задачей. На клетчатой бумаге задан прямоугольник. Ход состоит в перемещении из текущей клетки в соседнюю справа либо сверху в пределах прямоугольника. В каждой клетке имеется некоторое число. Требуется из клетки в левом нижнем углу за некоторое число ходов попасть в правую верхнюю клетку так, чтобы сумма чисел в клетках пути была минимальной. Пусть, например, прямоугольник (квадрат) заполнен следующим образом:
Начнем с конечной клетки E1. В нее можно попасть за один ход из клеток D1 и E2. В обеих клетках возможен единственный ход. В клетке D1 проставляется стоимость 14+9=23, а в e2 – (-1)+9=8. Далее проставим минимальную стоимость во все клетки первой строки, двигаясь влево от клетки D1. Для каждой такой клетки также имеется единственный ход, и стоимость формируется путем сложения стоимости текущей клетки с минимальной стоимостью клетки справа, рассмотренной на предыдущем шаге. Аналогично сверху вниз находятся минимальные стоимости для всех клеток последней вертикали E.
Начальная клетка A5 будет рассмотрена последней. Получим, что минимальная стоимость пути из A5 в E1 составляет 43 единицы. Сам путь восстанавливается по тем лучшим ходам, которые были сохранены. В этом примере есть 2 пути минимальной стоимости: A5, B5, B4, C4, C3, C2, C1, В1, E1 и A5, B5, C5, C4, C3, C2, C1, D1, E1. Сформулируем общий принцип, лежащий в основе решения задач динамического программирования, называемый принципом оптимальности. Каково бы ни было состояние процесса перед очередным шагом, надо выбирать управление на этом шаге так, чтобы стоимость на данном шаге плюс оптимальная стоимость на всех последующих шагах была минимальной. Отметим в заключение некоторые дополнительные аспекты применения методов динамического программирования.
Обычно в основе решения задач динамического программирования лежат рекуррентные соотношения. Поэтому задачи, связанные с выводом рекуррентных соотношений, часто относят к задачам динамического программирования, хотя обратный проход в этих задачах может и не потребоваться. Рассмотрим еще одну задачу, связанную с подходами динамического программирования [2]. Возрастающая подпоследовательность. Даны N целых чисел X1, X2, ..., XN. Требуется вычеркнуть из них минимальное количество чисел так, чтобы оставшиеся шли в порядке возрастания. Ввод из файла INPUT.TXT. В первой строке находится число N. В следующей строке - N чисел через пробел. Вывод в файл OUTPUT.TXT. В первой строке выводится количество невычеркнутых чисел, во второй - сами невычеркнутые числа через пробел в исходном порядке. Если вариантов несколько, вывести любой.
Во втором массиве для каждого элемента будем сохранять индекс следующего элемента в максимальной подпоследовательности, начинающейся от этого элемента. Пусть I – номер текущего элемента, и оба массива заполнены для индексов с I+1 по N. В цикле по J будем перебирать все элементы от I+1-го до последнего. Если окажется, что XI £ XJ, то элемент XI может дополнить подпоследовательность, начинающуюся с элемента XJ. Найдем номер J, для которого длина CJ такой подпоследовательности максимальна. Положим CI = CJ +1 и DI = J. Сохраним номер, начиная с которого максимальная возрастающая подпоследовательность оказалась самой длинной. На втором проходе уже в прямом направлении по массиву D, начиная с найденного номера, восстановим искомую подпоследовательность. Рассмотрим, например, при N=6 последовательность 6, 3, 9, 4, 7, 5. Для нее
Максимальное значение CI достигнуто при I = 2. Одна из максимальных возрастающих подпоследовательностей 3, 4, 7 находится с помощью сохраненных в массиве D индексов 4 и 5. Ниже приводится текст программы.
Свинья-копилка. Для того чтобы начать свой бизнес, юный коммерсант решил накопить немного денег. С этой целью он отыскал свинью-копилку и начал собирать деньги. Требуется написать программу, которая определяла бы минимальную сумму денег, которая может находиться в копилке, зная её вес без монет, вес с монетами и вес монет каждого типа. Ввод из файла INPUT.TXT . В первой строке содержатся два целых числа: E – вес пустой копилки (1 ≤ E ≤10000); F – вес копилки, заполненной монетами (1 ≤ E≤ F≤ 10000). Вторая строка содержит целое число N (1≤N≤500) — количество типов монет. Каждая из последующих N строк служит для описания монет заданных типов и содержит по два целых числа — Pi и Wi (1 ≤ Pi ≤ 30000, 1 ≤ Wi ≤ 10000, 1 ≤ i ≤ N), где Pi — достоинство монеты i-го типа, а Wi — ее вес. Вывод в файл OUTPUT.TXT. В единственной строке выводится значение минимальной суммы денег, которая может находиться в копилке. Если заданный вес копилки F не может быть достигнут с монетами заданного типа, то выходной файл должен содержать No. Пример
|
Loading
|