Задача о наибольшей общей подпоследовательности — различия между версиями
Андрей (обсуждение | вклад) (→Псевдокод) |
Андрей (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
+ | {{Задача | ||
+ | |definition= | ||
Задача нахождения '''наибольшей общей подпоследовательности''' (''longest common subsequence, LCS'') — это задача поиска последовательности, которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух). | Задача нахождения '''наибольшей общей подпоследовательности''' (''longest common subsequence, LCS'') — это задача поиска последовательности, которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух). | ||
+ | }} | ||
== Определения == | == Определения == | ||
Строка 56: | Строка 59: | ||
<tex> x </tex>, <tex> y </tex> — данные последовательности; <tex> lcs[i][j] </tex> — LCS для префикса длины <tex> i </tex> последовательности <tex> x </tex> и префикса длины <tex> j </tex> последовательности <tex> y </tex>; <tex> prev[i][j] </tex> — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении <tex> lcs[i][j] </tex>. | <tex> x </tex>, <tex> y </tex> — данные последовательности; <tex> lcs[i][j] </tex> — LCS для префикса длины <tex> i </tex> последовательности <tex> x </tex> и префикса длины <tex> j </tex> последовательности <tex> y </tex>; <tex> prev[i][j] </tex> — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении <tex> lcs[i][j] </tex>. | ||
− | + | ''<font color="green">// подсчёт таблиц</font>'' | |
LCS(x, y) | LCS(x, y) | ||
m = length(x) | m = length(x) | ||
n = length(y) | n = length(y) | ||
− | for i = 1 to m | + | '''for''' i = 1 to m |
lcs[i][0] = 0 | lcs[i][0] = 0 | ||
− | for j = 0 to n | + | '''for''' j = 0 to n |
lcs[0][j] = 0 | lcs[0][j] = 0 | ||
− | for i = 1 to m | + | '''for''' i = 1 to m |
− | for j = 1 to n | + | '''for''' j = 1 to n |
− | if x[i] == y[j] | + | '''if''' x[i] == y[j] |
lcs[i][j] = lcs[i - 1][j - 1] + 1 | lcs[i][j] = lcs[i - 1][j - 1] + 1 | ||
prev[i][j] = pair(i - 1, j - 1) | prev[i][j] = pair(i - 1, j - 1) | ||
− | else | + | '''else''' |
− | if lcs[i - 1][j] >= lcs[i][j - 1] | + | '''if''' lcs[i - 1][j] >= lcs[i][j - 1] |
lcs[i][j] = lcs[i - 1][j] | lcs[i][j] = lcs[i - 1][j] | ||
prev[i][j] = pair(i - 1, j) | prev[i][j] = pair(i - 1, j) | ||
− | else | + | '''else''' |
lcs[i][j] = lcs[i][j - 1] | lcs[i][j] = lcs[i][j - 1] | ||
prev[i][j] = pair(i, j - 1) | prev[i][j] = pair(i, j - 1) | ||
− | + | ''<font color="green">// вывод НОП, вызывается как printLCS(prev, x, m, n)</font>'' | |
printLCS(prev, x, i, j) | printLCS(prev, x, i, j) | ||
− | if i == 0 or j == 0 // пришли к началу НОП | + | '''if''' i == 0 or j == 0 ''<font color="green">// пришли к началу НОП</font>'' |
− | return | + | '''return''' |
− | if prev[i][j] == pair(i - 1, j - 1) // если пришли в lcs[i][j] из lcs[i - 1][j - 1], то x[i] == y[j], надо вывести этот элемент | + | '''if''' prev[i][j] == pair(i - 1, j - 1) ''<font color="green">// если пришли в lcs[i][j] из lcs[i - 1][j - 1], то x[i] == y[j], надо вывести этот элемент</font>'' |
printLCS(prev, x, i - 1, j - 1) | printLCS(prev, x, i - 1, j - 1) | ||
print x[i] | print x[i] | ||
− | else | + | '''else''' |
− | if prev[i][j] == pair(i - 1, j) | + | '''if''' prev[i][j] == pair(i - 1, j) |
printLCS(prev, x, i - 1, j) | printLCS(prev, x, i - 1, j) | ||
− | else | + | '''else''' |
printLCS(prev, x, i, j - 1) | printLCS(prev, x, i, j - 1) | ||
Строка 94: | Строка 97: | ||
LCS2(x, y) | LCS2(x, y) | ||
− | if length(x) < length(y) // в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y | + | '''if''' length(x) < length(y) ''<font color="green">// в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y</font>'' |
swap(x, y) | swap(x, y) | ||
m = length(x) | m = length(x) | ||
n = length(y) | n = length(y) | ||
− | for j = 0 to n | + | '''for''' j = 0 to n |
lcs[0][j] = 0 | lcs[0][j] = 0 | ||
lcs[1][j] = 0 | lcs[1][j] = 0 | ||
− | for i = 1 to m | + | '''for''' i = 1 to m |
lcs[1][0] = 0 | lcs[1][0] = 0 | ||
− | for j = 1 to n | + | '''for''' j = 1 to n |
− | lcs[0][j] = lcs[1][j] // элемент, который был в a[1][j], теперь в предыдущей строчке | + | lcs[0][j] = lcs[1][j] ''<font color="green">// элемент, который был в a[1][j], теперь в предыдущей строчке</font>'' |
− | if x[i] == y[j] | + | '''if''' x[i] == y[j] |
lcs[1][j] = lcs[0][j - 1] + 1 | lcs[1][j] = lcs[0][j - 1] + 1 | ||
− | else | + | '''else''' |
− | if lcs[0][j] >= lcs[1][j - 1] | + | '''if''' lcs[0][j] >= lcs[1][j - 1] |
lcs[1][j] = lcs[0][j] | lcs[1][j] = lcs[0][j] | ||
− | else | + | '''else''' |
lcs[1][j] = lcs[1][j - 1] | lcs[1][j] = lcs[1][j - 1] | ||
− | // ответ — lcs[1][n] | + | ''<font color="green">// ответ — lcs[1][n]</font>'' |
Также можно заметить, что от <tex> (i - 1) </tex>-ой строчки нужны только элементы с <tex> (j - 1) </tex>-го столбца. В этом случае можно использовать лишь <tex> min(m, n) </tex> элементов таблицы: | Также можно заметить, что от <tex> (i - 1) </tex>-ой строчки нужны только элементы с <tex> (j - 1) </tex>-го столбца. В этом случае можно использовать лишь <tex> min(m, n) </tex> элементов таблицы: | ||
LCS3(x, y) | LCS3(x, y) | ||
− | if length(x) < length(y) // в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y | + | '''if''' length(x) < length(y) ''<font color="green">// в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y</font>'' |
swap(x, y) | swap(x, y) | ||
m = length(x) | m = length(x) | ||
n = length(y) | n = length(y) | ||
− | for j = 0 to n | + | '''for''' j = 0 to n |
lcs[j] = 0 | lcs[j] = 0 | ||
− | d = 0 // d — дополнительная переменная, в ней хранится lcs[i - 1][j - 1] | + | d = 0 ''<font color="green">// d — дополнительная переменная, в ней хранится lcs[i - 1][j - 1]'' |
− | + | ''// в lcs[j], lcs[j + 1], …, lcs[n] хранятся lcs[i - 1][j], lcs[i - 1][j + 1], …, lcs[i - 1][n]'' | |
− | + | ''// в lcs[0], lcs[1], …, lcs[j - 1] хранятся lcs[i][0], lcs[i][1], …, lcs[i][j - 1]</font>'' | |
− | for i = 1 to m | + | '''for''' i = 1 to m |
− | for j = 1 to n | + | '''for''' j = 1 to n |
tmp = lcs[j] | tmp = lcs[j] | ||
− | if x[i] == y[i] | + | '''if''' x[i] == y[i] |
lcs[j] = d + 1 | lcs[j] = d + 1 | ||
− | else | + | '''else''' |
− | if lcs[j] >= lcs[j - 1] | + | '''if''' lcs[j] >= lcs[j - 1] |
− | lcs[j] = lcs[j] // в lcs[j] и так хранится lcs[i - 1][j] | + | lcs[j] = lcs[j] ''<font color="green">// в lcs[j] и так хранится lcs[i - 1][j]</font>'' |
− | else | + | '''else''' |
lcs[j] = lcs[j - 1] | lcs[j] = lcs[j - 1] | ||
d = tmp | d = tmp | ||
− | + | ''<font color="green">// ответ — lcs[n]</font>'' | |
== Список литературы == | == Список литературы == | ||
− | + | * Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4 | |
[[Категория:Дискретная математика и алгоритмы]] | [[Категория:Дискретная математика и алгоритмы]] | ||
[[Категория:Динамическое программирование]] | [[Категория:Динамическое программирование]] |
Версия 21:04, 9 января 2015
Задача: |
Задача нахождения наибольшей общей подпоследовательности (longest common subsequence, LCS) — это задача поиска последовательности, которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух). |
Содержание
Определения
Определение: |
Последовательность | является подпоследовательностью (subsequence) последовательности , если существует строго возрастающая последовательность индексов таких, что для всех выполняется соотношение .
Другими словами, подпоследовательность данной последовательности — это последовательность, из которой удалили ноль или больше элементов. Например,
является подпоследовательностью последовательности , а соответствующая последовательность индексов имеет вид .Определение: |
Последовательность | является общей подпоследовательностью (common subsequence) последовательностей и , если является подпоследовательностью как , так и .
Постановка задачи
Даны две последовательности:
и . Требуется найти общую подпоследовательность и максимальной длины. Заметим, что таких подпоследовательностей может быть несколько.Наивная идея решения
Переберем все различные подпоследовательности обеих строк и сравним их. Тогда искомая LCS гарантированно найдётся, однако время работы алгоритма будет экспоненциально зависеть от длины исходных последовательностей.
Динамическое программирование
Данная задача решается с использованием принципа оптимальности на префиксе.
Доказательство оптимальности
Теорема: |
Пусть имеются последовательности и , а — их НОП.
|
Доказательство: |
|
Решение
Обозначим как
НОП префиксов данных последовательностей, заканчивающихся в элементах с номерами и соответственно. Получается следующее рекуррентное соотношение:
Очевидно, что сложность алгоритма составит
, где и — длины последовательностей.Построение подпоследовательности
Для каждой пары элементов помимо длины LCS соответствующих префиксов хранятся и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, можно восстановить всю наибольшую общую подпоследовательность.
Псевдокод
, — данные последовательности; — LCS для префикса длины последовательности и префикса длины последовательности ; — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении .
// подсчёт таблиц LCS(x, y) m = length(x) n = length(y) for i = 1 to m lcs[i][0] = 0 for j = 0 to n lcs[0][j] = 0 for i = 1 to m for j = 1 to n if x[i] == y[j] lcs[i][j] = lcs[i - 1][j - 1] + 1 prev[i][j] = pair(i - 1, j - 1) else if lcs[i - 1][j] >= lcs[i][j - 1] lcs[i][j] = lcs[i - 1][j] prev[i][j] = pair(i - 1, j) else lcs[i][j] = lcs[i][j - 1] prev[i][j] = pair(i, j - 1) // вывод НОП, вызывается как printLCS(prev, x, m, n) printLCS(prev, x, i, j) if i == 0 or j == 0 // пришли к началу НОП return if prev[i][j] == pair(i - 1, j - 1) // если пришли в lcs[i][j] из lcs[i - 1][j - 1], то x[i] == y[j], надо вывести этот элемент printLCS(prev, x, i - 1, j - 1) print x[i] else if prev[i][j] == pair(i - 1, j) printLCS(prev, x, i - 1, j) else printLCS(prev, x, i, j - 1)
Оптимизация для вычисления только длины НОП
Заметим, что для вычисления
нужны только -ая и -ая строчки матрицы . Тогда можно использовать лишь элементов таблицы:LCS2(x, y) if length(x) < length(y) // в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y swap(x, y) m = length(x) n = length(y) for j = 0 to n lcs[0][j] = 0 lcs[1][j] = 0 for i = 1 to m lcs[1][0] = 0 for j = 1 to n lcs[0][j] = lcs[1][j] // элемент, который был в a[1][j], теперь в предыдущей строчке if x[i] == y[j] lcs[1][j] = lcs[0][j - 1] + 1 else if lcs[0][j] >= lcs[1][j - 1] lcs[1][j] = lcs[0][j] else lcs[1][j] = lcs[1][j - 1] // ответ — lcs[1][n]
Также можно заметить, что от
-ой строчки нужны только элементы с -го столбца. В этом случае можно использовать лишь элементов таблицы:LCS3(x, y) if length(x) < length(y) // в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y swap(x, y) m = length(x) n = length(y) for j = 0 to n lcs[j] = 0 d = 0 // d — дополнительная переменная, в ней хранится lcs[i - 1][j - 1] // в lcs[j], lcs[j + 1], …, lcs[n] хранятся lcs[i - 1][j], lcs[i - 1][j + 1], …, lcs[i - 1][n] // в lcs[0], lcs[1], …, lcs[j - 1] хранятся lcs[i][0], lcs[i][1], …, lcs[i][j - 1] for i = 1 to m for j = 1 to n tmp = lcs[j] if x[i] == y[i] lcs[j] = d + 1 else if lcs[j] >= lcs[j - 1] lcs[j] = lcs[j] // в lcs[j] и так хранится lcs[i - 1][j] else lcs[j] = lcs[j - 1] d = tmp // ответ — lcs[n]
Список литературы
- Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4