4 marca 2004
Podstawowe oznaczenia i definicje Wymagania wobec kodu Podstawowa idea Podsumowanie
Podstawowe oznaczenia i definicje Podstawowe oznaczenia i definicje: alfabet wejściowy: A = {a 1, a 2,..., a m } informacja własna litery: i(a) = log entropia (średnia informacja własna): 1 = log P(A) (1) P(A) H = P(A i )i(a i ) = P(A i ) log P(A i ) (2) Tw. Shannona: średnia ilość bitów przypadająca na jeden zakodowany symbol nie jest mniejsza od entropii źródła kod prefiksowy: kod, w którym żadne słowo kodowe nie jest prefiksem innego słowa kodowego
Wymagania wobec kodu Podstawowa idea Podsumowanie Co chcemy uzyskać: każdemu symbolowi z alfabetu chcemy przypisać jednoznacznie słowo kodowe symbolom rzadko występującym chcemy przypisać dłuższe słowa kodowe, a częściej występującym krótsze chcemy móc jednoznacznie dekodować zakodowane dane ale takich kodów jest wiele, który wybrać? no i jak wybrać, aby taki kod móc szybko zbudować?
Wymagania wobec kodu Podstawowa idea Podsumowanie Cel: optymalny kod spełniający wymienione wymagania. Kluczowe obserwacje: dla każdego jednoznacznie dekodowalnego kodu istnieje nie gorszy (w sensie średniej ilości bitów na symbol) kod prefiksowy w kodzie optymalnym częściej występującym symbolom będą odpowiadały krótsze słowa kodowe a rzadziej występującym dłuższe w kodzie optymalnym dwa symbole występujące najrzadziej będą miały słowa kodowe o tej samej długości
Wymagania wobec kodu Podstawowa idea Podsumowanie A P(A) c(a) a 2 0.4 c(a 2 ) a 1 0.2 c(a 1 ) a 3 0.2 c(a 3 ) a 4 0.1 c(a 4 ) a 5 0.1 c(a 5 ) c(a 4 ) = α 1 0 c(a 5 ) = α 1 1 A P(A) c(a) a 2 0.4 c(a 2 ) a 1 0.2 c(a 1 ) a 3 0.2 c(a 3 ) a 4 0.2 α 1 c(a 3 ) = α 2 0 c(a 4 ) = α 2 1 α 1 = α 2 1 A P(A) c(a) a 2 0.4 c(a 2 ) a 3 0.4 α 2 a 1 0.2 c(a 1 ) c(a 3 ) = α 3 0 c(a 1 ) = α 3 1 α 2 = α 3 0 A P(A) c(a) a 3 0.6 α 3 a 2 0.4 c(a 2 ) c(a 3 ) = 0 c(a 2 ) = 1 α 3 = 0 c(a 2 ) = 1 c(a 1 ) = 01 c(a 3 ) = 000 c(a 4 ) = 0010 c(a 5 ) = 0011
Wymagania wobec kodu Podstawowa idea Podsumowanie Algorytm konstrukcji drzewa Huffmana: 1. umieść m liści na liście L 2. dopóki lista L zawiera przynajmniej dwa elementy wykonuj 2.1 usuń z listy L dwa elementy x oraz y o najmniejszej wadze 2.2 stwórz nowy wierzchołek p, który będzie rodzicem x i y 2.3 ustaw wagę wierzchołka p na sumę wag x i y 2.4 umieść wierzchołek p na liście L
Wymagania wobec kodu Podstawowa idea Podsumowanie Algorytm Huffmana generuje optymalny kod, ale jaka jest jego średnia długość l? Twierdzenie: H(S) l H(S) + 1 (3)
Wymagania wobec kodu Podstawowa idea Podsumowanie Istnieje możliwość dokładniejszego oszacowania. Niech P max = max {P(a i )} m i=1 wówczas P max < 0.5 = l H(S) + P max P max 0.5 = l H(S) + P max + 0.086
Wymagania wobec kodu Podstawowa idea Podsumowanie Zalety: kod Huffmana minimalizuje sumę ważoną długości kodów, tj. jest optymalnym kodem prefiksowym procedura budowy drzewa Huffmana jest szybka i prosta w implementacji zarówno kodowanie jak i dekodowanie jest proste i efektywne Wady: do budowy drzewa konieczne są statystyki kodowanej wiadomości do przekazywanej/zapisywanej wiadomości trzeba dołączyć opis drzewa
Cel: stworzenie jednoprzebiegowego algorytmu kodującego Metoda: utrzymywanie drzewa Huffmana obliczonego zgodnie z częstościami wystąpień symboli w dotychczas przetworzonym fragmencie Co zyskamy: tylko jeden przebieg nie trzeba przesyłać drzewa Problem: jak szybko uaktualniać drzewo Huffmana?
Wierzchołkom w drzewie przypisujemy wagę, która dla liści jest równa ilości wystąpień kodowanego symbolu w dotychczasowym tekście, a dla wierzchołków wewnętrznych sumie wag dzieci. Niech M t = a i1 a i2... a ik będzie dotychczas przetworzonym fragmentem. Następna litera a ik+1 będzie zakodowana oraz odkodowana przy użyciu drzewa Huffmana dla M t. Główna trudność: jak szybko zmodyfikować drzewo dla M t aby otrzymać drzewo dla M t+1? Proste zwiększenie o 1 wagi wierzchołka i jego rodziców nie zawsze da drzewo Huffmana. Rozwiązanie: wykorzystać własność sąsiedztwa
Do wyprowadzenia algorytmu wykorzystamy pewną charakteryzację drzew Huffmana: Własność sąsiedztwa Drzewo binarne o p liściach oraz nieujemnych wagach wierzchołków w i jest drzewem Huffmana wtedy i tylko wtedy gdy: 1. waga każdego wierzchołka jest sumą wag jego dzieci 2. istnieje niemalejąca numeracja wierzchołków zgodna z niemalejącym uporządkowaniem według wagi taka, że dla 1 j p 1 wierzchołki 2j 1 i 2j są sąsiadami i ich wspólny rodzic ma wyższy numer
Rozwiązanie: aktualizacje drzewa wykonamy w dwóch fazach: 1. przekształcenie drzewa do takiej postaci, w której proste zwiększenie wagi odpowiednich wierzchołków nie zaburzy własności sąsiedztwa 2. zwiększenie wagi wierzchołka odpowiadającego przetwarzanemu symbolowi i jego rodzicom
Pytanie: co zrobić z drzewem, aby można było po prostu zwiększyć wagi wierzchołków? Odpowiedź: zaczynając od wierzchołka, który odpowiada kodowanemu symbolowi, zamieniać aktualny wierzchołek z wierzchołkiem o najwyższym numerze (w sensie numeracji z własności sąsiedztwa) spośród wierzchołków o tej samej wadze
procedure update; q := wierzchołek odpowiadający otrzymanej literze; if (q = wierzchołek 0) and (k < m - 1) then dodaj q dwoje dzieci (numeracja: lewe, prawe, rodzic) q := prawe dziecko if q jest sąsiadem wierzchołka 0 then zamień q z liściem o tej samej wadze i najw. zwiększ wagę q o 1 q := rodzic q numerze while q nie jest korzeniem zamień q z wierz. o tej samej wadze i najw. num. zwiększ wagę q o 1 q := rodzic q
Obserwacje: zamiany wierzchołków, wykonywane przez algorytm, nie powodują, że drzewo przestaje być drzewem Huffmana dla M t (co wynika z własności sąsiedztwa) po zwiększeniu odpowiednich wag (w drzewie otrzymanym przez wykonanie zamian) dostaniemy drzewo Huffmana dla M t+1 (co ponownie wynika z własności sąsiedztwa)
Ile nas to kosztuje? O ile więcej bitów wygeneruje algorytm FGK w porównaniu z klasycznymi kodami Huffmana? Odpowiedź: Jeżeli S jest ilością bitów wygenerowanych przez oryginalny algorytm Huffmana, S ilością bitów wygenerowanych przez algorytm FGK, a m rozmiarem alfabetu, to zachodzi: S 2S + m (4)
Pytanie: Czy można lepiej? Odpowiedź: Tak, używając algorytmu Vittera można mieć: S < S + m (5)
Podstawowa idea: ograniczyć ilość zamian, w których wierzchołek q porusza się w górę drzewa, do co najwyżej jednego przy każdym wywołaniu update konstruować drzewo w ten sposób, aby minimalizowało nie tylko sumę ważoną długości ścieżek w drzewie j w jl j, ale również sumę nieważoną długości ścieżek j L j oraz długość najdłuższej ścieżki max j {L j } - intuicyjnie powinno to ograniczyć długość słowa kodowego dla następnej litery
Klasyfikacja zamian: wierzchołek q przesuwa się do góry o jeden poziom wierzchołek q zamieniamy z wierzchołkiem z tego samego poziomu wierzchołek q zamieniamy z wierzchołkiem na niższym poziomie wierzchołek q zamieniamy z wierzchołkiem położonym o dwa poziomy wyżej
Niejawna numeracja Pomysł: numerować wierzchołki drzewa w sposób odpowiadający reprezentacji wizualnej: wierzchołki numerujemy w sposób zgodny z poziomami drzewa: wierzchołki na tym samym poziomie mają numery niższe niż te na następnym, wyższym poziomie wierzchołki na tym samym poziomie numerujemy rosnąco od lewej do prawej Gdy używamy niejawnej numeracji, nie będzie zamian typu. Oprócz tego, jeżeli wierzchołek przesuwa się do góry w zamianie typu, to ten, który przesuwa się w dół, jest liściem.
Niezmiennik algorytmu Kluczem do polepszenia algorytmu jest uniknięcie zamian typu poza pierwszą iteracją pętli while. Aby to zrobić będziemy utrzymywać następujący niezmiennik: dla każdej wagi w, wszystkie liście o wadze w poprzedzają w niejawnej numeracji wszystkie wierzchołki wewnętrzne o wadze w Można pokazać, że drzewo Huffmana, które spełnia ten niezmiennik, minimalizuje j L j oraz max j {L j }.
Kilka definicji: blok - klasa równoważności relacji na wierzchołkach drzewa: wierzchołki v i x są w relacji, jeśli mają tą samą wagę oraz obydwa są wierzchołkami lub obydwa są liśćmi (w algorytmie FGK nie zwracaliśmy uwagi na liście/wierzch. wewn.) lider bloku - wierzchołek o najwyższym numerze należący do bloku Bloki są połączone w listę w kolejności rosnącej wagi, blok liści zawsze poprzedza blok wierzchołków wewnętrznych o tej samej wadze.
procedure update; leaftoincrement := 0; q := wierzchołek odpowiadający otrzymanej literze; if (q = wierzchołek 0) and (k < m - 1) then dodaj q dwoje dzieci, prawe odpowiadające literze q := wierzchołek, który właśnie został tatusiem leaftoincrement := prawe dziecko q else zamień q z liderem jego bloku if q jest sąsiadem wierzchołka 0 then leaftoincrement := q; q := rodzic q while q nie jest korzeniem slideandincrement(q); if leaftoincrement 0 then slideandincrement(leaftoincrement);
procedure slideandincrement(p); wt := waga wierzchołka p; b := następny blok na liście po bloku wierzchołka p; if p jest liściem and b jest blokiem wierzch. wewn. o wadze wt or p jest wierzch. wewn. and b jest blokiem liści o wadze wt+1 then zjedź wierzch. p w drzewie w kierunku wierzch. z b p.weight := wt + 1; if p jest liściem then p := nowy rodzic p else p := dawny rodzic p
Podsumowanie długość danych zakodowanych algorytmem Vittera może się różnić od długości danych zakodowanych statycznym algorytmem Huffmana co najwyżej o długość alfabetu algorytm jest dosyć skomplikowany, ale Vitter opublikował jego wzorcową implementację algorytm wymaga specyficznych struktur danych, opisanych dokładnie w pracach Vittera
Wyniki testów Typ Rozmiar Stat. kody FGK Vitter pliku początkowy Huffmana Postscript 506197 334785 334907 334891 BMP 481078 448533 448821 448739 Poczta 1657081 1112169 1112278 1112264 Źródła w C 1331200 778081 778207 778182 WAV 1000000 763453 763933 763717
Współczynnik kompresji Typ Stat. kody FGK Vitter pliku Huffmana Postscript 0,6614 0,6616 0,6616 BMP 0,9323 0,9329 0,9328 Poczta 0,6712 0,6712 0,6712 Źródła w C 0,5845 0,5846 0,5846 WAV 0,7635 0,7639 0,7637 Średnio 0,7226 0,7229 0,7228
Gdzie szukać dalszych informacji: Khalid Sayood, Kompresja danych - wprowadzenie, wydawnictwo Read Me, kwiecień 2002. Jeffrey S. Vitter, Design and Analysis of Dynamic Huffman Codes, JACM Vol. 34, październik 1987. Jeffrey S. Vitter, Dynamic Huffman Coding, ACM Transactions on Mathematical Software Vol. 15, czerwiec 1989.
Długość kodów Huffmana Algorytm Huffmana generuje optymalny kod, ale jaka jest jego średnia długość l? Twierdzenie: H(S) l H(S) + 1 (6)
Długość kodów Huffmana Lemat (Kraft, McMillan): (McMillan) Niech C będzie jednoznacznie dekodowalnym kodem. Niech A = {a 1, a 2,..., a m } będzie alfabetem wejściowym oraz niech l i = C(a i ). Wówczas: m 2 l i 1 (7) i=1 (Kraft) Dla dowolnego ciągu dodatnich liczb całkowitych {l i } m i=1 spełniającego (7) istnieje jednoznacznie dekodowalny kod o długościach {l i } m i=1
Długość kodów Huffmana Wpierw pokażemy, że H(S) l. Prawdopodobieństwo wystąpienia litery a i oznaczmy przez P(a i ). Wtedy mamy: m l = P(a i )l i (8) H(S) l = = = = i=1 i=1 m P(a i ) log P(a i ) i=1 m ( [ 1 P(a i ) log m i=1 m i=1 P(a i ) ( [ 1 P(a i ) log P(a i ) ] [ 2 l i P(a i ) log P(a i ) m P(a i )l i i=1 ] l i ) ] [ ] ) log 2 l i
Długość kodów Huffmana Nierówność Jensena: dla każdej wklęsłej funkcji ( ) f (x) zachodzi: E [f (X )] f (E[X ]) (9) Ponieważ funkcja log jest wklęsła, wobec tego: m [ 2 l i ] [ m H(S) l = P(a i ) log log P(a i ) i=1 i=1 2 l i ] (10) Ponieważ kod jest optymalny, to z lematu Krafta-McMillana (7) mamy że m i=1 2 l i 1, a więc H(S) l 0 co kończy pierwszą część dowodu.
Długość kodów Huffmana Górna granica - wiemy, że kod jest optymalny, więc wystarczy pokazać istnienie kodu takiego, że l H(S) + 1. Zdefiniujmy: 1 l i = log (11) P(a i ) Ponieważ x. ɛ [0, 1). x = x + ɛ to zgodnie z (11) mamy: log 1 P(a i ) l 1 i log P(a i ) + 1 (12)
Długość kodów Huffmana Zauważmy, że z lewej nierówności z (12) mamy: 2 l i P(a i ) wobec czego, sumując obustronnie, otrzymujemy: m 2 l i i=1 m P(a i ) = 1 i=1 skąd z kolei, przez drugą część lematu Krafta-McMillana, istnieje jednoznacznie dekodowalny kod o długościach {l i }. Długość tego kodu możemy oszacować następująco: l = m P(a i )l i < i=1 co kończy dowód. m i=1 [ ] 1 P(a i ) log P(a i ) + 1 = H(S) + 1 (13)