Jak działa sam algorytm:
- ustalamy długość łańcuchów znaków (dlugoscP - długość łańcucha pierwszego, dlugoscD - długość łańcucha drugiego),
- tworzymy macierz o rozmiarze (dlugoscP+1) x (dlugoscD+1),
- inicjalizujemy pierwszy wiersz wartościami od 0 do dlugoscP,
- inicjalizujemy pierwszą kolumnę wartościami od 0 do dlugoscD,
- sprawdzamy każdy znak z łańcucha pierwszego (indeks i od 1 do dlugoscP),
- sprawdzamy każdy znak z łańcucha drugiego (indeksy j od 1 do dlugoscD),
- jeżeli znak na pozycji i równa się znakowi na pozycji j to koszt jest równy zero,
- jeżeli znak na pozycji i jest różny od znaku na pozycji j to koszt wynosi 1,
- ustawiamy wartość komórki i,j jako minimum:
- komórka powyżej + 1,
- komórka z lewej + 1,
- komórka po skosie (góra, lewo) + koszt.
- algorytm powtarzamy dla wszystkich znaków, całkowity koszt otrzymamy w komórce o indeksie dlugoscP, dlugoscD
Sprawdźmy działanie algorytmu na przykładzie dwóch łańcuchów: "notatnik" i "kodatnik". Poniżej utworzona została macierz i obliczone zostały odpowiednie wartości komórek.
n o t a t n i k
0 1 2 3 4 5 6 7 8
k 1 1 2 3 4 5 6 7 7
o 2 2 1 2 3 4 5 6 7
d 3 3 2 2 3 4 5 6 7
a 4 4 3 3 2 3 4 5 6
t 5 5 4 3 3 2 3 4 5
n 6 5 5 4 4 3 2 3 4
i 7 6 6 5 5 4 3 2 3
k 8 7 7 6 6 5 4 3 2
Odczytana odległość edycyjna wynosi zatem 2 (wartość komórki o indeksie [8][8]). Dodatkowo możemy określić podobieństwo wyrazów stosując następujący wzór:
1 / (1 + OL)
gdzie OL to odległość Levenshteina dla dwóch łańcuchów obliczona zgodnie z powyższym algorytmem. Dla naszego przykładu podobieństwo wynosi: 1/3.
Poniżej prosty program liczący odległość Levenshteina oraz podobieństwo łańcuchów.
01.
import
java.util.Scanner;
02.
03.
/**
04.
* Obliczanie odległości Levenshteina (odległości edycyjnej)
05.
* Wyznaczanie podobieństwa łancuchów znaków
06.
* @author kodatnik.blogspot.com
07.
*/
08.
public
class
Levenshtein {
09.
10.
public
static
int
obliczOdleglosc(String p, String d) {
11.
// określamy długości łańcuchów znaków
12.
int
dlugoscP = p.length();
13.
int
dlugoscD = d.length();
14.
15.
// tworzymy odpowiednią macierz
16.
int
[][] macierz =
new
int
[dlugoscP +
1
][dlugoscD +
1
];
17.
18.
// uzupełniamy pierwszy wiersz i pierwszą kolumnę
19.
for
(
int
i =
0
; i <= dlugoscP; macierz[i][
0
] = i++);
20.
for
(
int
j =
1
; j <= dlugoscD; macierz[
0
][j] = j++);
21.
22.
// sprawdzamy poszczególne znaki
23.
for
(
int
i =
1
; i <= dlugoscP; i++) {
24.
char
znak = p.charAt(i -
1
);
25.
for
(
int
j =
1
; j <= dlugoscD; j++) {
26.
// obliczamy koszt
27.
int
koszt = macierz[i -
1
][j -
1
];
28.
if
(d.charAt(j -
1
) != znak ) koszt++;
29.
// wstawiamy minimum
30.
macierz[i][j] = Math.min( Math.min( macierz[i -
1
][j] +
1
, macierz[i][j -
1
] +
1
), koszt);
31.
}
32.
}
33.
// zwracamy całkowity koszt, odległość Levenshteina
34.
return
macierz[dlugoscP][dlugoscD];
35.
}
36.
37.
public
static
double
obliczPodobienstwo(String lancuchPierwszy, String lancuchDrugi) {
38.
// obliczamy i zwracamy podobieństwo łańcuchów
39.
return
(
1.0
/(
1.0
+ obliczOdleglosc(lancuchPierwszy, lancuchDrugi)));
40.
}
41.
42.
public
static
void
main(String[] args) {
43.
String lancuchPierwszy, lancuchDrugi;
44.
45.
// pobieramy dane
46.
Scanner wejscie =
new
Scanner(System.in);
47.
System.out.print(
"Podaj pierwszy ciąg znaków: "
);
48.
lancuchPierwszy = wejscie.nextLine();
49.
System.out.print(
"Podaj drugi ciąg znaków: "
);
50.
lancuchDrugi = wejscie.nextLine();
51.
52.
// wyświetlamy obliczone wyniki
53.
System.out.println(
"Odległość Levenshteina: "
+ obliczOdleglosc(lancuchPierwszy, lancuchDrugi));
54.
System.out.println(
"Podobieństwo ciągów znaków: "
+ obliczPodobienstwo(lancuchPierwszy, lancuchDrugi));
55.
}
56.
}
Podaj pierwszy ciąg znaków: notatnik Podaj drugi ciąg znaków: kodatnik Odległość Levenshteina: 2 Podobieństwo ciągów znaków: 0.3333333333333333
Praktyczne wykorzystanie algorytmu to między innymi sprawdzanie pisownii, rozpoznawanie mowy, analiza DNA, wykrywanie plagiatów itp.
3 Komentarze - Odległość Levenshteina - podobieństwo łańcuchów
Dobrze wiedzieć o istnieniu tego algorytmu. Jednak czy zamiast własnej implementacji nie prościej skorzystać z metody StringUtils#getLevenshteinDistance, dostępnej w pakiecie Commons Lang od 8 lat?
@TN: Oczywiście masz rację....ale znając algorytm i kawałek kodu możemy wprowadzić na przykład inne wagi dla poszczególnych operacji usuwania, wstawiania, zamiany. Większość kodów tu dostępnych ma swoje odpowiedniki w postaci gotowych klas...ale warto czasami zrozumieć samą ideę :)
wartosciowa wiedza
Prześlij komentarz
Możesz użyć niektórych tagów HTML, takich jak <b>, <i>, <u>, <a> Nie spamuj :)