Открыть сервис

Алгоритм поиска наибольшей общей подпоследовательности

Алгоритм поиска наибольшей общей подпоследовательности (англ. Longest Common Subsequence, LCS) — это вычислительная процедура, предназначенная для нахождения самой длинной последовательности символов или элементов, которая встречается в одинаковом относительном порядке в двух или более заданных последовательностях (строках, массивах, списках). В отличие от подстроки, элементы общей подпоследовательности не обязаны располагаться в исходных данных подряд, что делает алгоритм универсальным инструментом для сравнения структурированной информации.

Задача поиска LCS относится к классу классических задач динамического программирования и имеет строгое математическое обоснование. Входными данными обычно служат две последовательности \(X = (x_1, x_2, ..., x_m)\) и \(Y = (y_1, y_2, ..., y_n)\), а результатом — последовательность \(Z\), являющаяся подпоследовательностью обеих и имеющая максимально возможную длину. При этом если существует несколько равных по длине решений, алгоритм, как правило, возвращает одно из них (например, первое найденное при обратном проходе).

История и контекст

Проблема поиска общей подпоследовательности впервые была формализована в середине XX века в связи с развитием молекулярной биологии и сравнительной геномики. Учёным потребовалось количественно оценивать степень родства ДНК-последовательностей, где мутации, вставки и делеции нуклеотидов приводят к тому, что идентичные участки генов не всегда расположены непрерывно. Одним из первых, кто предложил эффективный алгоритм решения, стал советский математик Владимир Левенштейн, чья работа по редакционному расстоянию (1965 год) тесно связана с понятием LCS. Позднее, в 1970-х годах, алгоритм был популяризирован в западной литературе благодаря трудам Роберта Вагнера и Майкла Фишера, которые описали его в контексте сравнения строк.

Математическая постановка задачи

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

Формально, для последовательностей \(X\) и \(Y\) ищется последовательность \(Z\), такая что:

  1. \(Z\) — подпоследовательность \(X\);
  2. \(Z\) — подпоследовательность \(Y\);
  3. Длина \(|Z|\) максимальна среди всех последовательностей, удовлетворяющих условиям 1 и 2.

Решение основано на принципе оптимальности Беллмана: оптимальное решение для префиксов строк строится из оптимальных решений для их меньших префиксов. Если последние символы строк совпадают, то они входят в LCS, а задача сводится к поиску LCS для строк без последних символов. Если символы различаются, то LCS равна максимуму из LCS для пары «строка X без последнего символа и строка Y» и пары «строка X и строка Y без последнего символа».

Алгоритм динамического программирования

Классическая реализация использует двумерную таблицу (матрицу) размером \((m+1) \times (n+1)\), где \(m\) и \(n\) — длины исходных последовательностей. Ячейка \(L[i][j]\) хранит длину LCS для префиксов \(X[1..i]\) и \(Y[1..j]\). Заполнение происходит итеративно по строкам или столбцам:

  1. Базовый случай: если \(i = 0\) или \(j = 0\), то \(L[i][j] = 0\), так как пустая строка имеет нулевую общую подпоследовательность с любой другой.
  2. Рекуррентное соотношение:
  • Если \(X[i] = Y[j]\), то \(L[i][j] = L[i-1][j-1] + 1\).
  • Если \(X[i] \neq Y[j]\), то \(L[i][j] = \max(L[i-1][j], L[i][j-1])\).

После заполнения матрицы длина искомой LCS находится в ячейке \(L[m][n]\). Для восстановления самой последовательности выполняется обратный проход от этой ячейки к началу матрицы: если символы равны, он добавляется к результату, и переход выполняется по диагонали; если не равны — движение происходит в сторону ячейки с большим значением (при равенстве выбирается, например, верхняя или левая ячейка в зависимости от реализации).

Пример работы

Рассмотрим строки «ABCBDAB» и «BDCABA». Матрица длин заполняется последовательно. Итоговая длина LCS для этих строк равна 4, а одной из возможных наибольших общих подпоследовательностей является «BCBA» (также подходят «BDAB» и «BCAB»). Обратный проход позволяет восстановить одну из них, но не все сразу.

Вычислительная сложность

Временная сложность алгоритма динамического программирования составляет \(O(m \cdot n)\), где \(m\) и \(n\) — длины входных последовательностей. Пространственная сложность также равна \(O(m \cdot n)\) при хранении полной матрицы, что необходимо для восстановления последовательности. Существует оптимизация, позволяющая сократить использование памяти до \(O(\min(m, n))\) для вычисления только длины LCS, однако она не позволяет восстановить саму подпоследовательность без дополнительных ухищрений (например, алгоритм Хиршберга, который делит задачу пополам и рекурсивно обрабатывает части).

Для частных случаев разработаны более быстрые алгоритмы. Например, если одна из строк значительно короче другой, применяется алгоритм на основе бинарного поиска по позициям символов, снижающий сложность до \(O((n + r) \log n)\), где \(r\) — число совпадающих пар. Однако в худшем случае (например, при сравнении двух случайных длинных строк) алгоритм динамического программирования остаётся стандартом де-факто.

Применение

Алгоритм LCS нашёл широкое применение в различных областях информатики и смежных дисциплинах.

Сравнение текстовых файлов

Утилиты вроде diff в Unix-подобных системах используют вариации алгоритма LCS для поиска минимального набора изменений (вставок и удалений) между двумя версиями файла. Строки, входящие в общую подпоследовательность, считаются неизменёнными, а остальные помечаются как добавленные или удалённые.

Биоинформатика

При сравнении нуклеотидных или аминокислотных последовательностей LCS позволяет выявлять консервативные участки генов, сохранившиеся в ходе эволюции. Хотя на практике чаще применяются более сложные алгоритмы выравнивания (например, Нидлмана — Вунша), учитывающие штрафы за гэпы, базовый принцип поиска общих фрагментов восходит именно к LCS.

Контроль версий и слияние кода

Системы управления версиями (Git, SVN) при выполнении операции слияния веток используют алгоритмы поиска общих участков для автоматического разрешения конфликтов. Если изменения в двух ветках не пересекаются, LCS позволяет объединить их без участия человека.

Обработка естественного языка

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

Проверка орфографии и автодополнение

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

Связь с другими метриками

LCS тесно связана с редакционным расстоянием (расстоянием Левенштейна), которое измеряет минимальное количество операций вставки, удаления и замены символов для преобразования одной строки в другую. При ограничении операций только вставкой и удалением (без замены) редакционное расстояние выражается формулой \(m + n - 2 \cdot |LCS|\), где \(m\) и \(n\) — длины строк. Это соотношение используется в некоторых алгоритмах для оптимизации вычислений.

Кроме того, LCS является частным случаем задачи о наибольшей общей подпоследовательности для более чем двух строк, которая относится к классу NP-трудных задач при количестве строк больше двух. Для двух строк задача решается за полиномиальное время, что и обеспечивает её практическую ценность.

Ограничения и особенности

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

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

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →