Você está na página 1de 8
Projeto de Algoritmos | LinguagemC | indice Busca de palavras em um texto "€.como procurar agulha num palheito. ‘io popular ‘Quantss veces palavra‘algortmo" aparece neste capitulo? Procurar uma palavra num texto é uma atividade corriqueira. No contexto computacional, esse problema ¢ conhecido como substring searching ou string matching e pode ser formulado assim: Encontrar as ocorréncias de um dado vetor a em um dado vetor 6. Suporemos que os elementos dos vetores sdo bytes (embora o problema faca sentido para vetores de qualquer outro tipo). Mas nao vamos supor que os vetores sdo sirings, ou seja, que terminam com um byte nulo Em muitas aplicacées, os elementos de a c b representam caracteres ASCII ¢ assim cada byte pertence ao conjunto 0.127). Em outras aplicacoes, ae b representam sequéncias de caracteres Unicode em codigo UTE-$ (portanto, cada caractere é representado por 1, 2,3 ou 4 bytes consecutivos). Em geral, entretanto, nao ha restrigGes sobre os elementos de a e b: cada um pode assumir qualquer valor entre 0 ¢ 255. Diremos que o vetor « é uma palavra (mesmo que nao represente uma palavra em portugués ou inglés no sentido usual) e 0 vetor b é um texto, O problema consiste, entdo, em encontrar as ocorréncias de uma palavra em um texto. A seguinte figura sugere uma aplicagao ao processamento de cédigo genético. Nesse caso, os elementos dos vetores sao as letras A, C,GeT. A figura destaca uma ocorréncia da palavra TACT A no textoGTAG... TAG GTAGTATATATATATACTACTAGTAG TACTA Outro exemplo: procurar por um determinado virus de software num arquivo digital também ¢, essencialmente, um problema de busca de uma palavra (o virus) num texto (0 arquivo). Terminologia e decisdes de projeto Jé tomamos a decisao de tratar apenas de vetores de bytes. Tomaremos outra decisio de projeto: a palavra e 0 texto serdo indexados a partir de 1 (e nao a partir de 0) a fim de simplificar ligeiramente as expressdes envolvendo segmentos desses vetores. Finalmente, vamos nos restringir a verso simplificada do problema que pede apenas um niimero como resposta: Encontrar 0 nimero de ocorréncias de uma palavra a1... m] em um texto bf.) Se m> n, o mtimero de ocorréncias de a em b é zero. Para garantir que o ntimero de ocorréncias seja finito, suporemos sempre que m>1, Convém adotar a seguinte terminologia ao tratar do problema: + um vetor a[1..m] casa com um vetor b[j..k] se afl] = bf], a[2] = bfj+1], ..., afm] m-1= kj; ‘+ um sufixo de um vetor b[1 . .k] € qualquer segmento terminal do vetor, ou seja, qualquer segmento da forma blj..K], com altimo indice igual a k; + aexpressdo "a[1 ...m] casa com um sufixo de bf. k]" seré usada como abreviatura de "@[1 .. mi] casa com blk-m+1 ky"; + um vetor [1 ...m] ocorre emt b{1..n] se existe k tal que a1 . m1] casa com um sufixo de bl1 ..k} [k]; é claro que devemos ter Note que duas ocorréncia de a em b podem se sobrepor. Por exemplo, as duas ocorréncias de BABA em XBABABAX se sobrepoem, Exemplos. Nos exemplos a seguir, o sinal | indica as posigdes k em que o vetor a (linha inferior) casa com um sufixo do vetor b[1..k] linha superior) Un Vekor a eee rnelem blk e L 1 2232432423242 5 932425926304 314159 CTAGTATATATATATACTACTAGTAG TACT Diregdes de varredura. Qualquer algoritmo que procure uma palavra num texto deveré executar uma varredura (= scart) do texto. Para procurar as ocorréncias de uma palavra a num texto b, poderiamos varrer b da esquerda para a direita ou da direita para a esquerda, As duas alternativas so equivalentes, mas vamos adotar sempre a primeira: comparar a.com BIL... m], depois com b[2. . #1], assim por diante. Para um k fixo, a comparacdo elemento-a-clemento de a[1 ..m] com um sufixo de b[1 ..k] poderia ser feita da esquerda para a direita ou da direita para a esquerda. Em geral, as duas alternativas sao equivalentes, mas um dos algoritmos que veremos adiante exige que a comparacio seja feita na direcao contriria a da varredura do texto. Por isso, a comparacao elemento-a- elemento sera sempre feita da direita para a esquerda: primeiro a{m] com bk], depois a{m-1] com b{k-1], e assim por diante. ABA AAAAA ACGeT agdo notacdo bindria Algoritmo inocente A seguinte funcao resolve o problema (da busca de uma palavra em um texto) da maneira mais Sbvia e direta. Pacientemente, ela tenta casar a com b[1..m], depois com b[2. .m+2], e assim por diante: typedef unsigned char bytes int Anocente (byte a[], int m, byte bl], int n) < Ant ocorrs = 0; for (int k = mj k <= nj +4k) { 4 (1 ©) socores; y Feturn ocorrs; y Podemos imaginar que, a cada iteragao, a palavra a desliza para a direita ao longo do texto b, como no seguinte exemplo: XCBABXCBAAXBCBABX BcbA BCBA BCBA BCBA BCBA No pior caso, a fungao inocente compara cada elemento de a com cada elemento de b e portanto consome tempo proporcional a an Sera possivel resolver o problema sem comparar cada elemento de a com cada elemento de b? mon se tentarmos executar a funcio com args gual 2e aed. inocente versio original acmb i a{t..n] b[j..mej-1] strstr da biblioteca string strstr bia. .n] ® mespacos b b saracteres ASCIL b Primeiro algoritmo de Boyer-Moore Um alfabeto de uma instancia do problema é qualquer conjunto de bytes que contém todos os elementos dos vetores ae b. Eiclaro que toda instancia admite @. .255 como alfabeto. Mas algumas instancias podem ter um alfabeto menor, como @. .127 no caso de caracteres ASCII, ou como AC GT no caso das aplicacées a genética RS. Boyer e |S, Moore tiveram a engenhosa ideia de usar uma tabela auxiliar indexada pelo alfabeto para acelerar 0 algoritmo inocente. Suponha que j4 comparamos a[1. .m] com um sufixo de b[1. .k]. Agora, podemos saltar alguemas iteragdes do algoritmo inocente e passar a comparar a[1..m] com um sufixo de [1 -ked] para algum d positivo. O valor de d é escolhido de tal modo que a posigao k+1 de b fique emparelhada com a tiltima ocorréncia (contando da esquerda para a direita) do byte b[k+1] em a[1..m]. No exemplo abaixo, marcamos com | as posigdes que fazem o papel de k+d. No caso em que ha casamento, a marca | foi substituida por | Lot Ly Lo XCBABKCBAAXBCBABX BCBA BCBA BC BA BcaA aicala BC BA Para implementar a ideia, basta pré-processar a palavra a de modo a lembrar, para cada "letra" f do alfabeto, A.a posicao da tiltima ocorréncia (contando da esquerda para direita) de f em a, Essa posicao seré denotada por ult[f]. Seo alfabeto do exemplo anterior é 0 conjunto dos 128 caracteres ASCII, teremos uitl#] Segue uma implementacdo do algoritmo. O primeiro processo iterativo faz o pré-processamento da palavra e o segundo cuida de contar as ocorréncias da palavra no texto. typedef unsigned char bytes int boyermooret (byte af], int m, byte bt], int n) « int ult{256]; for (int # = 0; f < 256; ef) ult[f] = @5 for (int i = 1; 4 <= mj 444) ult Lala] int ocores = @5 int k= m5 white (k'< n) { int i =m, J = ks while (1381 88 a[{] == 6{5]) 4 (L 61) rocores; if (koe ny k= 4; else k +2 m - ult{b[kea]] +3; > return ocorrsy y Esse é 0 primeiro algoritmo de Boyer-Moore. for (byte # = 0; F< 256; +44) ultff] = @: 1s ult[f] <¢ f ks m-ult[o[ker}] +2 blkea] a[t..m] b[ker] alm) boyernoores ult. a b ocorrs = 8; k= mi walle (kn) ¢ fem jek white’ Gh >= 1 a8 aft if (4 < 2) socorrss kk = kets While (kk <= n 88 ULt[[kK]) == 0) 44k: 3 (ke > n) break; keve m= ultofki]) + kk = ks retura ocorrs; nyktea blasa] boyernooret boyernooret Ant ult[256]; for (int 1 = 8; 4 ¢ 296; #04) vlt[i] = ey for (int i= 2; 4 € mi #04) ultfalil] = fs int ocorrs = 8, k= while (k = 1 8& k'>= 2) { int ism, j= ks While (i >= h 8&5 >= 1) af (a[i] == a[J]) --1, else i=in, j= jump h=-] = k3 y hile (h >= 1) Sump[h- int ocorrs kam; hile (k <= n) ( int i= while (i Af (4 < 1) te0corrs; a) k #1; = jump[iea]; y return ocorrss > Segue uma versio mais compacta e eficiente do pré-processamento: hem, k= a4; ism j= ks while (h >= 1) ( while (1 >= h && J >= 1) Ay 3s af (aLi] else i =m, 3 jump[h=-] = k3 > (5) boyernoore2 iF boyermoore2 jump. jump[ne2] white (4 af (a{1] elses = sumpih] = ks k for (n= m; h >= 4; --h) { while (em >= haw Ker > 2) AF (almer] = alker]) +r else r= 8, ken} Sjumpth] = ks boyernoore2 ABcAccaac ABCCBAABCARCACCABC Terceiro algoritmo de Boyer-Moore O terceiro algoritmo de Boyer-Moore é uma fusao dos dois anteriores. A cada paso, o algoritmo escolhe o maior dos dois deslocamentos: aquele ditado pela tabela ult e aquele dado pela tabela junp. (Esse é o algoritma de Boyer-Moore propriamente dito. A distingao que fizemos acima entre primeiro e segundo algoritmos ¢ apenas didatica.) O pré-processamento consome n? unidades de tempo. Infelizmente, a fase de busca consome mn unidades de tempo no pior ‘caso, tal como no algoritmo inocente. Mas o pior caso ¢ tao raro que no caso médio, tipico de aplicagses praticas, 0 terceiro algoritmo de Boyer-Moore consome tempo proporcional a n e independente de m, Ou seja, em média, cada elemento do texto ¢ comparado com apenas alguns poucos elemento da palavra, qualquer que seja o comprimento da palavra. ‘A definicao da tabela jump pode ser aperfeicoada de tal maneira que, mesmo no pior caso, a fase de busca do terceiro algoritmo consuma apenas 6n unidades de tempo. 2 inocente br-utfa.txt ca “Lis (Quincas Borba jump 6 b[1..n}, aft..m] ex[t..p] x aemb * 4 ana A c 8 acc xe[#]# x{i] = x{i-a] + 1; eree, Veja o verbete String searching algoriti na Wikipedia, Veja o sitio Exact String Matching Algoritms, com animagio de algoritmos de busca de palavras em texto, Veja também a bibliografia Pattern Matching Pointrs, Atualizado em 2018.08.13 htips:/ / www.ime.usp.br/~pf/algoritmos/ Paulo Feofiloff DCCIME-USP

Você também pode gostar