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