Nel 1977 Robert Boyer e J Moore hanno sviluppato un algoritmo molto efficiente per il String Searching Matching che prende appunto il nome degli autori.

Questo algoritmo è divenuto molto famoso, tant’è che non solo è considerato l’ algoritmo più efficiente nel caso comune, ma viene implementato (tutto o in una versione più semplice) nelle funzioni di ricerca degli editor di testo. L’ algoritmo di Boyer-Moore poi, è implementato anche dal programma di utilità grep.

Dati:

  • n: E’ la lunghezza del pattern e P il pattern da ricercare
  • m: E’ la lunghezza del testo in cui cercare e T il testo in cui cercare
  • σ: E’ la lunghezza dell’ alfabeto di supporto

Come vedremo fra poco, l’ algoritmo esegue i confronti partendo da destra verso sinistra. Prima di iniziare la ricerca viene effettuato in tempo O(m+σ) un preprocessamento del pattern da ricercare,  che permette poi all’ algoritmo di svolgere la ricerca in tempo O(mn) nel caso in cui il pattern sia presente nel testo, e  O(3m) nel caso in cui il pattern non sia presente.

Come funziona

Una delle idee chiave dell’ algortimo è il fatto di cercare di eseguire il matching del pattern dalla fine di questo, e non dall’ inizio. Questo ci permette, in caso di mismatch, di spostarci avanti di n posizioni (ovvero della lunghezza del pattern) nel caso migliore.

Per esempio:

         0 . j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 . . . . m
T:       P   I   A   R   C   I   A   O
P:       C   I   A   O                          n = 4

In questo caso, visto che T[j+3] = 'R' e P[4] = 'O' sono diversi, possiamo spostare il puntatore avanti di n posizioni. Questo è il caso migliore, lo spostamento in avanti si calcola prendendo il massimo fra due funzioni chiamate Good-Suffix Shift e Bad-Character Shift.

Bad-Character Shift: spostamento del carattere discordante

Il  Bad-Character Shift ci dice di trovare la prima occorrenza del carattere discordante del testo T in P, che indicheremo con k.

Ci sono 3 casi, che vediamo per esempi:

Primo caso: s = 0

E’ l’ esempio visto poco fa, che riproponiamo:

   0 . . j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 . . . . m
T:       P   I   A   R   C   I   A   O
P:       C   I   A   O                          n = 4

“R” è il carattere discordante. Cerchiamo la posizione k in P tale per cui P[k] = T[j+1] e la indichiamo con s. Visto che "R" non è presente in P, e quindi k=0, questa euristica ci suggerisce che possiamo spostare tranquillamente il pattern di 4 posizioni:

   0 . . j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 . . . . m
T:       P   I   A   R   C   I   A   O
P:                       C   I   A   O          n = 4

Secondo caso: s < k

Abbiamo un carattere discordante in posizione k=j+4:

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 . . . . m
T:          A   S   D   F   P   I   A   R   C   I   A    O
P:          R   C   A   P   C   I   A   O                          n = 8

Cerchiamo nel pattern P la prima occorrenza del carattere discordante T[k] (ovvero “P”). Questo si trova in posizione s=j+3.
Visto che k si trova a destra di s (la fine del nostro pattern) possiamo spostare avanti il pattern di k-s posizioni.

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 . . . . m
T:          A   S   D   F   P   I   A   R   C   I   A    O
P:              R   C   A   P   C   I   A   O                        n = 8

Terzo caso: s > k

Abbiamo un carattere discordante in posizione k=j+4:

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 . . . . m
T:          A   S   D   F   R   I   A   R   C   I   A    O
P:          S   C   A   P   C   I   A   R                          n = 8

Come in precedenza, l’ algoritmo del carattere errato cerca una corrispondenza del carattere discordante T[j+4] = “R” in P. In questo caso, lo troverà a destra di k, in posizione s=j+7. In questo caso quindi ci proporrà un poco conveniente spostamento negativo di s-k posizioni. L’ altra euristica verrà quindi scelta per questo caso.

 

Good Suffix Shift

Partiamo da un esempio come prima:

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 j+12 . . . . m
T:          A   S   D   F   R   I   A   R   O   C    I   A    R
P:          I   A   R   O   C   I   A   R                                 n = 8

In questo caso, il Bad Character abbiamo visto che ci suggerisce uno spostamento negativo. Il Good Suffix Shift invece, cerca nel nostro pattern P una sequenza di caratteri uguale a quella che ha determinato il “buon suffisso” (ovvero tutto ciò che c’è a destra del carattere discordante k=j+4). Visto che la sequenza [j+5 … j+7] = “IAR” è presente nel nostro pattern, ci suggerisce di spostarci in modo da far combaciare le sequenze:

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 j+12 . . . . m
T:          A   S   D   F   R   I   A   R   O   C    I   A    R
P:                              I   A   R   O   C    I   A    R           n = 8

In caso non sia possibile “riciclare” il suffisso, ci viene suggerito uno spostamento del pattern in avanti fino a dopo l’ultima posizione in T considerata (quindi di n posizioni):

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 j+12 . . . . m
T:          A   S   D   F   R   I   A   R   O   C    I   A    R
P:          I   B   R   O   C   I   A   R                                 n = 8

Visto che il suffisso nonè presente nel pattern:

   0 . . .  j  j+1 j+2 j+3 j+4 j+5 j+6 j+7 j+8 j+9 j+10 j+11 j+12 . . . . m
T:          A   S   D   F   R   I   A   R   O   C    I   A    R
P:                                          I   B   R   O   C   I   A   R  n = 8

Da questo algoritmo, è nata una famiglia di algoritmi chiamata appunto Boyer-Moore. La caratteristica di eseguire un matching dalla fine della stringa, gli permette di eseguire molti meno confronti e di essere quindi molto efficiente.