Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Дасгупты, Пападимитриу, Вазирани «Алгоритмы»

.pdf
Скачиваний:
176
Добавлен:
13.02.2015
Размер:
1.8 Mб
Скачать

6.3. Расстояние редактирования

161

В первом случае стоимость этого столбца равна 1, а оставшиеся столбцы

представляют собой выравнивание строк x[1‌i 1] и y[1‌j], причём оптимальное. А это в точности подзадача E[i 1, j], и она уже решена. Во втором случае стоимость последнего столбца также равна 1, и остаётся задача E[i, j 1]. В последнем же случае стоимость равна 1, если x[i] 6= y[j], и 0, если x[i] = y[j], а остаётся задача E[i 1, j 1]. Мы не знаем, какой из вариантов реально происходит в оптимальном выравнивании, поэтому проверим

все и выберем лучший:

 

 

DRAFT

E[i, j] = minf1 + E[i 1, j], 1

+ E[i, j 1], diff(i, j) + E[i 1, j 1]g,

где diff(i, j) = 0, если x[i] = y[j], и 1 в противном случае.

Например, для слов EXPONENTIAL и POLYNOMIAL подзадача E[4, 3] соот-

ветствует префиксам EXPO и POL. Правый столбец их наилучшего выравни-

вания может выглядеть одним из следующих способов:

O

 

O

 

L

L

Поэтому E[4, 3] = minf1 + E[3, 3], 1 + E[4, 2], 1 + E[3, 2]g.

Решения всех подзадач E[i, j] образуют двумерную таблицу (рис. 6.4).

В каком порядке следует решать эти подзадачи? Подойдёт любой порядок, где E[i 1, j], E[i, j 1] и E[i 1, j 1] обрабатываются раньше, чем E[i, j]. Например, мы можем заполнять таблицу по строкам, сверху вниз, а внутри строки слева направо. Или, наоборот, по столбцам. В обоих случаях к тому времени, когда мы подойдём к вычислению очередной ячейки, требуемые для неё значения будут уже известны.

Рис. 6.4. (a) Таблица подзадач. Для вычисления значения E[i, j] необходимы значения E[i 1, j 1], E[i 1, j] и E[i, j 1]. (b) Заполненная таблица.

(a)

 

 

 

 

 

 

 

 

 

(b)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

j

 

1 j

 

 

n

 

 

P

O

L

Y

N

O

M

I

A

L

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

1

2

3

4

5

6

7

8

9

10

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

E

1

1

2

3

4

5

6

7

8

9

10

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

X

2

2

2

3

4

5

6

7

8

9

10

 

i

 

1

 

 

 

 

 

 

 

 

 

 

 

P

3 2 3 3 4 5 6 7 8 9 10

 

 

i

 

 

 

 

 

 

 

 

 

 

 

O

4 3 2 3 4 5 5 6 6 8 9

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

N

5

4

3

3

4

4

5

6

6

8

9

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

E

6

5

4

4

4

5

5

6

6

8

9

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

N

7

6

5

5

5

4

5

6

6

8

9

 

 

 

 

 

 

 

 

 

 

 

 

 

 

T

8

7

6

6

6

5

5

6

6

8

9

 

 

m

 

 

 

 

 

 

 

 

 

цель

 

 

 

 

 

 

 

 

 

 

 

 

 

 

I

9

8

7

7

7

6

6

6

6

7

8

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A

10

9

8

8

8

7

7

7

7

6

7

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

L

11

10

9

8

9

8

8

8

8

7

6

 

 

 

 

 

 

Остаётся понять, с чего начинать (когда формула ещё не применима).

В данной задаче начальными значениями являются E[ , 0] и E[0, ] и за-

полняются они очевидным образом: E[0, j] есть расстояние редактирования

162

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

между пустой строкой и первыми j символами из y, то есть j. Аналогично

E[i, 0] = i.

Теперь у нас всё готово для записи алгоритма.

для i от 0 до m:

E[i, 0] i

для j от 0 до n:

E[0, j]

j

 

для i от 1 до m:

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

для j от 1

до n:

E[i, j]

minfE[i 1, j] + 1, E[i, j 1] + 1, E[i 1, j 1] + diff(i, j)g

вернуть

E[m, n]

Эта процедура заполняет таблицу построчно, слева направо в каждой строке. Обработка каждой ячейки занимает время O(1), поэтому общее время работы пропорционально размеру таблицы, то есть O(mn).

