LZ77

Lempel-Ziv 77, skracane zwykle do LZ77 (algorytm LZ77) – metoda strumieniowej słownikowej kompresji danych. Metoda LZ77 wykorzystuje fakt, że w danych powtarzają się ciągi bajtów (np. w tekstach naturalnych będą to słowa, frazy lub całe zdania) – kompresja polega na zastępowaniu powtórzonych ciągów o wiele krótszymi liczbami wskazującymi, kiedy wcześniej wystąpił ciąg i z ilu bajtów się składał; z punktu widzenia człowieka jest to informacja postaci „taki sam ciąg o długości 15 znaków wystąpił 213 znaków wcześniej”.

Algorytm LZ77 jest wolny od wszelkich patentów, co w dużej mierze przyczyniło się do jego popularności i szerokiego rozpowszechnienia. Doczekał się wielu ulepszeń i modyfikacji, dających lepsze współczynniki kompresji albo dużą szybkość działania. Na LZ77 opiera się m.in. algorytm deflate, używany jest również w formatach ZIP, gzip, ARJ, RAR, PKZIP, a także PNG.

Algorytm został opracowany w 1977 przez Abrahama Lempela i Ja’akowa Ziwa i opisany w artykule A universal algorithm for sequential data compression opublikowanym w IEEE Transactions on Information Theory (s. 8–19). Rok później autorzy opublikowali ulepszoną wersję metody, znaną pod nazwą LZ78.

Organizacja IEEE uznała algorytm Lempel-Ziv za kamień milowy w rozwoju elektroniki i informatyki.

Zasada działania 
W LZ77 zapamiętywana jest w słowniku pewna liczba ostatnio kodowanych danych – przeciętnie kilka do kilkudziesięciu kilobajtów. Jeśli jakiś ciąg powtórzy się, to zostanie zastąpiony przez liczby określające jego pozycję w słowniku oraz długość ciągu; do zapamiętania tych dwóch liczb trzeba przeznaczyć zazwyczaj o wiele mniej bitów niż do zapamiętania zastępowanego ciągu.

Metoda LZ77 zakłada, że ciągi powtarzają się w miarę często, tzn. na tyle często, żeby wcześniejsze wystąpienia można było zlokalizować w słowniku – ciągi powtarzające się zbyt rzadko nie są brane pod uwagę. Wady tej pozbawiona jest metoda LZ78, w której – przynajmniej teoretycznie – słownik rozszerza się w nieskończoność.

Bardzo dużą zaletą kodowania LZ77 (a także innych metod słownikowych z rodziny LZ, tj. LZSS, LZ78, LZW itp.) jest to, że słownika nie trzeba zapamiętywać i przesyłać wraz z komunikatem – zawartość słownika będzie na bieżąco odtwarzana przez dekoder.

Algorytm kompresji jest bardziej złożony i trudniejszy w realizacji niż algorytm dekompresji. W metodzie LZ77 można wpływać na prędkość kompresji oraz zapotrzebowania pamięciowe, regulując parametry kodera (rozmiar słownika i bufora kodowania).

Algorytm kompresji 
Metoda LZ77 korzysta z bufora (okna), który logicznie podzielony jest na dwie części:
 bufor słownikowy (słownik), przechowujący  ostatnio przetwarzanych symboli (sufiks);
 bufor wejściowy (lub bufor kodowania), przechowujący  symboli do zakodowania.
Bufor słownikowy obejmuje indeksy  bufor wejściowy 

Rozmiary  i  mogą być dowolne, w praktyce dobiera się je tak, aby były całkowitą potęgą dwójki, dzięki czemu w pełni wykorzystywane są słowa bitowe przeznaczone do ich zapamiętania. Rozmiar bufora słownikowego wynosi w praktyce kilka-kilkadziesiąt kilobajtów, bufor kodowania jest dużo mniejszy. Na przykład w programie gzip słownik ma 32 kB, bufor kodowania natomiast 258 bajtów.

Algorytm przebiega następująco:
 Bufor słownikowy jest wypełniany pierwszym symbolem wejściowym i ten symbol jest zapisywany na wyjście.
 Do bufora wejściowego wstawiane jest  pierwszych symboli wejściowych.
 Dopóki w buforze wejściowym są jakieś dane:
 W obrębie bufora słownikowego wyszukiwany jest najdłuższy podciąg równy początkowi bufora wejściowego (najdłuższy prefiks bufora kodowania). Wynikiem wyszukiwania jest indeks  początku wyszukanego podciągu oraz jego długość  ograniczona z góry przez  Część podciągu może być wspólna z buforem wejściowym!Jeśli podciągu nie uda się znaleźć, to  może mieć dowolną wartość, natomiast 
 Na wyjście wyprowadzana jest trójka (  symbol  z bufora wejściowego następujący po dopasowanym podciągu).
 Okno (bufor słownikowy + bufor wejściowy) przesuwane jest w lewo o  pozycji i na koniec bufora dopisywane jest tyleż kolejnych symboli wejściowych (o ile jeszcze są jakieś).

Stopień kompresji LZ77 w dużej mierze zależy od długości słownika oraz długości bufora wejściowego (bufora kodowania). Programy wykorzystujące tę metodę mają zwykle możliwość ustalania rozmiaru słownika, istnieją również programy dynamicznie zmieniające rozmiar słownika.

Prędkość kompresji natomiast jest uzależniona głównie od efektywności procedury wyszukującej najdłuższy prefiks. Używane są tutaj różne rozwiązania. Np. w programie gzip używa się tablicy haszującej adresowanej pierwszymi trzema literami z bufora kodowania. Stosowane są również zwykłe tablice, a do lokalizacji prefiksu używa się algorytmów wyszukiwania wzorca, np. algorytmu Karpa-Rabina.

