Transcript
Page 1: Алгоритмы и структуры данных осень 2013 лекция 8

Лекция 8.Динамическое

программирование и жадные алгоритмы.

Мацкевич С. Е.

Page 2: Алгоритмы и структуры данных осень 2013 лекция 8

План лекции «Динамическое программирование и жадные алгоритмы»

Динамическое программирование.

Наибольшая общая подпоследовательность.

Расстояние Левенштейна.

Жадные алгоритмы.

Покрытие отрезками.

Задача о рюкзаке.

2

Page 3: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

Динамическое программирование (ДП) – способ решения сложных задач путём разбиения их на более простые подзадачи.

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

Термин «динамическое программирование» также встречается в теории управления в смысле «динамической оптимизации». Основным методом ДП является сведение общей задачи к ряду более простых экстремальных задач.Лит.: Беллман Р., Динамическое программирование, пер. с англ., М., 1960.

3

Page 4: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

Пример. Числа Фибоначчи. 𝐹𝑛 = 𝐹𝑛−1 + 𝐹𝑛−2. 𝐹1 = 1, 𝐹0 = 1.

Можно вычислять рекурсивно:𝐹5 = 𝐹4 + 𝐹3,𝐹4 = 𝐹3 + 𝐹2,𝐹3 = 𝐹2 + 𝐹1,Многие значения могут вычислятьсянесколько раз.Решение – сохранять результатырешения подзадач.Этот подход называется кэшированием.

4

Page 5: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

Пример. Вычисление рекуррентных функций нескольких аргументов.

𝐹 𝑥, 𝑦 = 3 ⋅ 𝐹 𝑥 − 1, 𝑦 − 2 ⋅ 𝐹2 𝑥, 𝑦 − 1 ,

𝐹 𝑥, 0 = 𝑥, 𝐹 0, 𝑦 = 0.

Вычисление 𝐹 𝑥, 𝑦 сводится к вычислению двух 𝐹 ⋅,⋅ от меньших аргументов.

Есть перекрывающиеся подзадачи.

𝐹 𝑥 − 1, 𝑦 − 1 в рекурсивном решении вычисляется дважды.

𝐹 𝑥 − 2, 𝑦 − 1 в рекурсивном решении вычисляется три раза.

𝐹 𝑥 − 𝑛, 𝑦 − 𝑚 в рекурсивном решении вычисляется 𝐶𝑛+𝑚𝑛 раз.

Снова будем использовать кэширование – сохранять результаты.

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

5

Page 6: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

6

// Вычисление рекуррентного выражения от двух переменных.int F( int x, int y ){

vector<vector<int>> values( x + 1 );for( int i = 0; i <= x; ++i ) {

values[i].resize( y + 1 );values[i][0] = i; // F( x, 0 ) = x;

}for( int i = 1; i <= y; ++i ) {

values[0][i] = 0; // F( 0, y ) = 0;}// Вычисляем по рядам.for( int i = 0; i <= x; ++i ) {

for( int j = 0; j <= y; ++j ) {values[i][j] = 3 * values[i – 1][j] –

2 * values[i][j – 1] * values[i][j – 1];}

}return value[x][y];

}

Page 7: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

При вычисление рекуррентной функции 𝐹 𝑥, 𝑦 можно было не хранить значения на всех рядах.Для вычисления очередного ряда достаточно иметь значения предыдущего ряда.

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

Для вычисления следующего числа Фибоначчи 𝐹𝑖 также достаточно хранить лишь два предыдущих значения:𝐹𝑖−1 и 𝐹𝑖−2.

7

Page 8: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

Принципы ДП:

1. Разбить задачу на подзадачи.

2. Кэшировать результаты решения подзадач.

3. Удалять более неиспользуемые результаты решения подзадач (опционально).

8

Page 9: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

Два подхода динамического программирования:

1. Нисходящее динамическое программирование: задача разбивается на подзадачи меньшего размера, они решаются и затем комбинируются для решения исходной задачи. Используется запоминание для решений часто встречающихся подзадач.

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

9

Page 10: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

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

Пример. 𝐹 𝑛 = 𝐹𝑛

2+ 𝐹

2⋅𝑛

