Algoritmi e Strutture Dati - Algoritmi di String Matching

Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Algoritmi e Strutture Dati
Algoritmi di String Matching
Maria Rita Di Berardini, Emanuela Merelli1
1 Dipartimento
di Matematica e Informatica
Università di Camerino
A.A. 2007/08
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Il problema dello string matching
Trovare tutte le occorrenze di una data stringa (pattern) all’interno
di un testo
Applicazioni: programmi di elaborazione dei testi, cercare particolari
sequenze di DNA
Il testo viene rappresentato mediante un array T [1..n] di lunghezza n
Il pattern viene rappresentato con un array P[1..m] di lunghezza m
Sia il testo che il pattern sono stringhe su un alfabeto finito Σ (ad
esempio: Σ = {0, 1} oppure Σ = {a, b, ..., z})
Diciamo che P occorre con uno spostamento s nel testo T se
0 ≤ s ≤ n − m e T [s + 1 .. s + m] = P[1 .. m]
(ossia se T [s + j] = P[j] per 1 ≤ j ≤ m). In questo caso diciamo
che s è uno spostamento valido per P
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
L’algoritmo “naive” di string matching
Trova tutti gli spostamenti validi utilizzando un ciclo che verifica la
condizione T [s + 1 .. s + m] = P[1 .. m] per ciascun degli n − m + 1
valori possibili di s
1
2
3
4
5
Naive-String-Matcher(T , P)
n ← length[T ]
m ← length[P]
for s ← 0 to n − m
do if T [s + 1 .. s + m] = P[1 .. m]
then stampa “pattern con spostamento s”
Richiede un tempo O((n − m + 1)m) (questo limite è stretto nel
caso peggiore)
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Algoritmo di Rabin-Karp
Usa una funzione hash per associare un valore numerico al pattern e
ad ogni sottostringa del testo lunga m
Indichiamo con p il valore numerico del pattern e con ts il valore di
T [s + 1 .. s + m], per ogni s = 0, . . . , n − m
Se p 6= ts , lo spostamento non può essere valido e calcoliamo il
valore della prossima sottostringa
Se p = ts , possiamo confrontare T [s + 1, .. s + m] con P[1, .. m]
(esattamente come l’algoritmo naive)
Questo consente di ridurre drasticamente il numero di confronti tra
pattern e sottostringhe del testo
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Funzione Hash
Idea: considerare una sequenza di m caratteri come un numero di m
cifre in base b (dove b tipicamente è la dimensione dell’alfabeto Σ)
La sottostringa T [s + 1, .. s + m] diventa il numero
ts = T [s + 1] · b m−1 + T [s + 2] · b m−2 + . . . + T [s + m − 1]b + T [s + m]
Inoltre, possiamo ottenere ts+1 da ts come segue
1
Moltiplichiamo ts per b (shift a sinistra di una cifra) ottenendo
T [s + 1] · b m + T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Funzione Hash
Idea: considerare una sequenza di m caratteri come un numero di m
cifre in base b (dove b tipicamente è la dimensione dell’alfabeto Σ)
La sottostringa T [s + 1, .. s + m] diventa il numero
ts = T [s + 1] · b m−1 + T [s + 2] · b m−2 + . . . + T [s + m − 1]b + T [s + m]
Inoltre, possiamo ottenere ts+1 da ts come segue
1
Moltiplichiamo ts per b (shift a sinistra di una cifra) ottenendo
T [s + 1] · b m + T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
2
Sotttraimo il valore T [s + 1] · b m (sottrai la cifra più a sinistra)
ottenendo T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Funzione Hash
Idea: considerare una sequenza di m caratteri come un numero di m
cifre in base b (dove b tipicamente è la dimensione dell’alfabeto Σ)
La sottostringa T [s + 1, .. s + m] diventa il numero
ts = T [s + 1] · b m−1 + T [s + 2] · b m−2 + . . . + T [s + m − 1]b + T [s + m]
Inoltre, possiamo ottenere ts+1 da ts come segue
1
Moltiplichiamo ts per b (shift a sinistra di una cifra) ottenendo
T [s + 1] · b m + T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
2
Sotttraimo il valore T [s + 1] · b m (sottrai la cifra più a sinistra)
ottenendo T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
3
Sommiano il valore T [s + m + 1] (aggiungi una nuova cifra a
destra) ottenendo
T [s +2]·b m−1 +. . .+T [s +m−1]·b 2 +T [s +m]·b+T [s +m+1] = ts+1
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Funzione Hash
Idea: considerare una sequenza di m caratteri come un numero di m
cifre in base b (dove b tipicamente è la dimensione dell’alfabeto Σ)
La sottostringa T [s + 1, .. s + m] diventa il numero
ts = T [s + 1] · b m−1 + T [s + 2] · b m−2 + . . . + T [s + m − 1]b + T [s + m]
Inoltre, possiamo ottenere ts+1 da ts come segue
1
Moltiplichiamo ts per b (shift a sinistra di una cifra) ottenendo
T [s + 1] · b m + T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
2
Sotttraimo il valore T [s + 1] · b m (sottrai la cifra più a sinistra)
ottenendo T [s + 2] · b m−1 + . . . + T [s + m − 1] · b 2 + T [s + m] · b
3
Sommiano il valore T [s + m + 1] (aggiungi una nuova cifra a
destra) ottenendo
T [s +2]·b m−1 +. . .+T [s +m−1]·b 2 +T [s +m]·b+T [s +m+1] = ts+1
Ricapitolando ts+1 = (ts · b − T [s + 1] · b m ) + T [s + m + 1]
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Funzione Hash – Un esempio
Σ = {0, 1, . . . , 9}, T = 12345678 e P = 345 (m = 3)
Sia il pattern che le sottostringhe vengono considerate come numeri
in base 10
p = 345, t0 = 123, t1 = 234, t2 = 345, t3 = 456, t4 = 567 e
t5 = 678
ts+1 = (ts · b − T [s + 1] · b m ) + T [s + m + 1]
t1 = (t0 ·10−T [1]·103 )+T [4] = (123·10−1000)+4 = 230+4 = 234
t2 = (t1 ·10−T [2]·103 )+T [5] = (234·10−2000)+5 = 340+5 = 345
...
L’algoritmo verifica che p 6= t0 , p 6= t1 , ma p = t2 . A questo punto
verifica che P = 345 = T [2 + 1 .. 2 + m] = 345 e restituisce lo
spostamento 2
Poichè p 6= t3 , t4 , t5 non produce altri risultati
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Funzione Hash mod
L’algoritmo di Rabin-Karp esegue una serie di confronti e operazioni
aritmetiche (si veda il calcolo di ts+1 a partire da ts ) su numeri di m
cifre
Se si vuole che l’algoritmo sia efficiente, è necessario poter eseguire
queste operazioni in un tempo costante
Se m è molto grande, i valori di p e dei vari ts potrebbero essere
troppo grandi per lavorarci in maniera conveniente
Per questo motivo è utile calcolare i valori p e ts con 0 ≤ s ≤ n − m
modulo un qualche numero primo q
Aumenta la possibilità che si verifichino i cosidetti falsi successi,
i.e. i casi in cui p = ts ma T [s + 1 .. s + m] 6= P[1 .. m]
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Alcuni dettagli dell’algoritmo
Come calcoliamo i valori associati al patter P e a T [1 .. m], ossia p e
t0 (che verra poi usato per calcolare gli altri ts ) modulo un dato q?
Negli esempi che seguono assumiamo q = 11 e la base b = 10
Consideriamo P = 235, il suo valore ossia p = 235 mod 11 = 4.
Possiamo calcolare p mediante la seguente sequenza di valori:
p0 = 0
p1 = (b · p0 + P[1]) mod 11 = 2 mod 11 = 2
p2 = (b · p1 + P[2]) mod 11 = (10 · 2 + 3) mod 11 = 1
p3 = (b · p2 + P[3]) mod 11 = (10 · 1 + 5) mod 11 = 4
I valori p e t0 vengono calcolati mediante il seguente frammento di
codice
p ← 0, t0 ← 0
for i ← 1 to m
do p ← (b · p + P[i])
t0 ← (b · t0 + T [i])
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Alcuni dettagli dell’algoritmo
Anche i successi valori valori di ts devono essere calcolati modulo q
Ossia per 1 ≤ s ≤ n − m,
ts+1 = (ts · b − T [s + 1] · b m ) + T [s + m + 1] mod q
= b(ts − T [s + 1] · b m−1 ) + T [s + m +
1] mod q
= b(ts − T [s + 1] · h) + T [s + m + 1] mod q
dove h = b m−1
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
L’algoritmo di Rabin-Karp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Rabin-Karp-Matcher(T , P, b, q)
n ← length[T ]
m ← length[P]
h ← b m−1
p←0
t0 ← 0
for i ← 1 to m
do p ← (b · p + P[i])
t0 ← (b · t0 + T [i])
for s ← 0 to n − m
do if p = ts
then if P[1 .. m] = T [s + 1 .. s + m]
then stampa “pattern con spostamento s”
if s < n − m
then ts+1 = b(ts − T [s + 1] · h) + T [s + m + 1] mod q
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Calcolo della complessità
Il calcolo di h (riga 3) può essere effettuato in O(m)
Il calcolo di p e t0 viene effettuato in O(m) (ciclo for di righe 6-8)
Il ciclo for di righe 9-14 esegue
n − m + 1 confronti del tipo p = ts (ognuno in un tempo costante)
più il numero di confronti (al più m) per stabilire se
P[1 .. m] = T [s + 1 .. s + m]
(a)
nel caso peggiore (ossia quando tutti gli spostamenti sono validi) il
test (a) viene eseguito n − m + 1 volte ed ogni volta richiede m
confronti. In totale abbiamo m(n − m + 1) confronti
Quindi, nel caso peggiore il for di righe 9-14, e quindi l’algoritmo,
ha un costo dell’ordine di
O((m + 1)(n − m + 1)) = O(m(n − m + 1))
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Calcolo della complessità
In molte applicazioni sono previste solo pochi spostamenti validi,
talvolta soltanto un numero costante c
In questi casi, il test (a) viene eseguito solo c volte e richiede al più
cm confronti
Il ciclo for di righe 9-14 esegue al più
n − m + 1 + cm = n + (c − 1)m + 1
confronti
L’algoritmo, ha un costo dell’ordine di
O(n + (c − 1)m + 1)) = O(n + m)
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
Algoritmo di Knuth-Morris-Pratt (KMP)
s+1
s+q
b a c b a b a b a a b c b a b
s
a b a b a c a
q
k
s’
a b a b a c a
s’+1
Di Berardini, Merelli
s’+k
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
La funzione fallimento/prefisso
Sapendo che i primi q caratteri del pattern P[1 .. q] coincidono con i
caratteri del testo T [s + 1 .. s + q] trovare il minimo spostamento s 0
tale che
P[1 .. k] = T [s 0 + 1 .. s 0 + k]
con s 0 + k = s + q (e quindi s 0 = s + (q − k))
Che relazione esiste tra Pq = P[1 .. q] e Pk = P[1 .. k]?
Sappiamo che P[1 .. k] = T [s 0 + 1 .. s 0 + k] con s 0 + 1 > s + 1 e
s0 + k = s + q
Pk è un suffisso proprio di T [s + 1 .. s + q] = Pq
Siano x, y ∈ Σ∗ . Diciamo che y è un suffisso di x se esiste una
stringa w ∈ Σ∗ tale che x = wy . Quando y 6= allora diciamo che
y è un suffisso proprio di x (denotato con y = x)
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
La funzione fallimento/prefisso
Stiamo cercando un Pk (prefisso del pattern P) che sia anche un
suffisso di Pq ma k deve anche minimizzare il valore di
s 0 = s + (q − k)
Quindi k è il più grande k < q tale che Pk = Pq
Definition (la funzione fallimento/prefisso)
π[q] = max{k : k < q e Pk = Pq }
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
L’algoritmo di KMP
1
2
3
4
5
6
7
8
9
10
11
12
13
KMPMatcher(T , P)
π ← KMPPrefixFunction(P)
i ←1
j ←1
while i ≤ n do
if P[j] = T [i] then
if j = m .match trovato
then stampa “pattern con spostamento i − m + 1
i ←i +1
j ←j +1
else if j > 1 then
j ← π[j − 1] + 1
else
i ←i +1
Di Berardini, Merelli
Algoritmi e Strutture Dati
Definizione del problema
Algoritmo di Rabin-Karp
Algoritmo di Knuth-Morris-Pratt
La funzione KMPPrefixFunction
1
3
4
5
6
7
8
10
11
12
13
14
KMPPrefixFunction(P)
i ←2
j ←1
while i ≤ m do
if P[j] = P[i] then
π[i] ← j
i ←i +1
j ←j +1
else if j > 1 then
j ← π[j − 1] + 1
else
π[i] ← 0
i ←i +1
Di Berardini, Merelli
Algoritmi e Strutture Dati