Căutare pe şiruri
Proiect Analiza Algoritmilor
Descriere
Rabin si Karp au propus un algoritm pentru potrivirea sirurilor care se comporta bine in practica si care se poate generaliza si la alti algoritmi pentru probleme asemanatoare, cum ar fi potrivirea modelelor bidimensionale. Timpul de executie, in cazul cel mai defavorabil, este O( (n - m + 1) * m ), insa are un timp mediu de executie bun.
Pentru prezentarea propusa, presupunem un alfabet finit ∑ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} ( se poate considera de asemenea ∑ = {a, b, c, ..., z} ), deci fiecare caracter este o cifra zecimala (in general, putem presupune ca fiecare caracter este o cifra in baza d). Astfel putem privi un sir de k caractere consecutive ca reprezentand un numar zecimal de lungime k.
Fiind dat modelul P[1...m], notam cu p valoarea sa zecimala corespunzatoare. Intr-o maniera similara, fiind dat textul T[1...n], notam cu ts valoarea zecimala a subsirului T[s + 1 ... s + m] de lungime m, pentru
s = 0, 1, ..., n - m. Desigur ts = p daca si numai daca T[s + 1 ... s + m] = P[1 ... m]; astfel s este un deplasament corect daca si numai daca ts = p. Daca putem calcula p in timpul O(m) si toate valorile ti, in timpul total O(n), atunci putem determina toate deplasamentele corecte s in timpul O(n) prin compararea lui p cu fiecare ts.
Putem calcula p in timpul O(m) folosind schema lui Horner:
p = P[m] + 10( P[m - 1] + 10( P[m -2] + .....+ 10( P[2] + 10P[1] ) ... ) ).
Valoarea t0 poate fi calculata in mod analog din T[1...m] in timpul O(m). Pentru calcularea valorilor t1, t2, ...tn-m, in timpul O(n - m), este suficient sa observam ca ts + 1 poate fi calculat din ts intr-un timp constant deoarece:
ts+1 = 10(ts - 10m-1T[s + 1]) + T[s + m + 1].
De exemplu fie m = 5 si ts = 31415. Daca dorim sa stergem cifra cea mai semnificativa T[s + 1] = 3 si sa aducem pe pozitia cea mai putin semnificativa o noua cifra (de exemplu T[s + 5 + 1] = 2) avem:
ts+1 = 10(31415 - 3 * 1000) + 2 = 14152
Scazand 10m - 1T[s + 1], se sterge cifra cea mai semnificativa din ts; inmultind rezultatul cu 10, se deplaseaza numarul spre stanga cu o pozitie si adunand la aceasta cifra T[s + m + 1] o aduce la pozitia cea mai putin semnificativa. Daca, in prealabil, calculam constanta 10m-1 atunci fiecare executie a ecuatiei necesita un numar constant de operatii aritmetice. Astfel p si t0, t1, t2, ...tn-m pot fi calculate in timpul O(n + m) si putem determina toate aparitiile modelului P[1...m] in textul T[1...n] intr-un timp O(n + m).
Singurul dezavantaj la aceasta procedura este ca p si ts pot fi prea mari ca sa se poate lucra convenabil cu ele. Daca P contine m caractere, atunci presupunerea ca fiecare operatie aritmetica asupra lui p dureaza un timp contant nu este destul de buna. Dar exista o rezolvare simpla pentru aceasta problema, calculul lui p si ts modulo o valuare potrivita q. Deoarece calcilul lui p si t0 si recursivitatea pot fi calculate modulo q, atunci p si toate valorile ts pot fi calculate modulo q in timpul O(m + n). Valuarea q se alege de obicei un numar prim astfel incat 10q sa poata fi reprezentat pe un cuvant de memorie, ceea ce permite ca toate operatiile necesare sa fie efectuate cu precizie aritmetica simpla. In general avand dat un alfabet {0, 1, 2, ...,d - 1}, alegem q astfel incat sa se calculeze modulo q:
ts+1 = (d(ts - T[s + 1]h) + T[s + m + 1]) mod q,
unde h = dm-1(mod q) este valuarea pentru cifra "1" in pozitia cea mai semnificativa a ferestrei text de m-cifre. Calitatea folosirii modulo q confera rapiditate, desi ts = p(mod q) nu implica ts = p. Pe de alta parte daca ts ≠ p(mod q) atunci avem ts ≠ p, astfel incat deplasamentul s este incorect. Deci putem folosi testul ts = p(mod q) ca pe un test rapid pentru depistarea deplasamentelor incorecte s. Orice deplasament s pentru care ts = p(mod q) trebuie sa fie testat in plus pentru a vedea daca nu este doar o falsa potrivire. Aceasta testare poate fi facuta prin verificarea conditiei P[1...m] = T[s + 1...s + m]. Daca q este destul de mare atunci potrivirile false apar destul de rar.
Sa consideram urmatorul exemplu: se va cauta numarul 31415 in sirul 2359023141526739921. Deoarece 31415 mod 13 = 7, o sa cautam toate combinatiile p unde p mod 13 = 7:
2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 8 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 9 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 3 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 11 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 0 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 1 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 7 potrivire la pozitia 7 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 8 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 4 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 5 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 10 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 11 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 7 potrivire falsa 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 9 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 --------- 11
Concluzii
Timpul de executie, in cel mai defavorabil caz, pentru algoritmul Rabin-Karp este O( (n - m + 1) * m ) deoarece algoritmul verifica explicit fiecare deplasament corect. Daca P = am si T = an, atunci verificarile dureaza un timp O( (n - m + 1) * m ) deoarece fiecare dintre cele n - m + 1 deplasamente posibile sunt corecte. In multe aplicatii ne asteptam la putine deplasamente corecte , astfel timpul de executie este O(n + m), plus timpul necesar pentru procesarea potrivirilor false. Ne putem astepta ca numarul de potriviri false sa fie O(n / q), deoarece posibilitatea ca un ts arbitrar sa fie echivalent cu p(mod q) poate fi estimata la 1 / q. Atunci timpul de executie estimat este:
O(n) + O( m(v + n / q) ) ,
unde v este numarul de deplasamente corecte. Acest timp de executie este O(n) daca alegem q >= m. Asadar, daca numarul estimat de deplasamente corecte este mic ( O(1) ) si numarul prim q este ales mai mare decat lungimea modelului atunci se estimeaza pentru algoritmul Rabin - Karp un timp de executie O(n + m).
Pseudocodul pentru algoritmul Rabin - Karp
potrivireRabinKarp(T, P, d, q)
|
O implementare in limbajul C++
#include <iostream.h>
cout << "\nGiven pattern \n\"" << p << "\" and text \n\"" << a << "\"\n";
|