• Nie Znaleziono Wyników

W celu pełnego zrozumienia metody LZW, przyjmijmy najpierw, że słownik ma strukturę arraya, którego elementami są stringi.

Metoda LZW rozpoczyna swoje działanie od inicjalizacji

słownika. Dodawane są do niej wszystkie znaki rozważanego

alfabetu, stąd w najczęstszym przypadku 8-bitowym, pierwsze

256 pozycji słownika (od 0 do 255) jest zajętych jeszcze przed

rozpoczęciem czytania danych wejściowych. Dodatkowo ze

względu na sposób inicjalizacji słownika, pierwszy przeczytany

element zawsze jest możliwy do odnalezienia w słowniku. Z

tego powodu token tej metody może posiadać tylko jedno pole.

KODOWANIE

Koder w metodzie LZW działa w podobny sposób, co w metodzie LZ78. Pobiera on pierwszy symbol i przeszukuje słownik.

Oczywiście pierwszy element danych wejściowych zawsze będzie możliwy do odnalezienia. Następnie czytany jest kolejny symbol i, tak jak w przypadku LZ78, dołączany do pierwszego symbolu tworząc stringa.

• Jeżeli taki string zostanie odnaleziony w słowniku, wówczas czytany i dołączany jest kolejny symbol,

• Jeżeli string nie zostanie jednak odnaleziony, koder zwraca wskaźnik do ostatniego odnalezionego stringa i zapisuje w słowniku string nieodnaleziony. Następnie inicjalizuje nowy string, którego początkiem jest ostatni symbol (ten, przez którego słowo nie zostało odnalezione).

KODOWANIE

W ramach przykładu rozważmy ponownie dane wejściowe następującej postaci:

𝑠𝑖𝑟_𝑠𝑖𝑑_𝑒𝑎𝑠𝑡𝑚𝑎𝑛_𝑒𝑎𝑠𝑖𝑙𝑦_𝑡𝑒𝑎𝑠𝑒𝑠_𝑠𝑒𝑎_𝑠𝑖𝑐𝑘_𝑠𝑒𝑎𝑙𝑠

Metoda LZW dla powyższego inputu podejmuje następujące kroki:

1. Inicjalizuje słownik i zapisuje w nim wszystkie 256 elementów na pozycjach 0-255.

2. Czyta pierwszy symbol „s” i poszukuje go w słowniku. Symbol „s”

zostaje znaleziony na miejscu 115 (na podstawie kodu ASCII).

Następnie czytany jest kolejny symbol „i” oraz doklejany do „s”. W taki sposób metoda otrzymuje stringa postaci „si”, którego nie może znaleźć już w słowniku. Wówczas:

– Zwracany jest token postaci 115,

– String „si” zostaje zapisany na miejscy 256 w słowniku,

– Zainicjalizowany zostaje string, którego początkiem jest „i”.

KODOWANIE

3. Metoda czyta kolejny symbol postaci „r” i znajduje go w słowniku na miejscu 105. Następnie dokleja go do „i” otrzymując stringa postaci „ir”, którego nie może znaleźć w słowniku. Wówczas:

– Zwraca token postaci 105,

– Zapisuje string „ir” na miejscu 257 w słowniku, – Inicjalizuje stringa dla „r”.

4. Metoda pracuje w opisany powyżej sposób do momentu przeczytania wszystkich symboli z danych wejściowych.

Otatecznie jako output otrzymujemy:

115 (s), 105 (i), 114 (r), 32 (_), 256 (si), 100 (d), 32 (_), 101 (e), 97 (a), 115 (s), 116 (t), 109 (m), 97 (a), 110 (n), 262 (_e), 264 (as), 105 (i), 108 (l), 121 (y), 32 (_), 116 (t), 263 (ea), 115 (s), 101 (e), 115 (s), 259 (_s), 263 (ea), 259 (_s), 105 (i), 99 (c), 107 (k), 280 (_se), 97 (a), 108

(l), 115 (s), eof.

KODOWANIE

Ponieważ pierwsze 256 elementów słownika jest wypełnionych przed rozpoczęciem czytania pliku źródłowego, to wskaźniki do słownika muszą być dłuższe niż 8 bitów. W prostej implementacji rozważa się zwykle wskaźniki 16-bitowe, co daje słownik rozmiaru 64 kilobajtów.

Słownik ten, poza przypadkami z danymi

wejściowymi bardzo małych rozmiarów, będzie wypełniany w

bardzo szybkim tempie. Oczywiście podobna sytuacja

zachodziła w przypadku metody LZ78, stąd wszystkie metody

na radzenie sobie z tym problemem wspomniane w części

dotyczącej metody LZ78, mogą zostać zastosowane również

w przypadku metody LZW.

KODOWANIE

Ze względu na specyfikę tworzenia nowych elementów w słowniku w metodzie LZW, zapis długich stringów w słoniku może zająć dużo czasu. Każdy z nich zwiększa się bowiem za każdym razem tylko o jeden symbol.

Długi czas zapisu elementów w słowniku oznacza również

długi czas kompresji. Stąd przyjęto, że cechą metody LZW

jest wolne dostosowywanie się do danych wejściowych.

DEKODOWANIE

