Levenštejnova vzdálenost
Levenštejnova vzdálenost (také nazývána editační vzdálenost) je metrika zavedená v roce 1965 ruským matematikem Vladimirem Iosifovičem Levenštejnem pro měření editační vzdálenosti v prostoru textových řetězců. Připouští tři jednoduché editační operace, totiž přidání libovolného znaku, vypuštění libovolného znaku nebo záměnu libovolného znaku za jiný libovolný. Nejmenší počet těchto operací nutný k převedení jednoho řetězce na druhý udává Levenštejnovu vzdálenost mezi nimi.
Příklady:
| řetězec 1 | řetězec 2 | vzdál. | poznámka |
|---|---|---|---|
| aaa | abc | 2 | dvě záměny |
| aaa | aaaa | 1 | jedno přidání |
| aaa | abaa | 1 | jedno přidání (uvnitř) |
| xxx | yyyy | 4 | tři záměny a jedno přidání |
| hodinky | holinky | 1 | jedna záměna |
Typický příklad použití Levenštejnovy vzdálenosti je v algoritmech korektorů překlepů.
Definice
Levenštejnovu vzdálenost dvou řetězců znaků , (s délkou a ) lze rekurzivně definovat jako , přičemž
Přičemž znamená první znak v řetězci, zatímco znamená zbytek řetězce bez prvního znaku.
V podstatě se postupně prochází oba řetězce od začátku do konce a zjišťuje se, jestli se řetězce liší, protože došlo ke změně znaku nebo k odebrání nebo k přidání znaku. Přičemž se preferuje menší počet úprav.
Základní vlastnosti
Pro Levenštejnovu vzdálenost dvou řetězců platí následující vlastnosti:
- Vždy je menší nebo rovna počtu znaků v delším řetězci.
- Vždy je větší nebo rovna rozdílu délek porovnávaných řetězců.
- Je nula, právě když jsou oba řetězce stejné.
- Pro dva stejně dlouhé řetězce je vždy menší nebo rovna Hammingově vzdálenosti.
- Vždy je menší nebo rovna součtu vzdáleností každého z těchto řetezců a třetího řetězce.
Nerekurzivní výpočet
Výše uvedený postup lze poměrně snadno naprogramovat s využitím rekurze, což ale nemusí být vždy vhodné, protože opakované počítaní vzdálenosti stejných podřetězců vede na exponenciální výpočetní náročnost.
Existuje také nerekurzivní Wagnerův-Fisherův algoritmus využívající matici (resp. dvourozměrné pole) o rozměrech o jedna delších než je délka porovnávaných řetězců. Do tohoto pole se zapisuje, o kolik se liší zatím zpracované úseky řetězců. Algoritmus má zhruba kvadratickou výpočetní a paměťovou náročnost.
Následující pseudo-program předpokládá indexování řetězců od 1 (jako např. v Pascalu) a indexování pole od 0.
function int lev(string a, string b):
// Délky vstupních řetězců
int m = length(a)
int n = length(b)
// Připrava matice
int d[0..m, 0..n]
for int i = 0 to m:
d[i, 0] = i // Rozdíl, kdyby byl druhý řetězec prázdný
for int j = 0 to n:
d[0, j] = j // Rozdíl, kdyby byl první řetězec prázdný
// Vlastní výpočet
for int i = 1 to m:
for int j = 1 to n:
int edit = (a[i] == b[j] ? 0 : 1) // Možný rozdíl znaků
d[i, j] = min(
d[i-1, j] + 1, // V prvním řetězci je znak navíc
d[i, j-1] + 1, // Ve druhém řetězci je znak navíc
d[i-1, j-1] + edit
)
return d[m, n]
Hodnota d[i,j] udává, o kolik se navzájem liší podřetězce od začátku až do pozice a[i] a b[j].
Vzhledem k tomu, že při počítání prvků na řádku i jsou potřeba pouze hodnoty ze dvou řádků i-1 a i, lze program upravit na lineární paměťovou náročnost. (Což byla v podstatě jediná výhoda rekurzívního algoritmu.)
Reference
V tomto článku byl použit překlad textu z článku Levenshtein-Distanz na německé Wikipedii.