Il­giau­sios bend­ro­sios po­sek­ven­ci­jos (LCS) yra tipinė IT problema. LCS problemos sprendimo būdai dažnai pasirodo prog­ra­muo­to­jų ir algoritmų kursų po­kal­biuo­se.

Koks yra il­giau­sias bendras po­sek­ven­ci­ja?

Tikslas yra rasti ilgiausią bendrą po­sek­ven­ci­ją dviejose sek­ven­ci­jo­se. Tai yra vieta, kur po­sek­ven­ci­ja yra išvedama iš sek­ven­ci­jos. Po­sek­ven­ci­ja turi tą pačią elementų tvarką, kai kuriais atvejais, kai elementai yra pa­ša­li­na­mi. Pa­žvel­ki­me į keletą pavyzdžių, kad geriau su­pras­tu­me šį principą:

Sek­ven­ci­ja X Sek­ven­ci­ja Y LCS(X, Y)
FATHER MATTER ATER
MOTHER HERBAL HER
DAVID DANIEL DAI
Note

Yra svarbus skirtumas tarp il­giau­sios bend­ro­sios po­sek­ven­ci­jos ir il­giau­sios bend­ro­sios po­sek­ven­ci­jos, nes po­sek­ven­ci­ja negali turėti jokių tarpų. Pavyzdyje „DAVID“ ir „DANIEL“ ilgiausia bendroji po­sek­ven­ci­ja būtų „DA“, nes „I“ yra atskirta „V“ ir „N“.

Ar yra kokių nors praktinių LCS problemos pavyzdžių?

Il­giau­sios bend­ro­sios sekos problema yra svarbus klausimas visose srityse, kuriose nau­do­ja­mos viena iš kitos kilusios sekos. Yra tam tikri būdai rasti LCS, kad būtų galima pamatyti, ar yra panašumų ar skirtumų, ir ati­tin­ka­mai aptikti bet kokį pla­gi­ja­vi­mą. Gerai žinoma „diff“ programa, kuri tikrina šaltinio tekstinių failų pa­kei­ti­mus, yra pagrįsta LCS problema.

Bioin­for­ma­ti­ko­je il­giau­sios bend­ro­sios po­sek­ven­ci­jos problema dažnai naudojama ana­li­zuo­jant DNR sekas. DNR bazės tam tikrose po­zi­ci­jo­se laikui bėgant keičiasi dėl mutacijų. Ilgos bend­ro­sios po­sek­ven­ci­jos buvimas dviejose sekose rodo genetinį ryšį. Tai leidžia mums atsekti rūšių evo­liu­ci­ją pagal genetinę raidą.

Ling­vis­tai taip pat gali naudoti il­giau­sios bend­ro­sios sekos problemą, kad ištirtų kalbos raidą per šimt­me­čius. Jei turite du žodžius iš skirtingų kalbų, kurie turi tą pačią reikšmę ir ilgą bendrąją seką, tai rodo, kad jie turi tą pačią šaknį ir eti­mo­lo­gi­ją. Tai būtų laikoma panašia istorine raida.

Kokie yra il­giau­sios bend­ro­sios po­sek­ven­ci­jos problemos spren­di­mai?

Pir­miau­sia, LCS problemą galima išspręsti naiviu metodu. Tai paprastas metodas, ne­nau­do­jan­tis jokių op­ti­mi­za­ci­jų ar specialių metodų. Tam reikia tiesiog ap­skai­čiuo­ti visas dviejų sekų posekes ir rasti ilgiausią bendrą posekę. Deja, šis metodas nėra labai efektyvus, todėl tinka tik trumpoms sekoms.

Žemiau rasite tris veiks­min­gus LCS problemos spren­di­mus:

  1. Re­kur­sy­vu­sis metodas
  2. Op­ti­mi­za­vi­mas naudojant atminties funkciją
  3. Dinaminis prog­ra­ma­vi­mas

Visiems metodams būdinga tai, kad, at­si­žvel­giant į šias dvi sekas, jie skiriasi trimis atvejais:

  • Paskutinė raidė yra ta pati.
  • Paskutinė raidė nėra tokia pati.
  • Vienos sekos ilgis yra nulis.

Kiek­vie­nas iš šių metodų skiriasi savo laiko su­dė­tin­gu­mu (asimp­to­ti­niu vykdymo laiku) ir erdvės su­dė­tin­gu­mu (atminties naudojimu):

Prieiga Trukmė Atmintis
Naivus požiūris O(n * n²) O(n)
Re­kur­sy­vus metodas O(n²) O(1)
Op­ti­mi­za­vi­mas naudojant atminties funkciją O(n *m) O(n* m)
Dinaminis prog­ra­ma­vi­mas O(n *m) O(n* m)
Note

Toliau pateikti al­go­rit­mai ap­skai­čiuo­ja il­giau­sios bend­ro­sios po­sek­ven­ci­jos ilgį. Jei taikoma, yra keletas šios trukmės nustatytų po­sek­ven­ci­jų, kurias galima nustatyti naudojant kitus veiksmus.

Il­giau­sios bend­ro­sios po­sek­ven­ci­jos re­kur­si­nis nu­sta­ty­mas

Jei pa­žvelg­si­te į LCS problemą, pa­ma­ty­si­te, kad ji turi „optimalų substruk­tū­rą“. Tai reiškia, kad problema gali būti su­skai­dy­ta į smul­kes­nes problemas. Kaip sprendimą galite naudoti rekursyvų metodą. Toliau pa­tei­kia­mi keletas algoritmų pavyzdžių, kaip tai įgy­ven­din­ti trimis po­pu­lia­riau­sio­mis prog­ra­ma­vi­mo kalbomis.

Python

def lcs(X, Y, m, n):
    # Base case
    if m == 0 or n == 0:
        return 0
    # Last elements are equal
    elif X[m-1] == Y[n-1]:
        return 1 + lcs(X, Y, m-1, n-1)
    # Last elements differ
    else:
        return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n))
# Let's test
X, Y = "DANIEL", "DAVID"
lcs_len = lcs(X, Y, len(X), len(Y))
print(f"Length of LCS is: {lcs_len}")
python

Java

import java.io.*;
class LCS {
    public static int lcs(String A, String B, int m, int n)
    {
        // Base case
        if (m == 0 || n == 0)
            return 0;
        // Last elements are equal
        if (A.charAt(m - 1) == B.charAt(n - 1))
            return 1 + lcs(A, B, m - 1, n - 1);
        // Last elements differ
        else
            return Math.max(lcs(A, B, m, n - 1),
             lcs(A, B, m - 1, n));
    }
    
    // Let's test
    public static void main(String[] args)
        {
            String X = "DAVID";
            String Y = "DANIEL";
            int lcsLength = LCS.lcs(X, Y, X.length(), Y.length());
            System.out.println("Length of LCS is: " + lcsLength);
        }
}
java

C++

#include <iostream>
using namespace std;
int lcs(string X, string Y, int m, int n)
{
    // Base case
    if (m == 0 || n == 0) {
        return 0;
    }
    // Last elements are equal
    if (X[m - 1] == Y[n - 1]) {
        return 1 + lcs(X, Y, m - 1, n - 1);
    }
    // Last elements differ
    else {
        return max(lcs(X, Y, m, n - 1), lcs(X, Y, m - 1, n));
    }
}
// Let's test
int main()
{
    // Initialize variables
    string X = "DAVID";
    string Y = "DANIEL";
    // Compute and output length of LCS
    cout << "Length of LCS is " << lcs(X, Y, X.size(), Y.size());
    return 0;
}
c++

Re­kur­si­nio metodo op­ti­mi­za­vi­mas naudojant me­mi­za­ci­ją

Dar kartą pažvelgus į re­kur­sy­vų­jį metodą, matyti, kad per­si­den­gian­čios dalys yra ap­skai­čiuo­ja­mos. Šios savybės, vadinamos „per­si­den­gian­čio­mis dalinėmis prob­le­mo­mis“, yra žinomos iš Fibonačio sekų. Šiuo atveju taip pat re­kur­sy­vios sekos yra nuolat ap­skai­čiuo­ja­mos spren­di­mui rasti. Kad procesas būtų efek­ty­ves­nis, verta naudoti me­mo­ja­vi­mą. Kitaip tariant, jau ap­skai­čiuo­tas dalines problemas galima įrašyti į dvimatę matricą.

Python

def lcs(X, Y, m, n, table):
    
    # Base case
    if (m == 0 or n == 0):
        return 0
    # Already computed value at given position
    if (table[m][n] != -1):
        return table[m][n]
    # Last elements are equal
    if X[m - 1] == Y[n - 1]:
        table[m][n] = 1 + lcs(X, Y, m - 1, n - 1, table)
    # Last elements differ
    else:
        table[m][n] = max(lcs(X, Y, m, n - 1, table), lcs(X, Y, m - 1, n, table))
    return table[m][n]
# Let's test
X = "DAVID"
Y = "DANIEL"
m, n = len(X), len(Y)
# Initialize table fields to `-1`
table = [[-1 for i in range(n + 1)] for j in range(m + 1)]
// Compute and output length of LCS
print(f"Length of LCS is: {lcs(X, Y, m, n, table)}")
python

Java

import java.io.*;
class LCS {
    public static int lcs(String X, String Y, int m, int n, int[][] table) {
        // Base case
        if (m == 0 || n == 0) {
            return 0;
        }
        // Already computed value at given position
        if (table[m][n] != -1) {
            return table[m][n];
        }
        // Last elements are equal
        if(X.charAt(m - 1) == Y.charAt(n - 1)) {
            table[m][n] = 1 + lcs(X, Y, m - 1, n - 1, table);
            return table[m][n];
        }
        // Last elements differ
        else {
            table[m][n] = Math.max(lcs(X, Y, m, n - 1, table),lcs(X, Y, m - 1, n, table));
            return table[m][n];
        }
    }
    // Let's test
    public static void main(String args[]){
        // Initialize variables
        String X = "DAVID";
        String Y = "DANIEL";
        int m = X.length();
        int n = Y.length();
        int[][] table = new int[m + 1][n + 1];
        
        // Initialize table fields to `-1`
        for(int i=0; i < m + 1; i++) {
            for(int j=0; j < n + 1; j++) {
                table[i][j] = -1;
            }
        }
        // Compute and output length of LCS
        int lcsLength = lcs(X, Y, m, n, table);
        System.out.println("Length of LCS is: " + lcsLength);
    }
}
java

C++

#include <bits/stdc++.h>
using namespace std;
int lcs(char *X, char* Y, int m, int n, vector<vector<int>>& table)
{
    // Base case
    if (m == 0 || n == 0)
        return 0;
    // Already computed value at given position
    if (table[m][n] != -1) {
        return table[m][n];
    }
    // Last elements are equal
    if (X[m - 1] == Y[n - 1]) {
        table[m][n] = 1 + lcs(X, Y, m - 1, n - 1, table);
        return table[m][n];
    }
    // Last elements differ
    else { 
        table[m][n] = max(lcs(X, Y, m, n - 1, table), lcs(X, Y, m - 1, n, table));
        return table;
    }
}
// Let's test
int main()
{
    // Initialize variables
    char X[] = "DAVID";
    char Y[] = "DANIEL";
    int m = strlen(X);
    int n = strlen(Y);
    // Initialize table with `-1`
    vector<vector<int>> table(m + 1, vector<int>(n + 1, -1));
    
    // Compute and output length of LCS
    cout << "Length of LCS is: " << lcs(X, Y, m, n, table);
    return 0;
}
c++

Dinaminis prog­ra­ma­vi­mas il­giau­siam bendram po­sek­ven­ci­jai

Dinaminis prog­ra­ma­vi­mas yra ne­re­kur­sy­vus būdas spręsti op­ti­mi­za­vi­mo problemas, jas su­skai­dant į mažesnes dalines problemas ir tada spren­džiant jas „iš apačios į viršų“. Be kita ko, dinaminis prog­ra­ma­vi­mas taikomas kaip spren­di­mas kelių paieškos al­go­rit­mams. Il­giau­sios bend­ro­sios pa­kar­to­ti­nės sekos problema taip pat gali būti spren­džia­ma naudojant dinaminį prog­ra­ma­vi­mą, kai naudojama dvimatė matrica.

Python

def lcs(X , Y, m, n): 
    
    # Initialize dynamic programming table fields to `None`
    table = [[None] * (n + 1) for _ in range(m + 1)]
    
    # Compute table values
    for i in range(m + 1):
        for j in range(n + 1):
            # Base case
            if i == 0 or j == 0 :
                table[i][j] = 0
            # Last elements are equal
            elif X[i - 1] == Y[j - 1]:
                table[i][j] = table[i - 1][j - 1]+ 1
            # Last elements differ
            else:
                table[i][j] = max(table[i - 1][j] , table[i][j - 1])
    
    return table[m][n]
# Let's test
X = "DAVID"
Y = "DANIEL"
# Compute and output length of LCS
lcs_len = lcs(X, Y, len(X), len(Y))
print(f"Length of LCS is: {lcs_len}")
python

Java

import java.io.*;
class LCS {
    public static int lcs(String X, String Y, int m, int n)
    {
        // Initialize dynamic programming table fields
        int table[][] = new int[m + 1][n + 1];
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                // Base case
                if (i == 0 || j == 0)
                    table[i][j] = 0;
                // Last elements are equal
                else if (X.charAt(i - 1) == Y.charAt(j - 1))
                    table[i][j] = table[i - 1][j - 1] + 1;
                // Last elements differ
                else
                    table[i][j] = Math.max(table[i - 1][j], table[i][j - 1]);
            }
        }
        return table[m][n];
    }
    // Let's test
    public static void main(String args[]){
        // Initialize variables
        String X = "DAVID";
        String Y = "DANIEL";
        int m = X.length();
        int n = Y.length();
        // Compute and output length of LCS
        int lcsLength = lcs(X, Y, m, n);
        System.out.println("Length of LCS is: " + lcsLength);
    }
}
java

C++

#include <bits/stdc++.h>
using namespace std;
int lcs(string X, string Y, int m, int n) {
	// Initialize dynamic programming table
	int table[m + 1][n + 1];
	// Compute table values
	for (int i = 0; i <= m; i++) {
		for (int j = 0; j <= n; j++) {
			// Base case
			if (i == 0 || j == 0)
				table[i][j] = 0;
			// Last elements are equal
			else if (X[i - 1] == Y[j - 1])
				table[i][j] = table[i - 1][j - 1] + 1;
			// Last elements differ
			else
				table[i][j] = max(table[i - 1][j], table[i][j - 1]);
		}
	}
	return table[m][n];
}
// Let's test
int main() {
  // Initialize variables
  string X = "DAVID";
  string Y = "DANIEL";
  int m = X.size();
  int n = Y.size();
  // Compute and output length of LCS
  cout << "Length of LCS is " << lcs(X, Y, m, n);
  return 0;
}
c++
Go to Main Menu