В рассмотренном нами ранее примере расстояние редактирования равно 6:

E X P

O

N E

N

– T

I

A

L

– – P

O

L Y

N

O M I

A

L

Граф задачи и путь в нём

Мы уже говорили, что подзадачи можно рассматривать как вершины графа, в котором рёбра отражают зависимости между ними. В задаче нахождения расстояния редактирования вершинами можно считать клетки (i, j) таблицы, а рёбра соответствуют рекуррентной формуле:

(i 1, j) ! (i, j), (i, j 1) ! (i, j), (i 1, j 1) ! (i, j)

(см. рис. 6.5). Рёбрам этого графа можно так присвоить веса, что оптимальное расстояние редактирования будет равно длине кратчайшего пути. Для этого рёбрам типа (i 1, j 1) ! (i, j) при x[i] = y[j] (пунктирным) присвоим вес 0, а всем остальным –– вес 1. Ответом тогда будет просто длина кратчайшего пути из s = (0, 0) в t = (m, n). На рисунке показан один из таких путей. Он соответствует как раз оптимальному выравниванию, рассмотренному нами ранее. Каждый шаг вниз на этом пути соответствует удалению, вправо –– вставке, а по диагонали –– либо совпадению, либо замене.

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

Забравшийся в магазин вор нашёл больше добычи, чем он может унести с собой. Его рюкзак выдерживает не больше W килограммов. Ему надо выбрать

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

163

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 6.5. Граф выравнивания и путь длины 6.

 

 

 

 

 

 

 

 

 

 

 

 

P

O

 

L

 

Y

 

N

 

O

 

M

I

 

A

 

L

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

E X P O N E N T I A L

DRAFTЕсть две разновидности этой задачи. Если каждый товар имеется в неограниченном количестве, то оптимально будет взять товар номер 1 и две штуки товара номер 4 (общая стоимость: 48). Если же каждый товар есть в

какие-то из n товаров веса w1, ‌, wn и стоимости v1, ‌, vn. Как найти самый дорогой вариант?1

Пусть, например, рюкзак выдерживает W = 10 килограммов, а в магазине

имеются следующие изделия:

 

 

Товар

Вес

Стоимость

1

6

30

2

3

14

3

4

16

4

2

9

1Если этот пример кажется чересчур легкомысленным, несложно выбрать другие математически эквивалентные формулировки.

164

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

Часто используемые подзадачи

Включение задачи в семейство подзадач требует изобретательности –– но есть несколько стандартных вариантов.

ffВход –– последовательность x1, x2, ‌, xn, подзадача –– тот же вопрос для префикса x1, x2, ‌, xi для i < n.

 

x1

x2

x3

x4

x5

x6

x7

x8

x9

x10

 

DRAFT

Количество таких подзадач линейно.

 

 

 

 

ff Вход –– последовательности x1

, ‌, xm и y1, ‌, yn, подзадачи –– префиксы

x1, ‌, xi и y1, ‌, yj для i < m

и j < n.

 

 

 

 

 

x1

x2

x3

x4

x5

x6

x7

x8

x9

x10

 

y1

y2

y3

y4

y5

y6

y7

y8

 

 

Количество подзадач есть O(mn).

ff Вход –– последовательность x1, ‌, xn, подзадача –– участок xi , ‌, x j для i < j < n.

x1 x2 x3 x4 x5 x6 x7 x8 x9 x10

Количество подзадач есть O(n2).

ff Вход –– корневое дерево, подзадача –– поддерево.

Сколько в этом случае будет подзадач?

Первые два случая нам уже встречались, скоро очередь дойдёт и до остальных.

единственном экземпляре (как в художественной галерее), тогда оптимальным будет набор из 1 и 3 (стоимость: 46).

В главе 8 мы увидим, что полиномиальных по времени алгоритмов для этих двух задач, скорее всего, не существует. Однако динамическое програм-

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

165

мирование позволяет решить обе задачи (в случае целых весов) за время O(nW ), которое вполне допустимо для малых W , но всё же не является полиномиальным, так как размер входа пропорционален log W , а не W .

Задача о рюкзаке с повторениями

Начнём с варианта с повторениями. Как обычно, важно правильно выбрать подзадачи. В данном случае есть два естественных способа: рассмотреть рюк-

