Strings (Casamento de padrões) Estrutura de Dados II Jairo Francisco de Souza
Strings Tipo de dado importante para diversas aplicações. Abordaremos algumas questões relacionadas com Strings nos seguintes tópicos Busca em Strings (String matching) Codificação Compressão 2
Busca em Cadeias de Caracteres Dados a ser processados nem sempre se decompõem logicamente em registros independentes com pequenas partes identificáveis Este tipo de dados é caracterizado apenas pelo fato de que pode ser escrito como uma cadeia Uma cadeia é uma seqüência linear de caracteres, tipicamente podendo ser muito longa 3
Busca em Cadeias de Caracteres Cadeias são centrais em sistemas de processamento de textos, recuperação de informação, estudo de sequências de DNA em biologia computacional, etc. Esses objetos podem ser bem grandes e algoritmos eficientes são necessários para manipulá-los T = ABACAABACCABACABAABB P = ABACAB P é uma substring de T, mais exatamente, P = T[10...15]. 4
Busca em Cadeias de Caracteres Outro tipo de cadeia é a cadeia binária Cadeia binária é uma seqüência de apenas valores 0 e 1 Em certo sentido, isso é simplesmente um caso especial de cadeia de caracteres Mas vale a pena fazer distinção, porque algoritmos diferentes são apropriados para cada caso Além disso, cadeias binárias aparecem naturalmente em muitas aplicações Por exemplo, alguns sistemas gráficos representam figuras como cadeias binárias 5
Busca em Cadeias de Caracteres Veremos que o tamanho do alfabeto a partir do qual os caracteres são tomados para formar uma cadeia é um fator importante no projeto de algoritmos de processamento de cadeias Uma operação fundamental sobre cadeias é o casamento de padrão: Dado uma cadeia de comprimento N e um padrão de comprimento M, encontrar uma ocorrência do padrão no texto Vamos usar o termo texto para referenciar tanto uma seqüência de valores 0 e 1 quanto qualquer outro tipo especial de cadeia 6
Busca em Cadeias de Caracteres A maioria dos algoritmos para o problema de casamento de padrão pode ser facilmente estendida para encontrar todas as ocorrências do padrão no texto O problema de casamento de padrão pode ser visto também como um problema de busca com o padrão sendo a chave 7
Um Breve Histórico O algoritmo mais óbvio de busca em cadeia, chamado algoritmo força-bruta ou algoritmo ingênuo, tem o pior caso de tempo de execução proporcional a MN Embora as cadeias que aparecem em muitas aplicações levam a um tempo de execução que é virtualmente proporcional a M + N 8
Um Breve Histórico Em 1970, S. A. Cook provou um resultado teórico sobre um tipo particular de autômato que implicava na existência de um algoritmo de casamento de padrão com tempo proporcional a M + N no pior caso D. E. Knuth e V. R. Pratt seguindo a construção que Cook usara na demonstração do seu teorema obtiveram um algoritmo relativamente simples e prático 9
Um Breve Histórico Ocorreu também que J. H. Morris descobriu praticamente o mesmo algoritmo como solução de um problema de edição de texto Os três cientistas, Knuth, Morris e Pratt, não se preocuparam em publicar o algoritmo até 1976 Nesse meio tempo, R. S. Boyer e J. S. Moore (e, independentemente, R. W. Gosper) descobriram um algoritmo que é muito mais rápido em muitas aplicações Muitos editores de texto usam esse algoritmo para busca de cadeias 10
Um Breve Histórico Em 1980, M. O. Rabin e R. M. Karp desenvolveram um algoritmo tão simples quanto o de força bruta que roda virtualmente sempre em tempo proporcional a M + N Além disso, o algoritmo deles estende-se facilmente a padrões bidimensionais que o torna mais útil que os outros para processamento de figuras 11
Algoritmo Força-Bruta O método óbvio para casamento de padrão resume-se em testar, em cada posição do texto onde o padrão pode casar, se ele de fato casa O proceedimento forcabruta a seguir busca dessa maneira a primeira ocorrência do padrão p no texto t 12
Algoritmo Força-Bruta procedure ForcaBruta ( var T: TipoTexto ; var n : integer ; var P: TipoPadrao; var m : integer ) ; { Pesquisa P[ 1..m] em T[ 1..n] } var i, j, k : Integer ; begin for i := 1 to n m + 1 do begin k := i ; j := 1; while T[ k ] = P[ j ] do begin j := j + 1; k := k + 1; end; if j > m then writeln ( Casamento na posicao, i:3 ) ; end; end; 13
Algoritmo Força-Bruta 14
Algoritmo Força-Bruta Pior caso O pior caso ocorre quando, por exemplo, o padrão e o texto são os dois uma seqüência de zeros seguidos por um 1 Por exemplo, 00001 e 000000000000000001 Ou seja, quando é preciso percorrer praticamente todo P várias vezes a cada posição de T, estando P no fim da cadeia Complexidade: m*n 15
Algoritmo KMP A idéia básica do algoritmo desenvolvido por Knuth, Morris e Pratt é: quando um descasamento é detectado, o falso começo consiste em caracteres que já conhecemos de antemão (porque eles estão no padrão) De alguma forma, podemos levar vantagem desta informação ao invés de retroceder o índice j por todos os caracteres conhecidos 16
Algoritmo KMP Foi o primeiro algoritmo cujo pior caso tem complexidade de tempo linear no tamanho do texto: O(n) É um dos algoritmos mais famosos para resolver casamento de padrões Tem implementação complicada e na prática perde em eficiência para outros algoritmos, como o de Boyer. 17
Algoritmo KMP O algoritmo computa o sufixo mais longo no texto que é também o prefixo de P. Quando o comprimento do sufixo no texto é igual a P, ocorre um casamento. O pré-processamento de P permite que nenhum caractere seja reexaminado. O apontador para o texto nunca é decrementado. 18
Algoritmo KMP 19
Algoritmo KMP Porém, como saber quantas posições devem ser deslocadas? Para tal, utilizamos uma função chamada de Função Prefixo, denotada por π. Por enquanto iremos considerar que essa função é conhecida. 20
Função Prefixo Tendo P = ababaca contra um texto T. Em (a) sendo, q = 5, de caracteres que parearam com T. Conhecendo estes q caracteres do texto é possível determinar que alguns deslocamentos s são inválidos (não precisam ser testados). O deslocamento s = s + 1 é inválido, mas o deslocamento s = s + 2 é potencialmente válido pelo que conhecemos do texto. Dado que q caracteres tiveram comparações com sucesso no deslocamento s, o próximo potencial deslocamento válido será: s = s + (q π[q]) 21
Função Prefixo A função prefixo encapsula o conhecimento sobre quantas posições deve-se caminhar para continuar procedendo o casamento do padrão, evitando comparações inúteis. Para tal, a função analisa o padrão fornecido e cria uma tabela de deslocamentos. Idéia simples. 22
Função Prefixo Valores da função prefixo para P: P q π a b a b a c a 1 2 3 4 5 6 7 0 0 1 2 3 0 1 Outro exemplo: q 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 P b c b a b c b a e b c b a b c b a Prefix 0 0 1 0 1 2 3 4 0 1 2 3 4 5 6 7 8 23
Caso importante Considere o exemplo abaixo: i 0 1 2 3 4 5 6 7 8 9 10 P c g c g a g c g c g c Prefix 0 0 1 2 0 0 1 2 3 4? Qual o valor para Prefix(10)? Prefix(9)=4. Mas P(4) P(10). Podemos concluir que Prefix(10)=0? Não, não podemos. 24
Caso importante Existe um prefixo mais curto com tamanho 2 que é igual a um sufixo de P(0,9), e P(10)=P(2). Podemos concluir que Prefix(10)=2+1=3. i 0 1 2 3 4 5 6 7 8 9 10 P c g c g a g c g c g c Prefix 0 0 1 2 0 0 1 2 3 4? 25
Exemplo 26
Algoritmo KMP public static int KMPmatch(String text, String pattern) { int n = text.length(); int m = pattern.length(); int[] fail = prefixfunction(pattern); int i = 0; int j = 0; while (i < n) { if (pattern.charat(j) == text.charat(i)) { if (j == m - 1) return i - m + 1; // match i++; j++; } else if (j > 0) j = fail[j - 1]; else i++; } return -1; // no match } public static int[] prefixfunction(string pattern) { int[] fail = new int[pattern.length()]; fail[0] = 0; int m = pattern.length(); int j = 0; int i = 1; while (i < m) { if (pattern.charat(j) == pattern.charat(i)) { fail[i] = j + 1; i++; j++; } else if (j > 0) // j follows a matching prefix j = fail[j - 1]; else { // no match fail[i] = 0; i++; } } return fail; } 27
Algoritmo KMP Como são evitadas comparações em P, o algoritmo é executado em tempo polinomial. É feito um pré-processamento em P O(n) É feito um processamento em T O(m) Processamento total: O(n + m) 28
Exercício Considere o texto abaixo: ABACAABACCABACABAABB Pesquise o padrão ABACAB utilizando o método KMP. Diga quantas comparações o método faz. 29
Algoritmo BMH Também chamado de Boyer-Moore-Horspool. Pela extrema simplicidade de implementação e comprovada eficiência, o BMH deve ser escolhido em aplicações de uso geral que necessitam realizar casamento exato de cadeias. A idéia é pesquisar no padrão no sentido da direita para a esquerda, o que torna o algoritmo muito rápido. Executado frequentemente em editores de texto para os comandos de localizar" e "substituir". Veremos primeiro o algoritmo BM, o algoritmo original. 30
Algoritmo BM O algoritmo faz a varredura dos símbolos do padrão da direita para à esquerda (rightmost). O algoritmo utiliza duas funções pré-processadas para deslocar o padrão à direita. Estas funções dos deslocamentos são chamadas funções de ocorrência e de casamento. 31
Heurística de ocorrência Alinha a posição que causou a colisão no texto com o primeiro caractere no padrão que casa com este caractere; Ex.: P ={cacbac}, T ={aabcaccacbac}. 1 2 3 4 5 6 7 8 9 0 1 2 a a b c a c c a c b a c c a c b a c c a c b a c c a c b a c c a c b a c c a c b a c 32
Heurística de ocorrência A partir da posição 6, da direita para a esquerda, existe uma colisão na posição 4 de T, entre b do padrão e c do texto. Logo, o padrão deve ser deslocado para a direita até o primeiro caractere no padrão que casa com c. O processo é repetido até encontrar um casamento a partir da posição 7 de T. 33
Heurística do casamento Ao mover o padrão para a direita, faça-o casar com o pedaço do texto anteriormente casado. Ex.: P ={cacbac} no texto T ={aabcaccacbac}. 1 2 3 4 5 6 7 8 9 0 1 2 a a b c a c c a c b a c c a c b a c c a c b a c c a c b a c 34
Heurística do casamento Novamente, a partir da posição 6, da direita para a esquerda, existe uma colisão na posição 4 de T, entre o b do padrão e o c do texto. Neste caso, o padrão deve ser deslocado para a direita até casar com o pedaço do texto anteriormente casado, no caso ac, deslocando o padrão 3 posições à direita. O processo é repetido mais uma vez e o casamento entre P e T ocorre. 35
Escolha da heurística O algoritmo BM escolhe a heurística que provoca o maior deslocamento do padrão. Esta escolha implica em realizar uma comparação entre dois inteiros para cada caractere lido do texto, penalizando o desempenho do algoritmo com relação ao tempo de processamento. Várias propostas de simplificação ocorreram ao longo dos anos. As que produzem os melhores resultados são as que consideram apenas a heurística de ocorrência. 36
Algoritmo BMH A simplificação mais importante é devida a Horspool em 1980. Executa mais rápido do que o algoritmo BM original. Parte da observação de que qualquer caractere já lido do texto a partir do último deslocamento pode ser usado para endereçar a tabela de deslocamentos. Endereça a tabela com o caractere no texto correspondente ao último caractere do padrão. 37
Algoritmo BMH Tabela de Deslocamentos Para pré-computar o padrão o valor inicial de todas as entradas na tabela de deslocamentos é feito igual a m. A seguir, apenas os m 1 primeiros caracteres do padrão são usados para obter os outros valores da tabela. Formalmente, d[x] = min { j tal que j = m (1 j < m & P [m j] = x) }. Ex.: Para o padrão P ={teste}, os valores de d são d[t] = 1, d[e] = 3, d[s] = 2, e todos os outros valores são iguais ao valor de P, nesse caso m = 5. Para o padrão P={bola}, os valores de d são d[b] = 3, d[o] = 2, d[l] = 1, d[a] = 4. 38
Algoritmo BMH 39
Algoritmo BMH [e] 40
Algoritmo BMH { G C A T C G C A G A G A G T A T A C A G T A C G } 41
Algoritmo BMH achou! 42
Algoritmo BMH 43
Implementação procedure BMH ( var T: TipoTexto ; var n : integer ; var P: TipoPadrao ; var m: integer ) ; { Pesquisa P[ 1.. m] em T[ 1.. n] } var i, j, k : Integer ; d : array [ 0..MaxTamAlfabeto] of integer ; begin { Pre processamento do padrao } for j := 0 to MaxTamAlfabeto do d[ j ] : = m; for j := 0 to m-2 do d[ ord(p[ j ] ) ] : = m-j-1 ; i : = m; while i <= n do { Pesquisa } begin k := i ; j : = m; while (T[ k ] = P[ j ] ) and ( j >0) do begin k : = k 1; j : = j 1; end; if j = 0 then writeln ( Casamento na posicao :,k+1:3); i : = i + d[ ord(t[ i ] ) ] ; end; 44 end;
Exercício Considere o texto abaixo: ABACAABACCABACABAABB Pesquise o padrão ABACAB utilizando o método BMH. Diga quantas comparações o método faz. 45
Algoritmo BHMS Sunday (1990) apresentou outra simplificação importante para o algoritmo BM, ficando conhecida como BMHS. Variante do BMH: endereçar a tabela com o caractere no texto correspondente ao próximo caractere após o último caractere do padrão, em vez de deslocar o padrão usando o último caractere como no algoritmo BMH. Para pré-computar o padrão, o valor inicial de todas as entradas na tabela de deslocamentos é feito igual a m + 1. A seguir, os m primeiros caracteres do padrão são usados para obter os outros valores da tabela. Formalmente d[x] = min{j tal que j = m (1 j m & P [m + 1 j] = x)}. Exemplo: Para o padrão P = teste, os valores de d são d[t] = 2, d[e] = 1, d[s] = 3, e todos os outros valores são iguais ao valor de P + 1. 46
Implementação procedure BMHS ( var T: TipoTexto ; var n : integer ; var P: TipoPadrao ; var m: integer ) ; var i, j, k : Integer ; d : array [ 0.. MaxChar] of integer ; begin { Pre processamento do padrao } for j := 0 to MaxChar do d[ j ] : = m+1; for j := 0 to m-1 do d[ ord(p[ j ] ) ] : = m j ; i : = m; while i <= n do { Pesquisa } begin k := i ; j : = m; while (T[ k ] = P[ j ] ) and ( j >0) do begin k : = k 1; j : = j 1; end; if j = 0 then writeln ( Casamento na posicao :, k+1:3); i : = i + d[ ord(t[ i +1])]; end; end; 47
Implementação O pré-processamento do padrão ocorre nas duas primeiras linhas do código. A fase de pesquisa é constituída por um laço em que i varia de m até n, com incrementos d[ord(t[i+1])], o que equivale ao endereço na tabela d do caractere que está na i + 1-ésima posição no texto, a qual corresponde à posição do último caractere de P. 48
Exemplo 5 5 5 5 5 5 5 5 3 5 4 2 1 5 5 5 4 3 2 1 [e] 49
Exemplo Shift para [c] = 4 Shift para [c] = 4 match! 50
Algoritmo BMHS Embora normalmente utiliza-se a comparação do padrão seguindo da direita, ao contrário dos algoritmos de BM e BMH, o algoritmo de Sunday pode comparar o padrão de forma arbitrária. Caso saiba-se qual o caracter menos comum no padrão, ele pode ser utilizado primeiro na comparação 51
Exercício Considere o texto abaixo: ABACAABACCABACABAABB Pesquise o padrão ABACAB utilizando o método BMHS. Diga quantas comparações o método faz. 52
BM - Análise Os dois tipos de deslocamento (ocorrência e casamento) podem ser pré-computados com base apenas no padrão e no alfabeto. Assim, a complexidade de tempo e de espaço para esta fase é O(m + c). O pior caso do algoritmo é O(n + rm), onde r é igual ao número total de casamentos, o que torna o algoritmo ineficente quando o número de casamentos é grande. O melhor caso e o caso médio para o algoritmo é O(n/m), um resultado excelente pois executa em tempo sublinear. 53
BMH - Análise O deslocamento de ocorrência também pode ser pré-computado com base apenas no padrão e no alfabeto. A complexidade de tempo e de espaço para essa fase é O(c). Para a fase de pesquisa, o pior caso do algoritmo é O(nm), o melhor caso é O(n/m) e o caso esperado é O(n/m), se c não é pequeno e m não é muito grande. 54
BMHS - Análise Na variante BMHS, seu comportamento assintótico é igual ao do algoritmo BMH. Entretanto, os deslocamentos são mais longos (podendo ser iguais a m + 1), levando a saltos relativamente maiores para padrões curtos. Por exemplo, para um padrão de tamanho m = 1, o deslocamento é igual a 2m quando não há casamento. 55
Análise 56
Método Robin-Karp Princípio: tratar cada substring de tamanho M do texto como chave de uma tabela de dispersão padrão é encontrado quando a chave da substring coincide com a do padrão como se procura chave específica, não é preciso guardar a tabela, mas apenas calcular as chaves com tabela virtual: tamanho elevado reduz probabilidade de falsas escolhas Para que o método seja eficaz cálculo da chave deve ser menos pesado que fazer as comparações função de dispersão: h(k) = k mod q q primo grande cálculo das chaves: chave da posição i usa a da posição i-1 M caracteres transformam-se em número: empacotados na máquina como palavra, e esta interpretada como número 57
Pré-processamento do padrão = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} = 10 P temos: 1 9 9 1 (P[1] * 10 + P[2]) = 19 (19 * 10) + P[3] = 199 (199 * 10) + P[4] = 1991 = alfabeto = tamanho de Dado um caractere, a representação numérica deste será sua posição no alfabeto Complexidade O(m) Generalizando: P[m] + (P[m-1]+ (P[m-2] +... + (P[2] + P[1]) )) 58
Processamento do texto Padrão P 1 9 9 1 Texto T 1 8 8 7 1 9 9 1 2 0 0 0 5 1 8 8 7 = 1887 tempo O(m), para s = 0 8 8 7 1 = 8871 não usaremos tempo O(m) para s > 0, pois temos que: s = 0, temos O(m) s > 0, temos O(1) s variando de 0 à n m para calcular m - 1 temos O(lg m) = O(m) + (n m)o(1) + O(lg m) = O(n) (1887 m - 1 * P[1]) * + P[s+m] = 8871, onde m - 1 foi previamente calculado Portanto temos tempo O(1), para cada deslocamento s > 0 59
Método Robin-Karp Aplicando a idéia para caracteres: Atribuir números a cada letra (código ASCII, por exemplo) Aplicar hashing para a cadeia S[1,..,m] na base b (número primo) Por exemplo, considerando a base como 101 (a base pode ser o tamanho do alfabeto): Palavra hi =104 101 1 + 105 101 0 =10609 60
Método Robin-Karp Os valores das transformações de P e das substrings de T são muito grandes, quando m e são muito longos Solução 1: reduzir esses valores a uma faixa controlada, utilizando módulo de um número q,, por exemplo. Novo problema: um mesmo valor pode representar substrings distintas. Solução 2: ocorrendo um provável casamento de ocorrendo um provável casamento de P com uma substring X de T, cada caractere de P deve ser comparado a cada caractere de X, para verificar se o casamento realmente acontece. 61
Método Robin-Karp Implementação Calcular hashing(padrao) Calcular hashing(substrings(texto)) Opção: Guardar em uma tabela Pesquisar hashing(padrao) na tabela Se não encontrar, padrão não existe no texto Se encontrar, Verificar se Substring(TEXTO) = PADRAO Se for diferente, padrão não existe no texto 62
Método Robin-Karp Usado para descobrir casos de plágio Análise: - custo para pré-processamento do padrão P é O(m) - custo para processamento do texto T é O(n) - número máximo de deslocamentos s válidos é n m + 1 No pior caso: - Todas as substrings X de T casam com P Sabemos que o número de deslocamentos s válidos é n m + 1, então temos s possíveis X, sabemos também que X = P = m, é possível concluir então que para cada s faremos m comparações, então: O((n-m+1)m). 63
Exercício Aplique o método de Robin-Karp para criar a tabela de hashing do texto abaixo, considerando um tamanho 3 de caracteres pro hashing e um alfabeto de tamanho 27, onde a letra A é denotada pelo número 1, B pela número 2, C como 3, D como 4, etc... Texto = ABCAABAC Como você procuraria a existência do padrão ABA no texto? 64