Distância Levenshtein
Em teoria da informação, a distância Levenshtein ou distância de edição entre dois "strings" (duas sequências de caracteres) é dada pelo número mínimo de operações necessárias para transformar um string no outro. Entendemos por "operações" a inserção, deleção ou substituição de um carácter. O nome advém do cientista russo Vladimir Levenshtein, que considerou esta distância já em 1965. É muito útil para aplicações que precisam determinar quão semelhantes dois strings são, como é por exemplo o caso com os verificadores ortográficos.
Exemplo
[editar | editar código-fonte]Por exemplo, a distância Levenshtein entre as palavras inglesas "kitten" (gato) e "sitting" (sentando-se) é 3, já que com apenas 3 edições conseguimos transformar uma palavra na outra, e não há maneira de o fazer com menos de três edições:
- kitten
- sitten (substituição de 'k' por 's')
- sittin (substituição de 'e' por 'i')
- sitting (inserção de 'g' no final)
A distância de Levenshtein pode ser considerada como uma generalização da Distância de Hamming, usada para strings com o mesmo tamanho, a qual só considera edições por substituição. Há também outras generalizações da distância Levenshtein que consideram, por exemplo, a troca de dois caracteres como uma aplicação.
O algoritmo
[editar | editar código-fonte]Um algoritmo bottom-up de programação dinâmica usado frequentemente para calcular a distância Levenshtein usa uma matriz (n + 1) × (m + 1), na qual n e m são o número de caracteres dos dois strings. Aqui um pseudocódigo para uma função LevenshteinDistance que usa dois strings, str1 de comprimento lenStr1, e str2 de comprimento lenStr2, e calcula a distância Levenshtein entre eles:
Função LevenshteinDistance(Caracter : str1[1..lenStr1], Caracter : str2[1..lenStr2]) : INTEIRO Início // tab é uma tabela com lenStr1+1 linhas e lenStr2+1 colunas Inteiro: tab[0..lenStr1, 0..lenStr2] // X e Y são usados para iterar str1 e str2 Inteiro: X, Y, cost Para X de 0 até lenStr1 tab[X, 0] ← X Para Y de 0 até lenStr2 tab[0, Y] ← Y Para X de 1 até lenStr1 Para Y de 1 até lenStr2 Se str1[X] = str2[Y] Então cost ← 0 Se-Não cost ← 1 // Custo da substituição deve ser 1, deleção e inserção tab[X, Y] := menor( tab[X-1, Y ] + 1, // Deletar tab[X , Y-1] + 1, // Inserir tab[X-1, Y-1] + cost // Substituir ) LevenshteinDistance ← tab[lenStr1, lenStr2] Fim
A constante mantida ao longo do algoritmo é que podemos transformar o segmento inicial str1[1..X]
em str2[1..Y]
usando um mínimo de operações tab[X,Y]
. No final, o elemento no fundo ao lado direito do array contém a resposta.
Melhoramentos possíveis
[editar | editar código-fonte]Melhoramentos possíveis para este algoritmo poderiam ser por exemplo:
- Podemos adaptar o algoritmo para usar menos espaço, O(m) em vez de O(mn), já que ele apenas requer que a linha anterior e a linha actual sejam armazenadas ao mesmo tempo.
- Podemos armazenar o número de inserções, deleções e substituições separadamente, ou mesmo as posições em que elas ocorrem, que são sempre
Y
. - Podemos dar diferentes penalidades de custo à inserção, deleção e substituição.
- A inicialização do
tab[i,0]
pode passar para dentro do grande loop principal exterior. - Este algoritmo paraleliza de uma forma pouco eficiente, devido a grande número de dependências de dados. No entanto, todo o
custo
pode ser calculado em paralelo, e o algoritmo pode ser adaptado para perfazer a funçãomínimo
em fases para eliminar dependências.
Prova de sucesso
[editar | editar código-fonte]Como foi mencionado, a constante é que podemos transformar o segmento inicial s[1..X]
em t[1..Y]
usando um mínimo de tab[X,Y]
operações. Esta constante é verdadeira já que:
- É inicialmente verdadeira na linha e colunas 0 porque
s[1..X]
pode ser transformado num string vaziot[1..0]
por simplesmente apagando todos osX
caracteres. Do mesmo modo, podemos transformars[1..0]
emt[1..Y]
ao simplesmente adicionando todos os caracteresY
. - O mínimo é tomado em três distâncias, sendo em qualquer das quais possível que:
- Se podemos transformar
s[1..X]
at[1..Y-1]
emk
operações, então nós podemos simplesmente adicionart[Y]
depois para obtert[1..Y]
emk+1
operações. - Se podemos transformar
s[1..X-1]
at[1..Y]
emk
operações, então nós podemos fazer as mesmas operações ems[1..X]
e depois remover os[X]
original ao fim dek+1
operações. - Se podemos transformar
s[1..X-1]
at[1..Y-1]
emk
operações, então podemos fazer o mesmo coms[1..X]
e depois fazer uma substituição det[Y]
pelass[X]
originais no final, se necessário, requerindok+cost
operações.
- Se podemos transformar
- As operações requiridas para transformar
s[1..n]
emt[1..m]
é o número necessário para transformar todos oss
em todos ost
, e logotab[n,m]
contém o nosso resultado desejado.
Esta prova não confirma que o número colocado em tab[X,Y]
seja de facto o mínimo; isso é mais difícil de provar e exige um argumento Reductio ad absurdum no qual assumimos que tab[X,Y]
é menor que o mínimo dos três, e usamos isto para mostrar que um dos três não é mínimo.
Limites superior e inferior
[editar | editar código-fonte]A distância Levenshtein tem vários limites superior e inferior simples que são úteis em aplicações que calculam vários deles e os comparam. Estes incluem:
- É sempre pelo menos igual à diferença dos tamanhos dos dois strings.
- É no máximo igual ao tamanho do string mais longo.
- É zero se e só se os strings são idênticos.
- Se os strings têm o mesmo tamanho, a distância de Hamming é um limite superior da distância Levenshtein.
- Se os strings são chamados
s
et
, o número de caracteres (não contando os duplicados) que encontramos ems
mas não emt
é um limite inferior.
Links (em português, inglês e alemão)
[editar | editar código-fonte]- Levenshtein distance - Rosetta Code - Implementações do algoritmo em diversas linguagens de programação
- O fantástico mundo da distância de edição (um survey em formato PDF sobre distância de edição)
- Levenshtein Distance, in Three Flavors, by Michael Gilleland
- NIST's Dictionary of Algorithms and Data Structures: Levenshtein Distance
- CSE 590BI, Winter 1996 Algorithms in Molecular Biology[ligação inativa] The algorithms from lectures 2, 3 and 4 are based on the Levenshtein distance but implement a different scoring function. The Haskell example was based on these notes.
- Levenshtein Distance - visualized
- Distance between strings - Levenshtein and Hamming
- Another Edit Distance Visualization (very fast)
- Wie funktioniert der Levenshtein-Algorithmus…?