зак меньшей ёмкости w W (для краткости максимально допустимый вес мы называем «ёмкостью») или же меньшее число товаров (скажем, товары 1, 2, ‌, j, где j n). Для того чтобы понять, какой подход действительно работает, обычно приходится немного поэкспериментировать.

DRAFTгде, как обычно, максимум по пустому множеству считается равным 0. Получаем простой алгоритм:

Попробуем взять рюкзак меньшей ёмкости и положим

K[w] = максимально возможная стоимость для рюкзака ёмкости w.

Можем ли мы выразить K[w] через ответы для меньших подзадач? Ясно, что если в оптимальное заполнение рюкзака ёмкости w входит товар i, то без одной штуки этого товара мы получим оптимальное заполнение рюкзака ёмко-

сти w wi . Другими словами, K[w] –– это просто K[w wi ] + vi для некото-

рого i. Мы не знаем, для какого именно i, поэтому нам нужно перебрать все

возможные варианты:

 

 

 

 

 

 

 

 

max

K

[

w

 

w

v

i g

,

K[w] = i : wi wf

 

 

 

i ] +

 

K[0] 0

для w от 1 до W :

K[w] maxfK[w wi ] + vi : wi wg

вернуть K[W ]

Алгоритм последовательно заполняет массив размера W + 1. Значение каждой ячейки вычисляется за время O(n), общее время работы O(nW ).

Как обычно, данной задаче соответствует некоторый ориентированный ациклический граф на подзадачах. Решение исходной задачи можно свести к нахождению самого длинного пути в этом графе.

Задача о рюкзаке без повторений

Теперь рассмотрим вариант задачи, когда каждый товар есть в одном экземпляре. Тогда воспользоваться подзадачами из прошлого решения не удаётся, поскольку надо как-то учитывать, что мы уже взяли. Сделаем это, добавив второй параметр 0 ¶ j n: обозначим через K[w, j] максимальную стоимость унесённого, если разрешается уносить лишь товары 1, ‌, j и общий вес должен быть не больше W . Исходная задача: найти K[W, n].

Теперь нужно научиться выражать K[w, j] через результаты для меньших подзадач. Это несложно. В оптимальном заполнении товар j либо участвует,

166 Глава 6. Динамическое программирование

О мышах и людях

В каждой живой клетке есть «программа» –– последовательность символов алфавита {A, C, G, T} (нуклеотидов), записанная в молекулах ДНК (дезоксирибонуклеиновой кислоты).

У любых двух людей последовательности ДНК (длиной примерно в 3 миллиарда символов) отличаются всего лишь на 0,1%. Но этих трёх миллионов различий достаточно, чтобы люди были совсем разные. Эти различия важны для биологии и медицины –– например, анализ ДНК может вы-

DRAFTявить предрасположенность к некоторым болезням.

ДНК –– огромная программа, с которой учёные только начинают разбираться. В ней есть участки с конкретными ролями (производство тех или иных белков), которые называют генами. При их обнаружении используются компьютеры, и эта область называется вычислительной геномикой. Как могут помочь тут наши алгоритмы?

1. При открытии нового гена в геноме человека один из способов понять его функцию –– найти похожие известные гены (возможно, у других хорошо изученных видов –– например, мышей). Слово «похожие» можно уточнять с помощью того или иного расстояния.

База данных GenBank, в которой хранятся известные гены, уже имеет общую длину более 1010 нуклеотидов и продолжает стремительно расти. Сейчас для поиска обычно используется программа BLAST, при создании которой понадобились и алгоритмические трюки, и биологическая интуиция.

2. При секвенировании ДНК (DNA sequencing), то есть определении последовательности символов, обычно разрезают молекулу на куски (подстроки) длиной 500––700 нуклеотидов и все их «читают». Допустим, мы нашли миллиарды таких случайно рассеянных по всей строке фрагментов, но как восстановить исходную последовательность? Ведь ни для одного из этих кусочков мы не знаем, с какой позиции он идёт. Это надо определить, склеивая перекрывающиеся фрагменты, и это была непростая задача. Первые «черновики» полной ДНК человека появились в 2001 году у двух групп: «Human Genome Consortium» (с государственным финансированием) и «Celera Genomics» (частная компания).

3. Когда некий ген найден у нескольких видов, можно ли использовать эту информацию для выяснения происхождения видов?

