Czestina LZ 78 TNT's HomePage Algoritmy komprese dat

LZ 78

Kompresory založené na tomto principu postupně přidávají zapisované řetězce do slovníku a pokud ve vstupním souboru najdou nějaký řetězec, který již je ve slovníku, nahradí ho odkazem do tohoto slovníku. Dále je popsán algoritmus LZW pocházející od Terry Welsche z roku 1984, původně určený k hardwarové implementaci do diskových řadičů.

Výhodou této metody je velmi dobrý kompresní poměr (zvláště při aplikaci některé statistické metody na výsledný kód), rychlá komprese i dekomprese a nevelké nároky na pamět.

Komprese

Algoritmus začíná s prázdným slovníkem a řetězcem L obsahujícím první znak zdrojového souboru. Vždy po přečtení dalšího znaku c zjistí, jestli se řetězec L+c vyskytuje ve slovníku. Pokud ano, pouze prodlouží řetězec L o znak c, jinak zapíše např. 12-ti bitový odkaz do slovníku nebo (pokud řetězec L obsahuje jediný znak) pouze znak, ale opět 12-ti bitově. V tomto případě tedy čísla 0-255 jsou jednotlivé znaky a čísla 256-4095 indexy do slovníku. Pokud se slovník zaplní dříve než je přečten celý soubor, je celý slovník smazán a začíná se plnit znovu. Někdy se pro zlepšení účinnosti místo smazání slovníku používá algoritmus LRU (last-recently-used) pro odstranění nepoužívaných řetězců ze slovníku. Vzhledem k tomu, že slovník se plní od nejnižších pozic, je pro zlepšení kompresního poměru výhodné začít kódy ukládat pouze 9-ti bitově a teprve když se slovník více zaplní, postupně je rozšiřovat až na těch např. 12 bitů. Slovník dekompresoru se totiž plní stejně a tak dekompresor snadno pozná, kdy začít kódy číst 10-ti bitově.
L = readChar
while (!eof(input)) {
  c = readChar
  if (Lc ve slovníku)
    L = Lc
  else {
    zapsat kód pro řetězec L
    přidat Lc do slovníku
    L = c
  }
}

Příklad:
Vstup: WEB-WEB-WEB!

Řetězec L    Přečtený znak    Výstup    Nová položka ve slovníku
                 W
   W             E              W       (256) = WE
   E             B              E       (257) = EB
   B             -              B       (258) = B-
   -             W              -       (259) = -W
   W             E            Řetězec WE již je ve slovníku
   WE            B            (256)     (260) = WEB
   B             -
   B-            W            (258)     (261) = B-W
   W             E
   WE            B
   WEB           !            (260)     (262) = WEB!
   !           (eof)            !

Demonstrační Java Applet

Při kompresi je tedy velmi často nutné zjistit, jestli nějaký řetězec již ve slovníku je, případně jeho kód. K tomuto účelu by se hodil strom nebo hash-table: např. z kódu řetězce a přidávaného znaku spočteme index do tabulky obsahující kód řetězce, přidávaný znak a kód nového řetězce. Pokud první dvě položky souhlasí, máme kód celého řetězce, jinak jsme na špatné adrese a hledáme někde jinde (např. o několik řádků dál), nebo je tento řádek tabulky prázdný (zjistíme podle speciální hodnoty), celý řetězec tedy není ve slovníku a navíc máme místo, kam ho uložit.

Dekomprese

Dekompresor postupně čte kódy, zapisuje jim příslušející řetězce a přidává nové řetězce do slovníku. Do slovníku je vždy přidán řetězec reprezentovaný předcházejícím kódem a první znak z řetězce s aktuálním kódem. V případě, že byl přečten kód, kterému ještě nebyl přiřazen řetězec (to se stane např. pokud původní text obsahoval několik stejných znaků za sebou), je do slovníku přidán řetězec skládající se z předcházejícího řetězce a jeho posledního znaku. V průběhu dekomprese má dekompresor stejný slovník jako kompresor.
read last
write last
while (!eof(input)) {
  read code
  write řetězec table[code] (nebo znak code)
  přidání řetězce table[last]+prvni znak table[code] do slovníku
  last = code
}

Tabulku řetězců lze přímo indexovat přečteným kódem (pokud je větší než 0xFF, jinak jde o jediný znak a ten stačí zapsat na výstup). Stačí, když tabulka bude obsahovat kód prefixu a nově přidaný znak. Postupným procházením tabulky podle kódu prefixu tak dostaneme odzadu celý řetězec reprezentovaný přečteným kódem.

Problém nastane, pokud byl přečten kód řetězce, který ještě není v tabulce. To se opravdu stát může: při kompresi zjistíme, že řetězec (cL)c ještě není ve slovníku, proto ho přidáme a jako začátek nového řetězce vezmeme c, pokud na vstupu následuje Lcx, zapíšeme kód pro cLc a přidáme (cLc)x do slovníku. Při dekompresi přečteme kód pro cLc (který ještě není v tabulce) a ... Teď již je vidět, že stačí, když do tabulky přidáme frázi skládající se z předcházejícího řetězce (last) a jeho prvního znaku, celou tuto frázi zapíšeme na výstup a jako nový řetězec last vezmeme jeho první znak.


Demonstrační program v C
Last change Mar 24 1997 by TNT