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.