Мы рассмотрим эти вопросы в упражнениях в конце главы.

либо нет:

K[w, j] = maxfK[w wj , j 1] + vj , K[w, j 1]g

(первый член берётся, только если wj w.) Другими словами, можно выразить K[w, j] через результаты подзадач K[ , j 1].

Алгоритм заполняет двумерный массив из W + 1 строки и n + 1 столбца. Каждая ячейка заполняется за время O(1). Поэтому несмотря на гораз-

6.5. Произведение матриц

167

до больший размер таблицы по сравнению с предыдущим алгоритмом общее время работы остаётся прежним: O(nW ).

для всех j, w:

K[0, j]

0; K[w, 0] 0

для j от 1

до n:

 

 

для w от 1 до W :

K[w, j 1]

если wj > w:

K[w, j]

иначе:

K[w, j] maxfK[w, j 1], K[w wj , j 1] + vj g

вернуть K[W, n]

 

 

6.5. Произведение матриц

Пусть нам необходимо вычислить произведение A B C D четырёх матриц размеров 50 20, 20 1, 1 10 и 10 100 (рис. 6.6). Для этого нужно умножать матрицы попарно в каком-то порядке. Как известно, матричное

Рис. 6.6. A B C D = (A (B C)) D.

(a)

 

 

 

 

 

 

 

(b)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

C

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

B 1 10

 

 

 

 

 

B C

 

 

 

 

A

D

 

A

D

 

50 20 20 1

10 100

50 20 20 10

10 100

 

(c)

 

 

 

 

 

 

 

(d)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A (B C)

 

 

 

 

 

 

 

 

 

D

 

 

 

 

(A (B C)) D

 

 

 

50 10

10 100

 

 

 

 

 

50 100

 

умножение не является коммутативным (в общем случае A B 6= B A), но является ассоциативным (A (B C) = (A B) C). Таким образом, мы можем выбирать, в каком порядке перемножать матрицы (или, что то же самое, как расставить скобки). Важен ли тут порядок?

Обычная формула для произведения матриц m n и n p использует O(mnp) арифметических операций. Считая, что нужно ровно mnp операций, сравним несколько разных способов вычисления A B C D:

Порядок

Подсчёт количества операций

 

Количество операций

 

A ((B C) D)

20 1 10 + 20 10 100 + 50 20 100

 

120200

(A (B C)) D

20 1 10 + 50 20 10 + 50 10 100

 

60200

DRAFT(A B) (C D) 50 20 1 + 1 10 100 + 50 1 100

 

7000

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

168 Глава 6. Динамическое программирование

Запоминание

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

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

DRAFTпри повторных вызовах с тем же входом.

В случае задачи о рюкзаке (с повторениями) такой алгоритм мог бы использовать хеш-таблицу (см. раздел 1.5) для хранения вычисленных ранее значений K[ ]. При запросе значения K[w] сначала проверялось бы, не присутствует ли оно уже в таблице, и только при его отсутствии производилось бы вычисление. Такой приём называется запоминанием (memoization).

{В хеш-таблицу помещаются значения K[w] (w –– в роли индекса).}

{Изначально таблица пуста.} функция Knapsack(w)

если w содержится в хеш-таблице:

вернуть K[w]

K[w] max{Knapsack(w wi ) + vi : wi w} добавить K[w] в хеш-таблицу с ключом w

вернуть K[w]

Этот алгоритм никогда не решает дважды одну подзадачу. Его время работы O(nW ), как и при использовании динамического программирования без запоминания. Но постоянный множитель, скрытый в O( ), при использовании рекурсии больше, чем в случае динамического программирования (из-за дополнительных проверок и накладных расходов при реализации рекурсии).

В некоторых случаях, однако, запоминание себя оправдывает. И вот почему: при динамическом программировании решаются все подзадачи, которые потенциально могли бы понадобиться, а при запоминании –– только те, которые реально понадобились. Например, пусть W и все веса wi кратны 100. Тогда подзадача K[w] бесполезна, если w не делится на 100. Рекурсивный алгоритм с запоминанием даже не будет рассматривать эти лишние клетки в таблице.

которые дешевле всего перемножить), приведёт нас ко второму варианту, далёкому от оптимума.

