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.