Автор работы: Пользователь скрыл имя, 09 Декабря 2013 в 21:49, курсовая работа
Динамическое программирование (ДП) определяет оптимальное решение n-мерной задачи путем ее декомпозиции на n этапов, каждый из которых представляет собой подзадачу относительно одной переменной. Вычислительное преимущество такого подхода состоит в том, что мы занимаемся решением одномерных оптимизационных задач подзадач вместо большой n-мерной задачи.
Фундаментальным принципом ДП, составляющим основу декомпозиции задачи на этапы, является оптимальность. Так как природа каждого этапа решения зависит от конкретной оптимизационной задачи, ДП не предлагает вычислительных алгоритмов непосредственно для каждого этапа. Вычислительные аспекты решения оптимизационных подзадач на каждом этапе проектируются и реализуются по отдельности (но это не исключает того, что может быть применен единый алгоритм для всех этапов).
Введение
Динамическое программирование (ДП) определяет оптимальное решение n-мерной задачи путем ее декомпозиции на n этапов, каждый из которых представляет собой подзадачу относительно одной переменной. Вычислительное преимущество такого подхода состоит в том, что мы занимаемся решением одномерных оптимизационных задач подзадач вместо большой n-мерной задачи.
Фундаментальным принципом ДП, составляющим основу декомпозиции задачи на этапы, является оптимальность. Так как природа каждого этапа решения зависит от конкретной оптимизационной задачи, ДП не предлагает вычислительных алгоритмов непосредственно для каждого этапа. Вычислительные аспекты решения оптимизационных подзадач на каждом этапе проектируются и реализуются по отдельности (но это не исключает того, что может быть применен единый алгоритм для всех этапов).
Вычисления в ДП выполняются рекуррентно в том смысле, что оптимальные решения одной подзадачи используются в качестве исходных данных для следующей подзадачи. Способ выполнения рекуррентных вычислений зависит от того, как выполняются декомпозиции исходной задачи. В данной ситуации возможны несколько вариантов. Первый из них это проводить вычисления последовательно от первого до последнего этапа, такая последовательность известна как алгоритм прямой прогонки. Задача может быть решена с помощью алгоритма обратной прогонки, в соответствии с которым вычисления проводятся от последнего этапа до первого. Очевидно, что алгоритмы прямой и обратной прогонки приводят к одному и тому же решению. Обычно более логичным представляется использовать алгоритм прямой прогонки, но в общем случае алгоритм обратной прогонки может быть более эффективным с вычислительной точки зрения. Продемонстрируем ДП на решение конкретных задач. Здесь будут рассмотрены следующие задачи:
Задача о наибольшей общей подпоследовательности
В различных биологических задачах часто необходимо сравнивать ДНК двух или нескольких организмов. Моделью молекулы ДНК можно считать строку, над алфавитом из четырех символов (А, Г, Ц, Т). Тогда поставить задачу можно следующим образом: даны две строки, требуется найти подпоследовательность наибольшей длины, входящую в оба слова. Теперь формализуем задачу в несколько более общем случае.
Пусть у нас есть последовательность X = {x1, x2
Пример. Z = {B, C, D, B} — подпоследовательность X = {A,
Говорят, что Z — общая подпоследовательность X и Y, если она является подпоследовательность
Теперь задача окончательно
формализована, и можно приступать
к ее решению. Введем понятие i-го префикса для заданной
входной строки. Пусть имеется последовательностьX = {x1, x2,
Теорема (о структуре НОП)
Пусть Z = {z1, z2, …, zk} — это любая НОП
для последовательностей X = {x1, x
Доказательство.
Непосредственно из теоремы
следует способ нахождения НОП двух
заданных последовательностей X = {x1, x
Остается только выбрать какой из двух алгоритмов прогонки здесь больше подходит, прямой или обратной. С вычислительной точки зрения наиболее рациональным будет алгоритм прямой прогонки, вычисляющий НОП снизу вверх. В случае вычисления очень удобно организовать в виде таблицы c[0…n, 0…m], где c[i][j] соответствует длине НОП префиксов Xi и Yj. Значение c[i][j] можем вычислять по следующим правилам:
Таким образом, мы можем найти
длину НОП, а для того чтобы
найти саму НОП, необходимо хранить
некоторую дополнительную информацию.
Удобно хранить информацию, о том,
по какому условию осуществляется переход.
На примере это можно
LCS-LENGTH(X,Y)
1 m = length[X]
2 m = length[Y]
3 for i = 1 to m
4 do c[i, 0] = 0
5 for i = 1 to m
6 do c[0, j] = 0
7 for i = 1 to m do
8 do for j = 1 to n
9 do if xi = yi
10 then c[i,j] = c[i − 1, j − 1] + 1;
11 b[i,j] = «стрелка по диагонали»
12 else if c[i − 1, j] ≥ c[i, j - 1]
13 then c[i, j] = c[i − 1, j]
14 b[i, j] = «стрелка вверх»
15 else c[i, j] = c[i, j − 1]
16 b[i, j] = «стрелка влево»
17 return b, c
На следующем рисунке
показана работа алгоритма для X = {A, B, C, B,
Теперь покажем, как непосредственно найти саму НОП.
PRINT_LCS(b, X, i, j)
1 if i = 0 or j = 0
2 then return
3 if b[i, j] = «стрелка по диагонали»
4 then PRINT_LCS(b, X, i − 1, j − 1)
5 print vi
6 elseif b[im j] = «стрелка вверх»
7 then PRINT_LCS(b, X, i − 1, j)
8 else PRINT_LCS (b, X, i, j − 1)
Может показаться, что эта задача была разобрана слишком подробно, но это сделано лишь для того, чтобы прояснить все детали происходящего, в следующих примерах некоторые мелкие детали могут быть опущены. Более интересный пример для развития приведенных выше идей, представляет собой следующая задача.
Связь динамического программирования и регулярных выражений
Шаблоном называется строка состоящая из букв латинского алфавита (a, …, z, A, …, Z) и символов ? и *. Каждый из символов ? разрешается заменить на одну произвольную букву, а каждый из символов * на произвольную (возможно пустую) последовательность букв. Про любую строку из букв, которую можно получить из шаблонов такими заменами, будем говорить, что она удовлетворяет данному шаблону. Тогда задачу состоит в том, чтобы для двух заданных шаблонов найти строку минимальной длины, которая удовлетворяет обоим шаблонам, либо выяснить, что такой строки не существует.
Нельзя не отметить, что
подобного рода задачи, находят большое
применение на практике, обычно они
возникают при анализе и
Вычисляем значение F(i, j) в порядке возрастания i, а при равных i, в порядке возрастания j. Возможны следующие содержательные ситуации (считаем, что i, j > 0, а разбор граничных случаев, не представляет особого интереса, и может быть оставлен как упражнение).
Задача триангуляции
В качестве еще одного примера
динамического программирования рассмотрим задачу триангуляции многоугольника.
Заданы вершины многоугольника и расстояния
между каждой парой вершин. Это расстояние
может быть геометрическим расстоянием
на плоскости или произвольной функцией
стоимости, заданной в виде таблицы. Задача
заключается в том, чтобы выбрать такую
совокупность хорд (линий между
несмежными вершинами), что никакие две
хорды не будут пересекаться, а весь многоугольник
будет поделен на треугольники. Общая
длин всех хорд должна быть минимальной.
Такая совокупность хорд называется минимальной
триангуляцией. Зафиксируем несколько
фактов, которые помогут разработать необходимый
алгоритм. Предполагаем, что наш многоугольник
имеет n вершин v0, v1, …, vn−1
Чтобы приступить к поиску минимальной триангуляции, выберем две смежные вершины, например v0 и v1. Из указанных выше фактов следует, что в любой триангуляции (а значит и в минимальной) должна найтись такая вершина vk, что (v1, vk) и (vk, v0) являются хордами или сторонами многоугольника. Необходимо рассмотреть, насколько приемлемую триангуляцию можно построить после выбора каждого значения k. Если в многоугольнике n вершин, то в нашем распоряжении имеется (n−2) возможных вариантов. Каждый вариант выбора k приводит к не более чем двум подзадачам, где мы имеем многоугольники образованные, одной хордой и сторонами исходного многоугольника. Например, могут возникнуть такие подзадачи при случае выбора вершины v3.
Далее нужно найти минимальную триангуляцию для многоугольников, полученных в результате появления новых подзадач. Правда, если мы будем решать все подзадачи, которые будут появляться, то мы получим алгоритм с экспоненциальной трудоемкостью. Но, имея треугольник, включающий хорду (v0, vk), нам никогда не придется рассматривать многоугольники, у которых более одной стороны являются хордами исходного многоугольника. «Факт 2» говорит о том, что при минимальной триангуляции хорда в подзадаче (например, хорда (v0, v3) на рис. 1) должна составлять треугольник с одной из остальных вершин многоугольника.
Определим подзадачу размера s, начинающуюся с вершины vi и обозначаемую Sis, как задачу минимальной триангуляции для многоугольника, образованного s вершинами, начинающегося с вершины vi и содержащего вершины vi, vi+1, …, vi+s−1, перечисляемые в порядке обхода вершин по часовой стрелке. В v0 хордой является замыкающая сторона (vi, vi+s−1). Чтобы решить подзадачу Sis необходимо рассмотреть три варианта: