Stringhe con Dynamic Programming
Sottosequenza comune massimale
Problema
Date due sequenze
Vediamo la definizione di sottosequenza, sottosequenza comune e sottosequenza comune massimale
- Una sequenza
è una sottosequenza di se è ottenuto da rimuovendo uno o più dei suoi elementi - Alternativamente: P è definito come il sottoinsieme degli indici
degli elementi di che compaiono anche in - I rimanenti elementi sono elencati nello stesso ordine, senza essere necessariamente contigui
- La sequenza vuota ∅ è sottosequenza di ogni altra sequenza
Esempio:
Una sequenza
Scriviamo
Esempio:
Una sequenza
Esempio:
Soluzione
Prima di procedere, definiamo il prefisso di
Soluzione ricorsiva
Distinguiamo i vari casi che possono presentarsi quando vogliamo trovare la
Caso 1 - Gli ultimi caratteri coincidono
Gli ultimi caratteri di
Allora quel carattere è una sottosequenza e possiamo tranquillamente tenercela da parte per dopo e calcolare la
Dunque:
Esempio:
Caso 2 - Gli ultimi caratteri non coincidono
Gli ultimi caratteri di
Allora bisogna controllare entrambi i casi
Dunque:
Esempio:
Caso base
La più lunga sottosequenza di
Esempio:
Equazione di ricorrenza
Implementazione
string LCS(const string& t, const string& u) {
return LCSHelper(t, t.size() - 1, u, u.size() - 1);
}
string LCSHelper(const string& t, int i, const string& u, int j) {
if(i < 0 || j < 0)
return "";
else if(t.at(i) == u.at(j))
return LCSHelper(t, i - 1, u, j - 1) + t.at(i);
else
return max(LCSHelper(t, i, u, j - 1), LCSHelper(t, i - 1, u, j));
}
- Time complexity:
(n è la lunghezza della stringa più lunga) - Space complexity:
Soluzione iterativa con dynamic programming
Date due sequenze
Equazione di ricorrenza
Implementazione
int LCS(const string& t, int n, const string& u, int m) {
vector<vector<int>> DP(n + 1, vector<int>(m + 1));
for(int i = 0; i < m + 1; i++)
DP[0][i] = 0;
for(int i = 0; i < n + 1; i++)
DP[i][0] = 0;
for(int i = 1; i < n + 1; i++) {
for(int j = 1; j < m + 1; j++) {
if(t.at(i - 1) == u.at(j - 1))
DP[i][j] = DP[i - 1][j - 1] + 1;
else
DP[i][j] = max(DP[i - 1][j], DP[i][j - 1]);
}
}
return DP[n][m];
}
- Time complexity:
(n ed m sono le lunghezze delle due stringhe) - Space complexity:
/%E2%9A%99%EF%B8%8F%20Algoritmi%20e%20Strutture%20Dati/_images/LCS-DP.png)
Ricostruzione della soluzione
Visto che tramite dynamic programming possiamo solo ottenere la lunghezza della soluzione, è necessario fare un'altra passata sulla tabella
Implementazione
string LCS(const string& t, int n, const string& u, int m) {
vector<vector<int>> DP(n + 1, vector<int>(m + 1));
for(int i = 0; i < m + 1; i++)
DP[0][i] = 0;
for(int i = 0; i < n + 1; i++)
DP[i][0] = 0;
for(int i = 1; i < n + 1; i++) {
for(int j = 1; j < m + 1; j++) {
if(t.at(i - 1) == u.at(j - 1))
DP[i][j] = DP[i - 1][j - 1] + 1;
else
DP[i][j] = max(DP[i - 1][j], DP[i][j - 1]);
}
}
return solution(t, n - 1, u, m - 1, DP);
}
string solution(const string& t, int i, const string& u, int j, const vector<vector<int>>& DP) {
if(i < 0 || j < 0)
return "";
else if(t.at(i) == u.at(j)) {
return solution(t, i - 1, u, j - 1, DP) + t.at(i);
}
else if(DP[i - 1][j] > DP[i][j - 1])
return solution(t, i - 1, u, j, DP);
else
return solution(t, i, u, j - 1, DP);
}
Se invece si vuole solo misurare la lunghezza della
Edit distance (string matching approssimato)
Problema
Siano:
una stringa detta pattern una stringa detta testo, con
Trovare un’occorrenza k-approssimata di
Un’occorrenza k-approssimata di
- I corrispondenti caratteri in
, sono diversi (sostituzione) - Un carattere in
non è incluso in (inserimento) - Un carattere in
non è incluso in (cancellazione)
Esempio:
Devo cioè inserire una 'c' tra 's' ed 'e', e sostituire la 'e' in una 'o'.
Nota: Questo problema è praticamente identico al problema "edit distance" (detta anche distanza di Levenshtein), solo che non è necessario trasformare
Soluzione
Soluzione ricorsiva
Come accennato sopra, questa soluzione ritorna il minimo numero di operazione per trasformare
Caso 1 - Gli ultimi caratteri coincidono
Gli ultimi caratteri di
Dunque non dobbiamo fare nessuna operazione tra le tre elencate all'inizio, e possiamo chiamare
Esempio:
Caso 2 - Gli ultimi caratteri non coincidono - sostituzione
Gli ultimi caratteri di
Esempio:
Caso 3 - Gli ultimi caratteri non coincidono - inserimento
Gli ultimi caratteri di
Esempio:
Caso 4 - Gli ultimi caratteri non coincidono - rimozione
Gli ultimi caratteri di
Esempio:
Caso base
Se
Esempio:
Ossia, devo effettuare 3 rimozioni per trasformare "ABC" in "".
Esempio:
Ossia, devo effettuare 3 inserimenti per trasformare "" in "ABC".
Implementazione
int min(int x, int y, int z) { return min(min(x, y), z); }
int editDistance(const string& p, const string& t) {
// We pass size() so we can return i or j and not ++i or ++j (they would be -1)
return editDistance(p, p.size(), t, t.size());
}
int editDistance(const string& p, int i, const string& t, int j) {
// If first string is empty, the only option is to
// insert all characters of second string into first
if(i == 0)
return j;
// If second string is empty, the only option is to
// remove all characters of first string
else if(j == 0)
return i;
// If last characters of two strings are same, nothing much to do.
// Ignore last characters and get count for remaining strings.
else if(p.at(i - 1) == t.at(j - 1))
return editDistance(p, i - 1, t, j - 1) + 0;
// If last characters are not the same, consider all three
// operations on last character of first string,
// recursively compute minimum cost for all three
// operations and take minimum of three values.
else return 1 + min(
editDistance(p, i - 1, t, j - 1), // Replace
editDistance(p, i, t, j - 1), // Insert
editDistance(p, i - 1, t, j) // Remove
);
}
- Time complexity:
(n è la lunghezza della stringa più lunga) - Space complexity:
Soluzione iterativa con dynamic programming
Sia
Equazione di ricorrenza
Implementazione
int editDistance(const string& p, int n, const string& t, int m) {
vector<vector<int>> DP(n + 1, vector<int>(m + 1));
for(int i = 0; i < m + 1; i++)
DP[0][i] = 0; // with DP[0][i] = i we would resolve the original edit distance
for(int i = 1; i < n + 1; i++)
DP[i][0] = i;
for(int i = 1; i < n + 1; i++) {
for(int j = 1; j < m + 1; j++) {
if(p.at(i - 1) == t.at(j - 1))
DP[i][j] = DP[i - 1][j - 1] + 0; // Nothing to do
else
DP[i][j] = 1 + min(DP[i - 1][j - 1], // Replace
DP[i][j - 1], // Insert
DP[i - 1][j]); // Remove
}
}
return *min_element(DP[n].begin(), DP[n].end());
}
- Time complexity:
(n ed m sono le lunghezze delle due stringhe) - Space complexity:
Dunque non è detto che la "soluzione finale" si trovi nella casella "in basso a destra". È invece possibile che la soluzione debba essere ricercata essa stessa nella tabella