Kodowanie Huffmana Platforma programistyczna.net; materiały do laboratorium 24/5 Marcin Wilczewski
Algorytm Huffmana (David Huffman, 952) Algorytm Huffmana jest popularnym algorytmem generującym optymalny kod w ramach przyjętego probabilistycznego modelu źródła. Oczywiście różne modele mogą prowadzić do kodów o różnej średniej bitowej. Zawsze jednak w ramach przyjętego modelu probabilistycznego kod wygenerowany przez algorytm Huffmana jest optymalny. Algorytm Huffmana wykorzystuje dwa podstawowe fakty dotyczące optymalnych kodów prefiksowych:. Symbolom występującym z większym prawdopodobieństwem odpowiadają w kodzie optymalnym krótsze słowa kodowe niż symbolom występującym rzadziej. 2. Dwa najmniej prawdopodobne symbole są kodowane słowami kodowymi tej samej długości. Algorytm Huffmana rozszerza tę własność optymalnych kodów prefiksowych o dodatkową cechę: słowa kodowe odpowiadające dwóm najmniej prawdopodobnym symbolom różnią się tylko na jednym bicie. Niech dane jest źródło o alfabecie S zawierającym N symboli: S={s, s2, s3,, sn} oraz niech znane jest prawdopodobieństwo wystąpień poszczególnych symboli: p(s)=s, p(s2)=p2,, p(sn)=pn. Algorytm Huffmana znajdowania optymalnego kodu można wówczas sprowadzić do problemu konstrukcji drzewa binarnego zgodnie z poniższym algorytmem:. Ustaw symbole alfabetu S w porządku rosnącym ze względu na prawdopodobieństwo ich wystąpienia. Traktuj symbole alfabetu jako węzły (liście) budowanego drzewa binarnego. Każdemu węzłowi nadaj etykietę równą prawdopodobieństwu wystąpienia symbolu. 2. Znajdź w zbiorze węzłów drzewa dwa węzły o najmniejszym prawdopodobieństwie wystąpienia (nazwijmy je odpowiednio ch oraz ch2) i utwórz dla takich węzłów wspólny węzeł rodzica. Skojarz z węzłem rodzica prawdopodobieństwo będące sumą prawdopodobieństw węzłów ch oraz ch2. Ścieżce od węzła rodzica do lewego potomka nadaj etykietę, ścieżce od węzła rodzica do prawego potomka nadaj etykietę. Nowododany węzeł-rodzic staje się symbolem nowego, zredukowanego alfabetu (alfabet ten nie zawiera symboli będących potomkami nowego węzła). 3. Gdy drzewo zawiera więcej niż jeden węzeł przejdź do punktu algorytmu. W przeciwnym przypadku przejdź do punktu 4. 4. Słowo kodowe dla określonego symbolu tworzone jest przez konkatenację etykiet ścieżek jakie należy przejść od korzenia drzewa do liścia odpowiadającego danemu symbolowi.
Algorytm Huffmana. Przykład. a.5.6.5.9.25.3 b f d c a e b..5.6.5.9.25.3 b f d c a e c.26 d.26..44..5.6.5.9.25.3 b f d c a e.9.25.5.6.5.3 c a b f d e Symbol Prawdopodobieństwo a.25 b.5 c.9 d.5 e.3 f.6 Rys.. Ilustracja konstrukcji drzewa Huffmana dla alfabetu zawierającego 6 symboli o prawdopodobieństwach wystąpień podanych w tabeli obok. Rys. (a) przedstawia fazę inicjalną. Węzły zaznaczone kolorem zielonym odpowiadają symbolom (zredukowanego) alfabetu w bieżącej iteracji algorytmu. Kolejne rysunku przedstawiają kolejne iteracje algorytmu Huffmana. c.d. na następnej stronie
Algorytm Huffmana. Przykład (c.d.) e f..56.56..26.44.44..26.5.6.5.3.9.25 b f d e c a.9.25.5.6.5.3 c a b f d e Symbol Prawdopodo- Słowo kodowe bieństwo a.25 b.5 c.9 d.5 e.3 f.6 Rys.. c.d. z poprzedniej strony. Ostateczne drzewo Huffmana przedstawione jest na rysunku (f). Etykiety ścieżek przechodzonych od korzenia skonstruowanego drzewa do kolejnych liści reprezentujących symbole alfabetu wejściowego odpowiadają słowom kodowym symboli. Postać słów kodowych przedstawiona jest w tabeli obok. Ostateczna postać drzewa binarnego, a co za tym idzie postać słów kodowych zależy od szczegółów konstrukcji drzewa: etykiety ścieżek mogą być nadawane w kolejności przeciwnej niż zaproponowana w przedstawionym algorytmie, ustawianie węzłów w porządku rosnącego prawdopodobieństwa może być pominięte (lub porządek może być zmieniony). Wykorzystując taką dowolność otrzymać można różną postać słów kodowych. Niezależnie jednak od tego wynikowy kod zawsze jest optymalnym kodem prefiksowym.
Cechy kodów Huffmana Prawdopodo- Słowo Symbol bieństwo kodowe a.25 b.5 c.9 d.5 e.3 f.6 Rys.. Model probabilistyczny źródła oraz ciągi kodowe odpowiadające symbolom alfabetu. Cechy kodu Huffmana: Kod Huffmana jest kodem prefiksowym, a przez to jednoznacznie dekodowalnym. Cecha prefiksowości wynika ze sposobu konstrukcji kodu: przez drzewo binarne. Słowa kodowe odpowiadające symbolom najmniej prawdopodobnym mają największą długość (tu: 4 bity) i różnią się na jedynym bicie. Symbole najbardziej prawdopodobne posiadają najkrótsze słowa kodowe (tu: 2 bity). Kod Huffmana jest kodem optymalnym, tzn. H(S)<= B C <=H(S)+. Dla przykładu, entropia źródła kodowanego w przykładzie jest równa: H(S)=2.35, podczas gdy średnia długość skonstruowanego kodu Huffmana jest równa: ceecdaeccddfcadaedeaacadcca edeeaeaacaeceaeacecbcabccac aeeebadeaaaeddaadebaeaaddae adeaeaceecdccddcece Rys.. Rysunek przedstawia przykładowy strumień symboli wygenerowanych przez opisane w przykładzie źródło (model probabilistyczny podany jest w tabeli powyżej). W przypadku stosowania kodu stałe długości, np. kodu 3 bitowego zakodowany strumień zajmować będzie 3 bitów. W przypadku kodowania tego samego ciągu za pomocą wcześniej skonstruowanego kodu Huffmana zakodowany ciąg zajmować będzie 237 bitów, co daje 2.37 bita na symbol. Ten prosty przykład ilustruje jak stosowanie kodów zmiennej długości prowadzi do uzyskania kompresji, w tym przypadku stopień kompresji wynosi CR=.27. Kodowanie Huffmana jest efektywną techniką kodowania entropijnego, ale posiada też pewne ograniczenia: Efektywność kodowania Huffmana zależy od wielu czynników takich jak: rozmiar alfabetu wejściowego oraz rozkład prawdopodobieństw występowania poszczególnych symboli. Kod Huffmana jest optymalny ale tylko w przypadku, gdy znany jest rzeczywisty rozkład prawdopodobieństwa poszczególnych symboli generowanych przez źródło (model źródła). W przypadku, gdy założony rozkład prawdopodobieństwa odbiega od rzeczywistego rozkładu efektywność kodu Huffmana maleje. Z powyższego powodu kodowanie Huffmana nie nadaje się do kodowania źródeł o zmiennej charakterystyce (rozkład prawdopodobieństwa symboli zmienia się w czasie). W takim przypadku stosowanie statycznego kodowania Huffmana może doprowadzić do zwiększenia objętości strumienia zakodowanego w stosunku do wejściowego.
Cechy kodów Huffmana. Przykład Przykład: Niech dane są dwa źródła Ź oraz Ź2 niezależnie generujące symbole z alfabetów odpowiednio S=(,,,9) oraz S2=(,,2). Prawdopodobieństwa z jakimi źródła generują poszczególne symbole są przedstawione na rysunkach obok: rysunek (a) dla Ź, (b) dla Ź2. Jedną z wcześniej wymienionych cech kodów Huffmana jest zależność średniej długości kodu od rozmiaru alfabetu i rozkładu prawdopodobieństw poszczególnych symboli. Można pokazać, że jeśli p max jest prawdopodobieństwem najbardziej prawdopodobnego symbolu generowanego przez źródło, to średnia długość kodu Huffmana opisana jest następującą nierównością: a b.2..8.6.4.2 2 3 4 5 6 7 8 9.8.6.4.2 2 Entropia źródeł jest równa odpowiednio: H(S)=3.29 bita oraz H(S2)=.7 bita. Kody Huffmana skonstruowane niezależnie dla obu źródeł charakteryzują się średnią długością równą odpowiednio: BC(S)=3.32 oraz BC(S2)=.5 bita na symbol. Prosty rachunek pokazuje, że redundancja kodu Huffmana dla pierwszego źródła wynosi.3 bita na symbol, co oznacza że skonstruowany kod potrzebuje średnio dodatkowo.3 bita (.9%) do zakodowania pojedynczego symbolu w stosunku do przypadku idealnego. Redundancja kodu dla drugiego źródła wynosi.44 bita na symbol;.44 bita stanowi 6.2% entropii takiego źródła. stanowiącą dokładniejsze oszacowanie średniej długości kodu Huffmana. Z nierówności tej wynika, średnia długość kodu zbliża się do minimalnej wartości określonej przez entropię źródła w przypadku, gdy maksymalne prawdopodobieństwo w modelu probabilistycznym ma niewielkie wartości. Własność taką mogą posiadać alfabety dużych rozmiarów. W przeciwnym przypadku, gdy alfabet składa się z małej liczby symboli a prawdopodobieństwo jednego z symboli jest wyraźnie większe od pozostałych (duża wartość p max ), średnia długość kodu Huffmana oddala się od minimalnej długości kodu wyznaczonej przez entropię i kod staje się mało efektywny. Pytanie: Jaką średnią długość oraz stopień kompresji osiągają kody Huffmana konstruowane dla źródeł o alfabetach zawierających dokładnie dwa symbole?
Kodowanie strumienia danych Symbol Kod nieuwzględniający EOF a b c d e f EOF - Kod uwzględniający EOF Rys.. Postać kodu Huffmana dla symboli alfabetu źródła rozpatrywanego we wcześniejszych przykładach. Uwzględnienie symbolu EOF powoduje zmianę postaci słów kodowych (ostatnia kolumna tabeli). Rys. 2. Zakodowany ciąg symboli daaefeab uzupełniony znacznikiem EOF oraz dodatkowymi bitami dopełniającymi do 32 bitów (wielokrotność 8 bitów). Zielony blok wskazuje kod znacznika EOF, czerwony blok zawiera bity dopełniające. Kodowanie strumienia danych sprowadza się na mapowaniu kolejnych symboli pojawiających się w strumieniu wejściowym na wcześniej skonstruowane słowa kodowe. Tak powstały strumień kodowy w praktyce musi być rozszerzony o dodatkowe elementy umożliwiające jego poprawne przesłanie lub zapisanie oraz odkodowanie przez dekoder. Pierwszym elementem o jaki należy uzupełnić strumień kodowy jest nagłówek zawierający model probabilistyczny źródła, które wygenerowało pierwotny strumień danych. W takim przypadku pierwszą fazą pracy dekodera jest zbudowanie drzewa binarnego identycznego ze zbudowanym przez koder. W innym przypadku nagłówek może przyjmować postać ciągu par postaci: (symbol, słowo_kodowe). Są to informacje niezbędne dla dekodera do zdekodowania strumienia. Innym problemem jaki należy rozwiązać jest uzupełnienie strumienia kodowego o znak końca danych EOF oraz dopełnienie długości strumienia do wielokrotności 8 bitów. Symbol końca strumienia EOF uwzględnia się już na poziomie konstrukcji drzewa binarnego poprzez rozszerzenie alfabetu wejściowego o symbol EOF o najmniejszej częstości wystąpienia. Dekoder po napotkaniu słowa kodowego reprezentującego EOF ignoruje wszystkie pozostałe bity w strumieniu będące dopełnieniem strumienia do wielokrotności 8 bitów.
Zadanie. Stwórz klasę lub zbiór klas narzędziowych realizujących algorytm Huffmana; 2. Wykorzystaj powyższą klasę lub klasy do kompresji oraz dekompresji dużego pliku tekstowego, np. e-book. Ważne by przetwarzany zbiór stanowił dobrą reprezentację statystki symboli alfabetu; 3. Wyznacz wartość współczynnika kompresji dla skompresowanego zbioru.