Задача о наибольшей общей подпоследовательности
Задача нахождения наибольшей общей подпоследовательности (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)