Jeśli słownik jest realizowany jako tablica, to aby uniknąć fizycznego, czasochłonnego przesuwania danych w oknie (punkt 3. algorytmu), używa się buforów cyklicznych.

Przykładowy krok algorytmu 
1. Wyszukanie najdłuższego podciągu równego początkowi bufora wejściowego (tutaj: aac).
           słownik               |  bufor wejściowy  | nieprzetworzone symbole
   0   1   2   3   4   5   6   7 | 0   1   2   3   4 |
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-----
 | a | a |  |  |  | c | a | b |  |  |  |  | a | c | a | b | a | c | ...
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-----
 |                      okno                         |

2. Wynik wyszukiwania:
 P = 2 (pozycja w słowniku)
 C = 3 (długość podciągu)
 S =  (symbol z bufora wejściowego następujący po dopasowanym podciągu)

3. Przesunięcie bufora o  pozycji, dopisanie do bufora wejściowego nieprzetworzonych symboli.
           słownik               |  bufor wejściowy  | nieprzetworzone symbole
   0   1   2   3   4   5   6   7 | 0   1   2   3   4 |
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---------------------
 |  | c | a | b |  |  |  |  | a | c | a | b | a | c | ...
 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---------------------

Przykład

Algorytm dekompresji 
Dekompresja danych w metodzie LZ77 jest o wiele prostsza i szybsza niż kompresja. Do zdekodowania wymagane jest istnienie bufora (okna) o dokładnie takich samych parametrach jak przy kodowaniu – bufor podzielony jest na część słownikową obejmującą  pierwszych pozycji i bufor wyjściowy zajmujący  kolejnych pozycji.

Dekodowanie przebiega następująco:
 Bufor słownikowy jest wypełniany początkowym symbolem
 Dla wszystkich trójek () wykonuj:
 Skopiuj z bufora słownikowego do bufora wyjściowego symbole z zakresu 
 Doklej na koniec bufora wyjściowego symbol 
 Na wyjście wyprowadź  początkowych symboli z bufora wyjściowego.
 Przesuń zawartość bufora o  pozycji w lewo.

Ad 1) Jeśli przy kompresji podciąg obejmował także bufor wejściowy, to należy bufor słownikowy tymczasowo „przedłużyć” i wypełnić brakującymi symbolami. Podciągi takie mają strukturę xYxYx, gdzie x to pojedynczy symbol, a Y ciąg znajdujący się już w buforze słownikowym. Uzupełnienie polega na cyklicznym kopiowaniu końcówki bufora słownikowego (którą obejmuje początek podciągu). Np. jeśli podciąg ma długość 8 i tylko 3 symbole bca znajdują się w słowniku, to odtworzony ciąg ma postać: bcabcabc. Nie ma potrzeby fizycznego rozszerzania słownika i kopiowania danych – wystarczy odpowiednio przeliczać indeksy.

Tego specjalnego przypadku można uniknąć na etapie kompresji, biorąc pod uwagę tylko te podciągi, które w całości znajdują się w słowniku – przy dużych rozmiarach słownika i niewielkich bufora wejściowego praktycznie nie rzutuje to na stopień kompresji i jest to rozwiązanie stosowane w rzeczywistych zastosowaniach.

Modyfikacja algorytmu 
Algorytm LZ77 stał się podstawą dla wielu rozwiązań:

 LZR (1981) – słownik o nieograniczonej wielkości (indeksy do słownika kodowane kodem omega Eliasa)
 LZSS (1982) – słowa kodowe dwojakiego rodzaju: pojedynczy symbol albo para: (pozycja dopasowania, długość dopasowania)
 LZB (1987) – wariant LZSS, w którym zarówno odległość dopasowania, jak i jego długość nie są ograniczone (długość kodowana kodem gamma Eliasa)
 LZH (1987) – zarówno indeks, jak i długość dopasowania kodowane kodem Huffmana
 LZS – algorytm używany w komercyjnym programie Stacker
 LZO.

Przykład kompresji 
Niech długość słownika i bufora wejściowego będą równe 4, tzn.  Do zapisu pozycji w słowniku (P), jak i długości ciągu (C) wystarczą 2 bity. Kodowany komunikat składa się z trzynastu symboli: aabbcabbcdddc.

Zakodowanych zostało 13 symboli; symbole to przeważnie bajty (8 bitów), więc cały komunikat  bity.

Dane wyjściowe kodera to:
 początkowy symbol – 8 bitów,
 pięć trójek; każda trójka zajmuje 12 bitów (2 bity na indeks podciągu, 2 na jego długość i 8 bitów na symbol).

Ostatecznie rozmiar skompresowanych danych wynosi 68 bitów, co daje współczynnik kompresji ok. 34%.

Przykład dekompresji 
Dekompresji zostaną poddane dane otrzymane we wcześniejszym przykładzie:
 symbol początkowy a;
 trójki: (0,2,b), (3,1,c), (0,3,c), (0,0,d), (3,2,c).

Ostatecznie zdekodowany komunikat ma postać: aabbcabbcdddc.

Zobacz też 
 LZSS
 LZP
 LZMA
 LZ78

Przypisy

Bibliografia

Linki zewnętrzne 
 Jacob Ziv, Abraham Lempel; A Universal Algorithm for Sequential Data Compression, IEEE Transactions on Information Theory, 23(3), s. 337–343, May 1977.

Algorytmy kompresji bezstratnej