3, 𝐹 0 = 𝐹 1 = 1, деление

целочисленное.

Восходящее динамическое программирование применить можно, но будут вычислены все значения F от 1 до n. Сложно определить, для каких k требуется вычислять 𝐹 𝑘 .

Нисходящее динамическое программирование позволит вычислить только те 𝐹 𝑘 , которые требуются, но рекурсивно. Максимальная глубина рекурсии = log 3 2 𝑛 .

10

Page 11: Алгоритмы и структуры данных осень 2013 лекция 8

Динамическое программирование

11

// Вычисление рекуррентного выражения методом нисходящего ДП.int F( int n ){

map<int, int> values;values[0] = 1;values[1] = 1;return calcF( n, values );

}

// Рекурсивное вычисление с запоминанием.int calcF( int n, map<int, int>& values ){

if( values.find( n ) != values.end() )return values[n];

values[n] = calcF( n / 2, values ) + calcF( 2 * n / 3, values );return values[n];

}

Page 12: Алгоритмы и структуры данных осень 2013 лекция 8

Наибольшая общая подпоследовательность

Последовательность X является подпоследовательностью Y, если из Y можно удалить несколько элементов так, что получится последовательность X.

Задача. Наибольшая общая подпоследовательность (англ. longest common subsequence, LCS) – задача поиска последовательности, которая является подпоследовательностьюнескольких последовательностей (обычно двух).

Элементами подпоследовательности могут быть числа, символы…

X = ABCAB,Y = DCBA,НОП( X, Y ) = BA, CA, CB.

12

Page 13: Алгоритмы и структуры данных осень 2013 лекция 8

Наибольшая общая подпоследовательность

Будем решать задачу нахождения наибольшей общей подпоследовательности с помощью Динамического программирования.

Сведем задачу к подзадачам меньшего размера:

𝑓 𝑛1, 𝑛2 – длина наибольшей общей подпоследовательностистрок 𝑠1 0. . 𝑛1 , 𝑠2 0. . 𝑛2 .

13

Page 14: Алгоритмы и структуры данных осень 2013 лекция 8

Наибольшая общая подпоследовательность

A B C A B

0 0 0 0 0 0

D 0 ←↑ 0 ←↑ 0 ←↑ 0 ←↑ 0 ←↑ 0

C 0 ←↑ 0 ←↑ 0 ↖ 1 ← 1 ← 1

B 0 ←↑ 0 ↖ 1 ←↑ 1 ←↑ 1 ↖ 2

A 0 ↖ 1 ←↑ 1 ←↑ 1 ↖ 2 ←↑ 2

14

Page 15: Алгоритмы и структуры данных осень 2013 лекция 8

Наибольшая общая подпоследовательность

𝑓 𝑛1, 𝑛2 – длина НОП.

Как восстановить саму подпоследовательность?

Можно хранить в каждой ячейке таблицы «направление» перехода. Переход по диагонали означает, что очередной символ присутствует в обоих последовательностях.

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

«Направления» можно не хранить, а вычислять по значениям в таблице.

15

Page 16: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна

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

Впервые задачу упомянул в 1965 году советский математик Владимир Иосифович Левенштейн при изучении последовательностей 0-1.

16

Page 17: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна

Расстояние Левенштейна и его обобщения активно применяется:

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

для сравнения текстовых файлов утилитой diff и ей подобными. Здесь роль «символов» играют строки, а роль «строк» —файлы.

в биоинформатике для сравнения генов, хромосом и белков.

17

Page 18: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна

Будем вычислять расстояние Левенштейна с помощью Динамического программирования.

18

Page 19: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна

Пример.

19

Page 20: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна.

Доказательство.

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

Две замены разных символов можно менять местами

Стирание и вставку разных символов можно менять местами

Вставка символа и замена другого символа меняются местами

Стирание символа и замена другого символа меняются местами

Два стирания или две вставки можно менять местами

Две замены одного и того же символа — неоптимально (если мы заменили x на y, потом y на z, выгоднее было сразу заменить x на z)

Вставка символа с его последующим стиранием — неоптимально (можно их обе отменить)

Вставка символа с его последующей заменой — неоптимально (излишняя замена)

Замена символа с его последующим стиранием — неоптимально (излишняя замена)

20

Page 21: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна.

Доказательство.

Пусть 𝑆1 кончается на символ «a», 𝑆2 кончается на символ «b». Есть три варианта:

1. Если символ «а», на который кончается 𝑆1 , в какой-то момент был стёрт. Сделаем это стирание первой операцией. Тогда мы стёрли символ «a», после чего превратили первые i-1 символов 𝑆1 в 𝑆2 (на что потребовалось 𝐷 𝑖 − 1, 𝑗 операций), значит, всего потребовалось 𝐷 𝑖 − 1, 𝑗 + 1 операций.

2. Символ «b», на который кончается 𝑆2 , в какой-то момент был добавлен. Сделаем это добавление последней операцией. Мы превратили 𝑆1 в первые j-1 символов 𝑆2 , после чего добавили «b». Аналогично предыдущему случаю, потребовалось 𝐷 𝑖, 𝑗 − 1 + 1 операций.

3. Оба предыдущих утверждения неверны. Если мы добавляли символы справа от финального «a», то, чтобы сделать последним символом «b», мы должны были или в какой-то момент добавить его (но тогда утверждение 2 было бы верно), либо заменить на него один из этих добавленных символов (что тоже невозможно, потому что добавление символа с его последующей заменой неоптимально). Значит, символов справа от финального «a» мы не добавляли. Самого финального «a» мы не стирали, поскольку утверждение 1 неверно. Значит, единственный способ изменения последнего символа — его замена. Заменять его 2 или больше раз неоптимально. Значит,

Если 𝑎 = 𝑏, мы последний символ не меняли. Поскольку мы его также не стирали и не приписывали ничего справа от него, он не влиял на наши действия, и, значит, мы выполнили 𝐷 𝑖 − 1, 𝑗 − 1 операций.

Если 𝑎 ≠ 𝑏, мы последний символ меняли один раз. Сделаем эту замену первой. В дальнейшем, аналогично предыдущему случаю, мы должны выполнить 𝐷 𝑖 − 1, 𝑗 − 1 операций, значит, всего потребуется 𝐷 𝑖 − 1, 𝑗 −

21

Page 22: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Левенштейна

22

// Вычисление расстояния Левенштейна.int LevenshteinDistance( const string& s, int sLength, const string& t,

int tLength ){

// Крайние случаи.if( sLength == 0 ) return tLength;if( tLength == 0 ) return sLength;

// Проверим совпадение последних символов.int cost = s[sLength - 1] == t[tLength - 1] ? 0 : 1;

// Вычисляем минимум.return min( LevenshteinDistance( s, sLength - 1, t, tLength ) + 1,

LevenshteinDistance( s, sLength, t, tLength – 1 ) + 1,LevenshteinDistance( s, sLength - 1, t, tLength – 1 ) + cost );

}

Page 23: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Дамерау-Левенштейна.

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

Является модификацией расстояния Левенштейна: к операциям вставки, удаления и замены символов, определенных в расстоянии Левенштейна добавлена операция транспозиции (перестановки) символов.

23

Page 24: Алгоритмы и структуры данных осень 2013 лекция 8

Расстояние Дамерау-Левенштейна.

Расстояние Дамерау-Левенштейна также вычисляется динамически.

24

Page 25: Алгоритмы и структуры данных осень 2013 лекция 8

Жадные алгоритмы

Жадный алгоритм (англ. Greedy algorithm) – алгоритм, заключающийся в принятии локально оптимальных решений на каждом этапе, допуская, что конечное решение также окажется оптимальным.

Доказательство оптимальности часто следует такой схеме:

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

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

3. Рассуждение завершается по индукции.

25

Page 26: Алгоритмы и структуры данных осень 2013 лекция 8

Жадные алгоритмы

Пример. Размен монет.

Монетная система некоторого государства состоит из монет достоинством 𝑎1 =1 < 𝑎2 < ⋯ < 𝑎𝑛 . Требуется выдать сумму S наименьшим возможным количеством монет.

