Задача о наибольшей общей подпоследовательности — различия между версиями
Андрей (обсуждение | вклад) |
Андрей (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
− | + | == == | |
− | == | ||
{{Определение | {{Определение | ||
|definition= | |definition= | ||
Строка 11: | Строка 10: | ||
Последовательность <tex> Z </tex> является '''общей подпоследовательностью''' (англ. ''common subsequence'') последовательностей <tex> X </tex> и <tex> Y </tex>, если <tex> Z </tex> является подпоследовательностью как <tex> X </tex>, так и <tex> Y </tex>. | Последовательность <tex> Z </tex> является '''общей подпоследовательностью''' (англ. ''common subsequence'') последовательностей <tex> X </tex> и <tex> Y </tex>, если <tex> Z </tex> является подпоследовательностью как <tex> X </tex>, так и <tex> Y </tex>. | ||
}} | }} | ||
− | |||
{{Задача | {{Задача | ||
|definition= | |definition= | ||
Найти '''наибольшую общую подпоследовательность''' (англ. ''longest common subsequence, <tex>LCS</tex>''), которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух). | Найти '''наибольшую общую подпоследовательность''' (англ. ''longest common subsequence, <tex>LCS</tex>''), которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух). | ||
}} | }} | ||
− | |||
== Наивное решение == | == Наивное решение == | ||
Переберем все различные подпоследовательности обеих строк и сравним их. Тогда искомая <tex>LCS</tex> гарантированно найдётся, однако время работы алгоритма будет экспоненциально зависеть от длины исходных последовательностей. | Переберем все различные подпоследовательности обеих строк и сравним их. Тогда искомая <tex>LCS</tex> гарантированно найдётся, однако время работы алгоритма будет экспоненциально зависеть от длины исходных последовательностей. | ||
Строка 22: | Строка 19: | ||
== Динамическое программирование == | == Динамическое программирование == | ||
− | Данная задача решается с использованием принципа оптимальности на префиксе. | + | Данная задача решается с использованием [[Принцип_оптимальности_на_префиксе | принципа оптимальности на префиксе]]. |
=== Доказательство оптимальности === | === Доказательство оптимальности === | ||
Строка 29: | Строка 26: | ||
Пусть имеются последовательности <tex> X = \left \langle x_1, x_2, \dots, x_m \right \rangle </tex> и <tex> Y = \left \langle y_1, y_2, \dots, y_n \right \rangle </tex>, а <tex> Z = \left \langle z_1, z_2, \dots, z_k \right \rangle </tex> — их <tex>LCS</tex>. | Пусть имеются последовательности <tex> X = \left \langle x_1, x_2, \dots, x_m \right \rangle </tex> и <tex> Y = \left \langle y_1, y_2, \dots, y_n \right \rangle </tex>, а <tex> Z = \left \langle z_1, z_2, \dots, z_k \right \rangle </tex> — их <tex>LCS</tex>. | ||
− | # Если <tex> x_m = y_n </tex>, то <tex> z_k = x_m = y_n </tex> и <tex> Z_{k - 1} </tex> — <tex>LCS | + | # Если <tex> x_m = y_n </tex>, то <tex> z_k = x_m = y_n </tex> и <tex> Z_{k - 1} </tex> — <tex>LCS(X_{m - 1},Y_{n - 1})</tex> |
− | # Если <tex> x_m \neq y_n </tex>, то из <tex> z_k \neq x_m </tex> следует, что <tex> Z </tex> — <tex>LCS | + | # Если <tex> x_m \neq y_n </tex>, то из <tex> z_k \neq x_m </tex> следует, что <tex> Z </tex> — <tex>LCS(X_{m - 1},Y)</tex> |
− | # Если <tex> x_m \neq y_n </tex>, то из <tex> z_k \neq y_n </tex> следует, что <tex> Z </tex> — <tex>LCS | + | # Если <tex> x_m \neq y_n </tex>, то из <tex> z_k \neq y_n </tex> следует, что <tex> Z </tex> — <tex>LCS(X,Y_{n - 1})</tex> |
|proof= | |proof= | ||
− | # Если бы выполнялось <tex> z_k \neq x_m </tex>, то к <tex> Z </tex> можно было бы добавить элемент <tex> x_m = y_n </tex>, и тогда получилась бы общая подпоследовательность длины <tex> k + 1 </tex>, что противоречит предположению, что <tex> Z </tex> — <tex>LCS</tex>. Значит, выполняется <tex> z_k = x_m = y_n </tex>. Значит, <tex> Z_{k - 1} </tex> — общая подпоследовательность <tex> X_{m - 1} </tex> и <tex> Y_{n - 1} </tex>. Докажем от противного, что <tex> Z_{k - 1} </tex> — <tex>LCS</tex>: тогда есть общая подпоследовательность <tex> W </tex>, длина которой больше <tex> k - 1 </tex>. Добавив к <tex> W </tex> элемент <tex> x_m = y_n </tex>, получим <tex>LCS | + | # Если бы выполнялось <tex> z_k \neq x_m </tex>, то к <tex> Z </tex> можно было бы добавить элемент <tex> x_m = y_n </tex>, и тогда получилась бы общая подпоследовательность длины <tex> k + 1 </tex>, что противоречит предположению, что <tex> Z </tex> — <tex>LCS</tex>. Значит, выполняется <tex> z_k = x_m = y_n </tex>. Значит, <tex> Z_{k - 1} </tex> — общая подпоследовательность <tex> X_{m - 1} </tex> и <tex> Y_{n - 1} </tex>. Докажем от противного, что <tex> Z_{k - 1} </tex> — <tex>LCS</tex>: тогда есть общая подпоследовательность <tex> W </tex>, длина которой больше <tex> k - 1 </tex>. Добавив к <tex> W </tex> элемент <tex> x_m = y_n </tex>, получим <tex>LCS(X,Y)</tex>, длина которой больше <tex> k </tex> (т.е. больше длины <tex> Z </tex>), что приводит к противоречию. |
− | # Если <tex> z_k \neq x_m </tex>, то <tex> Z </tex> — общая подпоследовательность <tex> X_{m - 1} </tex> и <tex> Y </tex>. Пусть существует их общая подпоследовательность <tex> W </tex>, длина которой превышает <tex> k </tex>. Она также является общей подпоследовательностью <tex> X </tex> и <tex> Y </tex>, что противоречит предположению о том, что <tex> Z </tex> (длины <tex> k </tex>) — <tex>LCS | + | # Если <tex> z_k \neq x_m </tex>, то <tex> Z </tex> — общая подпоследовательность <tex> X_{m - 1} </tex> и <tex> Y </tex>. Пусть существует их общая подпоследовательность <tex> W </tex>, длина которой превышает <tex> k </tex>. Она также является общей подпоследовательностью <tex> X </tex> и <tex> Y </tex>, что противоречит предположению о том, что <tex> Z </tex> (длины <tex> k </tex>) — <tex>LCS(X,Y)</tex>. |
# Аналогично второму случаю. | # Аналогично второму случаю. | ||
}} | }} | ||
Строка 54: | Строка 51: | ||
=== Построение подпоследовательности === | === Построение подпоследовательности === | ||
− | Для каждой пары элементов помимо длины <tex>LCS</tex> соответствующих префиксов хранятся и номера последних элементов, участвующих в этой LCS.Таким образом, посчитав ответ, можно восстановить всю наибольшую общую подпоследовательность. | + | Для каждой пары элементов помимо длины <tex>LCS</tex> соответствующих префиксов хранятся и номера последних элементов, участвующих в этой <tex>LCS</tex>.Таким образом, посчитав ответ, можно восстановить всю наибольшую общую подпоследовательность. |
=== Псевдокод === | === Псевдокод === | ||
Строка 60: | Строка 57: | ||
''<font color="green">// подсчёт таблиц</font>'' | ''<font color="green">// подсчёт таблиц</font>'' | ||
− | + | '''int''' LCS(x, y : '''int'''): | |
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 | ||
Строка 80: | Строка 77: | ||
prev[i][j] = pair(i, j - 1) | prev[i][j] = pair(i, j - 1) | ||
− | ''<font color="green">// вывод LCS, вызывается как printLCS( | + | ''<font color="green">// вывод LCS, вызывается как printLCS(m, n)</font>'' |
− | printLCS( | + | '''int''' printLCS(i, j: '''int'''): |
− | '''if''' i == 0 or j == 0 ''<font color="green">// пришли к началу LCS</font>'' | + | '''if''' i == 0 '''or''' j == 0 ''<font color="green">// пришли к началу LCS</font>'' |
'''return''' | '''return''' | ||
'''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>'' | '''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( | + | printLCS(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( | + | printLCS(i - 1, j) |
'''else''' | '''else''' | ||
− | printLCS( | + | printLCS(i, j - 1) |
== Оптимизация для вычисления только длины <tex>LCS</tex> == | == Оптимизация для вычисления только длины <tex>LCS</tex> == | ||
Заметим, что для вычисления <tex> lcs[i][j] </tex> нужны только <tex> i </tex>-ая и <tex> (i-1) </tex>-ая строчки матрицы <tex> lcs </tex>. Тогда можно использовать лишь <tex> 2 \cdot min(m, n) </tex> элементов таблицы: | Заметим, что для вычисления <tex> lcs[i][j] </tex> нужны только <tex> i </tex>-ая и <tex> (i-1) </tex>-ая строчки матрицы <tex> lcs </tex>. Тогда можно использовать лишь <tex> 2 \cdot min(m, n) </tex> элементов таблицы: | ||
− | LCS2(x, y) | + | '''int''' LCS2(x, y: '''int'''): |
'''if''' length(x) < length(y) ''<font color="green">// в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y</font>'' | '''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] ''<font color="green">// элемент, который был в a[1][j], теперь в предыдущей строчке</font>'' | lcs[0][j] = lcs[1][j] ''<font color="green">// элемент, который был в a[1][j], теперь в предыдущей строчке</font>'' | ||
'''if''' x[i] == y[j] | '''if''' x[i] == y[j] | ||
Строка 119: | Строка 116: | ||
Также можно заметить, что от <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) | + | '''int''' LCS3(x, y: '''int'''): |
'''if''' length(x) < length(y) ''<font color="green">// в таблице будет length(y) столбцов, и если length(x) меньше, выгоднее поменять местами x и y</font>'' | '''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 ''<font color="green">// 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[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>'' | ''// в 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] | ||
Строка 142: | Строка 139: | ||
''<font color="green">// ответ — lcs[n]</font>'' | ''<font color="green">// ответ — lcs[n]</font>'' | ||
− | == | + | == Длина кратчайшей общей суперпоследовательности == |
− | Для двух подпоследовательностей <tex> | + | Для двух подпоследовательностей <tex>X_{m - 1}</tex> и <tex>Y_{n - 1}</tex> длина кратчайшая общей суперпоследовательности равна |
<tex> | <tex> | ||
|SCS(X,Y)| = n + m - |LCS(X,Y)| | |SCS(X,Y)| = n + m - |LCS(X,Y)| |
Версия 11:49, 10 января 2015
Содержание
Определение: |
Последовательность | является подпоследовательностью (англ. subsequence) последовательности , если существует строго возрастающая последовательность индексов таких, что для всех выполняется соотношение .
Другими словами, подпоследовательность данной последовательности — это последовательность, из которой удалили ноль или больше элементов. Например,
является подпоследовательностью последовательности , а соответствующая последовательность индексов имеет вид .Определение: |
Последовательность | является общей подпоследовательностью (англ. common subsequence) последовательностей и , если является подпоследовательностью как , так и .
Задача: |
Найти наибольшую общую подпоследовательность (англ. longest common subsequence, | ), которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух).
Наивное решение
Переберем все различные подпоследовательности обеих строк и сравним их. Тогда искомая
гарантированно найдётся, однако время работы алгоритма будет экспоненциально зависеть от длины исходных последовательностей.Динамическое программирование
Данная задача решается с использованием принципа оптимальности на префиксе.
Доказательство оптимальности
Теорема: |
Пусть имеются последовательности и , а — их .
|
Доказательство: |
|
Решение
Обозначим как
префиксов данных последовательностей, заканчивающихся в элементах с номерами и соответственно. Получается следующее рекуррентное соотношение:
Очевидно, что сложность алгоритма составит
, где и — длины последовательностей.Построение подпоследовательности
Для каждой пары элементов помимо длины
соответствующих префиксов хранятся и номера последних элементов, участвующих в этой .Таким образом, посчитав ответ, можно восстановить всю наибольшую общую подпоследовательность.Псевдокод
, — данные последовательности; — для префикса длины последовательности и префикса длины последовательности ; — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении .
// подсчёт таблиц int LCS(x, y : int): 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) // вывод LCS, вызывается как printLCS(m, n) int printLCS(i, j: int): if i == 0 or j == 0 // пришли к началу LCS return if prev[i][j] == pair(i - 1, j - 1) // если пришли в lcs[i][j] из lcs[i - 1][j - 1], то x[i] == y[j], надо вывести этот элемент printLCS(i - 1, j - 1) print x[i] else if prev[i][j] == pair(i - 1, j) printLCS(i - 1, j) else printLCS(i, j - 1)
Оптимизация для вычисления только длины
Заметим, что для вычисления
нужны только -ая и -ая строчки матрицы . Тогда можно использовать лишь элементов таблицы:int LCS2(x, y: int): 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]
Также можно заметить, что от
-ой строчки нужны только элементы с -го столбца. В этом случае можно использовать лишь элементов таблицы:int LCS3(x, y: int): 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]
Длина кратчайшей общей суперпоследовательности
Для двух подпоследовательностей [1]
и длина кратчайшая общей суперпоследовательности равнаСм. также
- Задача о наибольшей возрастающей подпоследовательности
- Наибольшая общая возрастающая подпоследовательность
- Задача о наибольшей общей палиндромной подпоследовательности
Примечания
Список литературы
- Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4