Задача о наибольшей общей подпоследовательности — различия между версиями
| Строка 24: | Строка 24: | ||
a[i][j] = | a[i][j] = | ||
\begin{cases} | \begin{cases} | ||
| + | 0, & i = 0\text{ or }j = 0 \\ | ||
a[i - 1][j - 1] + 1, & x[i] = y[j] \\ | a[i - 1][j - 1] + 1, & x[i] = y[j] \\ | ||
max(a[i][j - 1], a[i - 1][j]), & x[i] \neq y[j] | max(a[i][j - 1], a[i - 1][j]), & x[i] \neq y[j] | ||
| Строка 29: | Строка 30: | ||
</tex> | </tex> | ||
| − | Очевидно, что сложность алгоритма составит <tex> O(n | + | Очевидно, что сложность алгоритма составит <tex> O(mn) </tex>, где <tex> m </tex> и <tex> n </tex> — длины последовательностей. |
=== Доказательство оптимальности === | === Доказательство оптимальности === | ||
| − | + | База: при <tex> i = 0 </tex> или <tex> j = 0 </tex> длина одной из последовательностей равна нулю, поэтому и их НОП тоже нулевой длины. | |
| + | |||
| + | Переходы: предположим, что некоторое значение <tex> a[i][j] </tex> посчитано неверно. Однако, в случае различия соответствующих символов, они не могут одновременно участвовать в НОП, а значит ответ действительно равен формуле для случая с различными символами. В случае же равенства, ответ не может быть больше, чем <tex> a[i - 1][j - 1] + 1 </tex>, так как тогда неверно посчитано значение <tex> a[i - 1][j - 1] + 1 </tex>. | ||
=== Построение подпоследовательности === | === Построение подпоследовательности === | ||
Для каждой пары элементов будем хранить не только длину НОП соответствующих префиксов, но и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, мы сможем восстановить всю наибольшую общую подпоследовательность. | Для каждой пары элементов будем хранить не только длину НОП соответствующих префиксов, но и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, мы сможем восстановить всю наибольшую общую подпоследовательность. | ||
| − | === | + | === Псевдокод === |
| − | + | X, Y — данные последовательности; a[i][j] — НОП для префикса длины i последовательности X и префикса длины j последовательности Y; b[i][j] — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении a[i][j]. | |
| − | + | ||
| − | + | // подсчёт таблиц | |
| − | + | LCS(X, Y) | |
| − | + | m = length(X) | |
| − | + | n = length(Y) | |
| − | + | for i = 1 to m | |
| − | + | a[i][0] = 0 | |
| − | + | for j = 0 to n | |
| − | + | a[0][j] = 0 | |
| − | + | for i = 1 to m | |
| − | + | for j = 1 to n | |
| − | + | if x[i] = y[i] | |
| − | + | a[i][j] = a[i - 1][j - 1] + 1 | |
| − | + | b[i][j] = pair(i - 1, j - 1) | |
| − | + | else | |
| − | + | if a[i - 1][j] >= a[i][j - 1] | |
| − | + | a[i][j] = a[i - 1][j] | |
| − | + | b[i][j] = pair(i - 1, j) | |
| − | + | else | |
| − | + | a[i][j] = a[i][j - 1] | |
| − | + | b[i][j] = pair(i, j - 1) | |
| + | |||
| + | // вывод НОП | ||
| + | PrintLCS(b, X, i, j) | ||
| + | if i = 0 or j = 0 // пришли к началу НОП | ||
| + | return | ||
| + | if b[i][j] = pair(i - 1, j - 1) // если пришли в a[i][j] из a[i - 1][j - 1], то X[i] = Y[j], надо вывести этот элемент | ||
| + | PrintLCS(b, X, i - 1, j - 1) | ||
| + | print X[i] | ||
| + | else | ||
| + | if b[i][j] = pair(i - 1, j) | ||
| + | PrintLCS(b, X, i - 1, j) | ||
| + | else | ||
| + | PrintLCS(b, X, i, j - 1) | ||
Версия 01:54, 23 ноября 2011
Задача нахождения наибольшей общей подпоследовательности (longest common subsequence, LCS) — это задача поиска последовательности, которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух).
Содержание
Определения
| Определение: |
| Последовательность является подпоследовательностью (subsequence) последовательности , если существует строго возрастающая последовательность индексов таких, что для всех выполняется соотношение . |
Другими словами, подпоследовательность данной последовательности — это последовательность, из которой удалили ноль или больше элементов. Например, является подпоследовательностью последовательности , а соответствующая последовательность индексов имеет вид .
| Определение: |
| Последовательность является общей подпоследовательностью (common subsequence) последовательностей и , если является подпоследовательностью как , так и . |
Постановка задачи
Даны две последовательности: и . Требуется найти общую подпоследовательность и максимальной длины. Заметим, что таких подпоследовательностей может быть несколько.
Наивная идея решения
Переберем все различные подпоследовательности обеих строк и сравним их. Мы гарантированно найдем искомую НОП, однако время работы алгоритма будет экспоненциально зависеть от длины исходных последовательностей.
Динамическое программирование
Решение
Обозначим как НОП префиксов данных последовательностей, заканчивающихся в элементах с номерами и соответственно. Получаем следующее рекуррентное соотношение:
Очевидно, что сложность алгоритма составит , где и — длины последовательностей.
Доказательство оптимальности
База: при или длина одной из последовательностей равна нулю, поэтому и их НОП тоже нулевой длины.
Переходы: предположим, что некоторое значение посчитано неверно. Однако, в случае различия соответствующих символов, они не могут одновременно участвовать в НОП, а значит ответ действительно равен формуле для случая с различными символами. В случае же равенства, ответ не может быть больше, чем , так как тогда неверно посчитано значение .
Построение подпоследовательности
Для каждой пары элементов будем хранить не только длину НОП соответствующих префиксов, но и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, мы сможем восстановить всю наибольшую общую подпоследовательность.
Псевдокод
X, Y — данные последовательности; a[i][j] — НОП для префикса длины i последовательности X и префикса длины j последовательности Y; b[i][j] — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении a[i][j].
// подсчёт таблиц
LCS(X, Y)
m = length(X)
n = length(Y)
for i = 1 to m
a[i][0] = 0
for j = 0 to n
a[0][j] = 0
for i = 1 to m
for j = 1 to n
if x[i] = y[i]
a[i][j] = a[i - 1][j - 1] + 1
b[i][j] = pair(i - 1, j - 1)
else
if a[i - 1][j] >= a[i][j - 1]
a[i][j] = a[i - 1][j]
b[i][j] = pair(i - 1, j)
else
a[i][j] = a[i][j - 1]
b[i][j] = pair(i, j - 1)
// вывод НОП
PrintLCS(b, X, i, j)
if i = 0 or j = 0 // пришли к началу НОП
return
if b[i][j] = pair(i - 1, j - 1) // если пришли в a[i][j] из a[i - 1][j - 1], то X[i] = Y[j], надо вывести этот элемент
PrintLCS(b, X, i - 1, j - 1)
print X[i]
else
if b[i][j] = pair(i - 1, j)
PrintLCS(b, X, i - 1, j)
else
PrintLCS(b, X, i, j - 1)