Підзарядок, а не підрядковий рядок
Підзарядок зберігає відносну послідовність символів, але не вимагає їхньої безпосередньої близькості — "ACE" є підзалишком для "ABCDE", пропускаючи B і D. Найдовший спільний підзарядок (LCS) двох рядків – це найдовша послідовність, яка є підзалишком як для одного, так і для іншого рядка. Це обманньо просте питання — «який найбільший шматок вони поділяють, дозволяючи проміжки?» — який виявляється точно правильною формалізацією «наскільки подібні ці два файли, ці дві ДНК-ланцюги, ці дві версії документа».
Рекурсія
Нехай dp[i][j] буде довжиною LCS (найдовша спільна підпослідовність) перших i символів рядка A та перших j символів рядка B. На відміну від відстані редагування, немає вартості для невідповідності — непарну пару просто не можна включити, тому рекурсія приймає найкраще з того, щоб пропустити один символ з будь-якої з двох рядків:
dp[0][j] = dp[i][0] = 0 dp[i][j] = dp[i-1][j-1] + 1 якщо A[i] == B[j] dp[i][j] = max(dp[i-1][j], dp[i][j-1]) якщо A[i] != B[j] Заповнення цієї (n+1)×(m+1) сітки займає O(nm) часу та простору, так само як і відстань редагування — ці дві проблеми є близькими родичами, і довжина LCS фактично дорівнює n + m мінус двачі відстані редагування, якщо дозволено лише вставки та видалення (немає підстанов).
dp[0][j] = dp[i][0] = 0 dp[i][j] = dp[i-1][j-1] + 1 if A[i] == B[j] dp[i][j] = max(dp[i-1][j], dp[i][j-1]) if A[i] != B[j]
Відстеження діагональних збігів
Почніть із найнижчого праворуч комірки та використовуйте той самий зворотний шлях, що й для розрахунку відстані редагування: кожного разу, коли A[i] дорівнює B[j], комірка повинна надходитися з діагонального кроку, і цей символ належить до LCS; інакше зробіть крок до будь-якого з сусідніх по вертикалі або по діагоналі та спробуйте ще раз. Збираючи символи, знайдені на діагональних кроках, в зворотному порядку, вони утворюють одну валідну найдовшу спільну підпослідовність — може бути декілька однакових за довжиною, і яка саме послідовність буде відновлена цим шляхом залежить від розірвання зв’язків.
Алгоритм, що стоїть за кожну відмінність, яку ви читали
Лінійне порівняння відмінностей (diff) розглядає кожен файл як послідовність рядків і обчислює LCS між цими двома послідовностями. Рядки, які входять до LCS, відображаються як незмінні; все інше позначається як додане (присутнє у новому файлі, але не в LCS) або видалене (присутнє у старому файлі, але не в LCS). Git та більшість сучасних інструментів diff не виконують прямо алгоритм DP з часом O(nm) — вони використовують алгоритм Майєра, який знаходить одну й ту ж найкоротшу редакторську скрипт шляхом пошуку по "діагоналях" в імплицитному графі редагування, що зазвичай працює швидше ніж O(nm), коли два файли схожі, що є типовим випадком у системі контролю версій.
ДНК, білки та далі текст
LCS розглядає кожен збіг як безкоштовний і кожен невідповідність як примусове обстрибнування, що є розумною моделлю для простого тексту, але занадто грубою для біології, де заміна та вставлення рідко несуть однакові еволюційні витрати. Bioінформатика використовує ту ж структуру діагоналі/вгору/зліва, але з матрицею оцінювання — алгоритм Needleman-Wunsch для повного вирівнювання від кінця до кінця, а Smith-Waterman для пошуку найкращої локальної області відповідності між двома довгими послідовностями. Обидва структурно є LCS із доданими вагами, тому розуміння незвареної версії робить біологічні алгоритми набагато легшими для розуміння.
Frequently asked questions
Чи є підпослідовність однаковою з підрядком?
Ні. Підрядок має бути безперервним — 'ABC' є підрядком 'XABCY', але не 'AXBXC'. Підпослідовність лише повинна зберігати відносну послідовність, тому 'ABC' є допустимою підпослідовністю 'AXBXC', хоча символи не прилеглі. LCS знаходить найдовшу послідовність, яка є підпослідовністю обох вхідних даних, що пояснює, чому відмінність може позначати рядки як незмінними, навіть якщо між ними вставлено інші рядки.
Як LCS пов'язаний із інструментом diff, який я використовую щодня?
Лінійне порівняння двох файлів по суті є застосуванням LCS до послідовності рядків: найдовша спільна підпослідовність рядків є набором рядків, які вважаються незмінними, а все, що не входить у цю підпослідовність, показується як додане або видалене. Реальні інструменти diff, такі як Git, зазвичай використовують алгоритм Майєра — уточнення, яке знаходить той самий результат шляхом пошуку найкоротшого сценарію редагування замість повної таблиці DP O(nm), що швидше, коли два файли в основному схожі.
Чому біоінформатика піклується про LCS?
Порівняння двох послідовностей ДНК або білків для пошуку консервативних областей є фундаментальною проблемою вирівнювання послідовностей, а LCS — її найпростіша форма — збіги безкоштовні, невідповідності просто пропускаються замість підстановки. Реальні інструменти біоінформатики використовують зважені варіанти (Needleman-Wunsch, Smith-Waterman), які також штрафують невідповідності та проміжки за допомогою матриці оцінювання, але одна й та сама рекуренція діагональ-проти-поперек-вниз лежить в основі всіх їх.
Спробуйте наживо
Усе, що вище, працює прямо у вашому браузері — відкрийте Longest Common Subsequence і змінюйте параметри під час роботи. Нічого не встановлюється, нічого не завантажується на сервер, уся модель живе в одній вкладці.
▶ Відкрити симуляцію Longest Common Subsequence