Жадный алгоритм решения этой задачи:Берётся наибольшее возможное количествомонет достоинства 𝑎𝑛 : 𝑥𝑛 = 𝑆 𝑎𝑛 . Такжеполучаем, сколько нужно монет меньшего номинала.

Для данной задачи жадный алгоритм не всегда даётоптимальное решение.Например. Монеты в 1, 5 и 7коп. Сумма 24.Жадный алгоритм разменивает так:7к. — 3 шт., 1к. — 3 шт.Правильное решение:7к. — 2 шт., 5к. — 2 шт.

Тем не менее, на всех реальных монетных системах жадный алгоритм даёт правильный ответ.

26

Page 27: Алгоритмы и структуры данных осень 2013 лекция 8

Покрытие отрезками

Задача. Дано множество отрезков 𝑎𝑖 , 𝑏𝑖 , покрывающее отрезок 0, 𝑋 . Найти наименьшее подпокрытие, т.е. минимальный набор

отрезков, по-прежнему покрывающий отрезок 0, 𝑋 .

Жадное решение.

Упорядочим набор отрезков по возрастанию левого конца 𝑎𝑖 .Шаг 1. Среди отрезков, содержащих 0, найдем такой, у которого

наибольший правый конец 𝑏𝑖 . Обозначим этот отрезок 𝑎1, 𝑏1 .

Шаг 2. Среди отрезков, содержащих 𝑏1, найдем такой, у которого

наибольший правый конец 𝑏𝑖 . Обозначим этот отрезок 𝑎2, 𝑏2 .

И так далее.

27

Page 28: Алгоритмы и структуры данных осень 2013 лекция 8

Покрытие отрезками

Утверждение. Жадное решение является оптимальным.

Доказательство. От противного. Пусть жадное решение состоит из K отрезков 𝑎𝑖 , 𝑏𝑖 . И пусть существует более оптимальное решение 𝑎𝑖 , 𝑏𝑖 , состоящие из менее чем K отрезков.

Упорядочим отрезки в решениях по правому краю. Заметим, что отрезки также будут упорядочены и по левому краю в оптимальном решении и в жадном решении.

Найдем первый отрезок, отличающийся в жадном решении от оптимального. У такого отрезка оптимального решения меньше правый край и он по-прежнему покрывает правый край предыдущего общего отрезка. Значит, его можно заменить на отрезок из жадного решения.

Так, заменяя отрезки оптимального решения на отрезки жадного, получим, что менее K первых отрезков жадного решения являются оптимальным решением, что невозможно по построению жадного решения.

28

Page 29: Алгоритмы и структуры данных осень 2013 лекция 8

Задача о рюкзаке

Задача о рюкзаке (англ. Knapsack problem) — одна из NP-полных задач комбинаторной оптимизации. Название своё получила от максимизационной задачи укладки как можно большего числа нужных вещей в рюкзак при условии, что общий объём (или вес) всех предметов, способных поместиться в рюкзак, ограничен.

Имеется N грузов. Для каждого i-го груза определён вес 𝑤𝑖 и ценность 𝑐𝑖 . Нужно упаковать в рюкзак ограниченной грузоподъёмности G те грузы, при которых суммарная ценностьупакованного была бы максимальной.

29

Page 30: Алгоритмы и структуры данных осень 2013 лекция 8

Задача о рюкзаке

Жадный алгоритм.

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

В оставшееся место помещаем следующий предмет аналогично.

Точное решение можно получить не всегда.

30

Page 31: Алгоритмы и структуры данных осень 2013 лекция 8

Задача о рюкзаке

Пример.

Пусть вместимость рюкзака 90. Предметы уже отсортированы. Применяем к ним жадный алгоритм.

вес цена цена/вес

1 20 60 3

2 30 90 3

3 50 100 2

Кладём в рюкзак первый, а за ним второй предметы. Третий предмет в рюкзак не влезет. Суммарная ценность поместившегося равна 150. Если бы были взяты второй и третий предметы, то суммарная ценность составила бы 190. Видно, что жадный алгоритм не обеспечивает оптимального решения, поэтому относится к приближенным.

31

Page 32: Алгоритмы и структуры данных осень 2013 лекция 8

Вопросы?

Спасибо за внимание!


Recommended