Dekoder, podobnie jak koder, startuje inicjalizując słownik ze wszystkimi znakami w alfabecie na pozycjach 0 – 255. Następnie czyta dane wejściowe zawierające wskaźniki i używa ich do odzyskania nieskompresowanych symboli, które zwraca.

Rozważmy działanie dekodera na tym samym przykładzie:

𝑠𝑖𝑟_𝑠𝑖𝑑_𝑒𝑎𝑠𝑡𝑚𝑎𝑛_𝑒𝑎𝑠𝑖𝑙𝑦_𝑡𝑒𝑎𝑠𝑒𝑠_𝑠𝑒𝑎_𝑠𝑖𝑐𝑘_𝑠𝑒𝑎𝑙𝑠

Pierwszym pobranym przez dekoder wskaźnikiem jest 115, który może odnaleźć w swoim słowniku. Odpowiada on znakowi „s”, który zostaje zapisany w stringu S, a następnie w w outpucie. Następnym wskaźnikiem jest 105, symbol „i” zostaje zapisany w stringu J oraz również w outpucie. Dekoder łączy stringi S i J otrzymując „si”, którego nie ma w słowniku dekodera. Zostaje zatem dodane na miejscu 256.

Wartość stringa J jest przeniesiona do stringa S, który zamiast wartości „s”, przechowuje teraz wartość „i”.

DEKODOWANIE

Kolejnym wskaźnikiem, który pobiera dekoder jest 114, odnajduje on więc literę „r” w swoim słowniku, zapisuje w stringu J i w outpucie. Łączy następnie ze stringiem S otrzymując „ir”, którego nie ma w słowniku. Dodaje je na miejscu 257 i przenosi wartość J do S, zatem S przechowuje teraz symbol „r”. Taka logika jest przeprowadzana do momentu wyczerpania się tokenów.

Pierwszym krokiem dekodera jest zatem pobranie pierwszego wskaźnika i uzyskanie stringa S, który zostaje wypisany do outputu. W każdym kolejnym kroku dekoder pobiera następny wskaźnik, odnajduje string J ze słownika, wpisuje do outputu, izoluje jego pierwszy symbol „x” i zapisuje string Sx w słowniku (o ile nie został tam wcześniej zapisany). Dekoder przenosi następnie wartość J do S i jest gotowy na kolejny krok.

STRUKTURA SŁOWNIKA

Dotychczas zakładaliśmy, że słownik w metodzie LZW jest arrayem złożonym ze stringów. Oczywiście lepszym wyborem jest struktura drzewa, podobna jak w przypadku metody LZ78.

Dodanie nowego stringa postaci Sx do drzewa wykonywane przez dodanie dziecka „x” węzła S. Kwestią problematyczną w metodzie LZW jest to, że każdy węzeł może mieć wiele dzieci.

Struktura drzewa powinna być zatem zaprojektowana tak, że węzeł może mieć dowolną liczbę dzieci, bez konieczności rezerwowania dla nich pamięci na zapas.

Sposobem na zaprojektowanie takiej struktury jest umieszczenie drzewa w arrayu z węzłami, takimi, że każdy z nich ma dwa pola – symbol i wskaźnik do swojego rodzica. Węzeł nie ma więc żadnych wskaźników do dzieci. Schodzenie po drzewie do jednego z dzieci danego węzła odbywa się przez proces, w którym wskaźnik do węzła i symbol dziecka zostają zmieszane w celu utworzenia nowego wskaźnika.

KODOWANIE

Jako przykład rozważmy string postaci „abc”, który został już przeczytany, symbol po symbolu, i zapisany w drzewie w trzech węzłach: 97 („a”), 266 („ab”) i 284 („abc”).

Następnie koder pobiera znak „d” i poszukuje stringa

„abcd” w drzewie. Tak naprawdę poszukuje on węzła o wartości „d”, którego rodzicem jest węzeł na pozycji 284.

Koder miesza więc pozycję 284 rodzica oraz 100, oznaczające „d” w kodzie ASCII, w celu stworzenia nowego wskaźnika, przyjmijmy 299. Koder sprawdza potem węzeł 299. Istnieją 3 możliwości:

1. Węzeł jest nieużywany. Oznacza to, że wartość „abcd”

nie została wcześniej dodana do słownika. Koder dodaje

zatem węzeł postaci (284:d).

KODOWANIE

2. Rozważany węzeł 299 zawiera wskaźnik do rodzica 284 i kod ASCII dla symbolu „d”, co oznacza, że „abcd” należy już do drzewa. Wówczas koder pobiera kolejny znak, przyjmijmy „e” i przeszukuje drzewo, aby znaleźć stringa postaci „abcde”.

3. Węzeł zawiera inną informację. To oznacza kolizję i może być rozwiązane w różny sposób. Najprostszym jest zwiększenie wskaźnika 299 i sprawdzenie węzłów 300, 301, … , aż zostanie znaleziony pusty węzeł lub węzeł z informacją (284:d). W praktyce często budowane są węzły, które zawierają 3 pola – wskaźnik z procesu mieszania, kod (na przykład ASCII) i symbol, który zawiera węzeł. Drugie pole jest konieczne ze względu na możliwość kolizji.

W dokumencie Metody słownikowe KOMPRESJA (Stron 40-51)

Powiązane dokumenty