Как же найти оптимальный порядок перемножения матриц A1, A2, ‌, An размеров m0 m1, m1 m2, ‌, mn 1 mn соответственно? Порядок операций можно изобразить двоичным деревом, листьям которого будут соответствовать исходные матрицы, корню –– результат, а внутренним вершинам (у них

6.5. Произведение матриц

169

два ребёнка) –– промежуточные результаты (рис. 6.7). Количество таких деревьев с n листьями зависит от n экспоненциально (упражнение 2.13), поэтому их перебирать долго.

Рис. 6.7. (a) ((A B) C) D. (b) A ((B C) D). (c) (A (B C)) D.

(a) (b) (c)

DRAFT

D

D

A

 

 

C

 

 

D A

 

A B

B

C

B C

Вспомним, что поддеревья оптимального дерева оптимальны. Какие подзадачи соответствуют поддеревьям? Ими оказываются произведения Ai Ai+1 Aj . Следуя этой схеме, для 1 ¶ i j n положим

C[i, j] = минимальная стоимость вычисления Ai Ai+1 Aj .

Размер подзадачи –– это количество матричных операций, то есть j i. При i = j перемножать ничего не надо: C[i, i] = 0. При j > i рассмотрим оптимальное поддерево для вычисления C[i, j]. В двух его поддеревьях вычисляются

Ai Ak и Ak+1 Aj

для некоторого k между i и j. Стоимость всего

поддерева –– это сумма стоимостей двух частичных произведений и стоимо-

сти их перемножения: C[i, k] + C[k + 1, j] + mi 1

mk

mj . Нам остаётся най-

ти k, для которого эта стоимость окажется минимальной:

 

j g

 

 

C[i, j] = ik<jf

C

[

i, k

] +

C

[

k

+

1, j

] +

m

i 1

 

m

k

m

.

 

min

 

 

 

 

 

 

 

 

 

Получаем такой алгоритм (в котором s –– размер подзадачи):

для i

от 1 до n: C(i, i)

 

0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

для s

от 1 до n 1:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

для i от 1 до n s:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

j

i + s

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

C[i, j] minfC[i, k] + C[k + 1, j] + mi 1 mk mj : i k < jg

вернуть C[1, n]

Все подзадачи образуют двумерную таблицу. Вычисление значения каждой ячейки требует времени O(n). Поэтому общее время работы алгоритма есть O(n3).

170

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

6.6. Кратчайшие пути

Мы начали эту главу с простого алгоритма поиска кратчайшего пути в ориентированном ациклическом графе в качестве примера. Динамическое программирование полезно и в более сложных вариантах поиска кратчайших путей.

Кратчайшие надёжные пути

В практических задачах часто появляются разные дополнительные требования: супердешёвый маршрут с большим числом пересадок вряд ли нужен; пакет байтов, проходящий через большое число маршрутизаторов (даже очень быстрых), может потеряться. В таких ситуациях нужны пути, которые одновременно имеют малую длину (сумму длин рёбер) и малое количество рёбер. Скажем, в графе на рис. 6.8 кратчайший путь из вершины S в T состоит из

Рис. 6.8. Пути из S в T : длина и число рёбер.

A

2

B

 

1

 

4

S

5

1 T

5

2

1

 

C

3

D

 

 

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

Дан взвешенный граф G, вершины s и t и число k. Нужно найти кратчайший путь из s в t, проходящий не более чем по k рёбрам.

Можно ли приспособить алгоритм Дейкстры для этой задачи? Сложность в том, что алгоритм Дейкстры сосредоточен на длине кратчайшего пути и не заботится о числе рёбер.

Используя динамическое программирование, мы должны изобрести такие подзадачи, чтобы вся важная информация сохранилась для следующих этапов. В данном случае для каждой вершины v и каждого числа i k определим dist[v, i] как длину кратчайшего пути из s в v, проходящего не более чем по i рёбрам. Начальные значения dist[v, 0] бесконечны для всех вершин, кроме s, для которой dist[s, 0] = 0. Рекуррентная формула очевидна:

min

u, i

 

1

] +

l u, v

)g

.

dist[v, i] = (u,v)2Efdist[

 

 

(

 

Кратчайшие пути между всеми парами вершин

 

 

 

DRAFT

Пусть мы хотим найти кратчайшие пути не только между вершинами s и t,

а между всеми парами вершин. Можно запустить общий алгоритм нахождения кратчайших путей из пункта 4.6.1 (мы допускаем отрицательные рёбра)