|
Kodowanie Huffmana (ang. Huffman coding) to jedna z najprostszych i łatwych w implementacji metod kompresji bezstratnej. Została opracowana w 1952 roku przez Amerykanina Davida Huffmana. Algorytm Huffmana nie należy do najefektywniejszych systemów bezstratnej kompresji danych, dlatego też praktycznie nie używa się go samodzielnie. Często wykorzystuje się go jako ostatni etap w różnych systemach kompresji, zarówno bezstratnej jak i stratnej, np. MP3 lub JPEG. Pomimo, że nie jest doskonały, stosuje się go ze względu na prostotę oraz brak ograniczeń patentowych.
Kodowanie HuffmanaDany jest alfabet źródłowy (zbiór symboli) Kodowanie Huffmana polega na utworzeniu słów kodowych (ciągów bitowych), których długość jest odwrotnie proporcjonalna do prawdopodobieństwa pi. Tzn. im częściej dane słowo występuje (może wystąpić) w ciągu danych, tym mniej zajmie bitów. Własności kodu są następujące:
Kompresja polega na zastąpieniu symboli otrzymanymi kodami. Algorytm statycznego kodowania HuffmanaAlgorytm Huffmana:
Drzewo, które pozostanie na liście, jest nazywane drzewem Huffmana – prawdopodobieństwo zapisane w korzeniu jest równe 1, natomiast w liściach drzewa zapisane są symbole. Algorytm Huffmana jest algorytmem niedeterministycznym ponieważ nie określa w jakiej kolejności wybierać drzewa z listy, jeśli mają takie samo prawdopodobieństwo. Nie jest również określone, które z usuwanych drzew ma stać się lewym bądź prawym poddrzewem. Jednak bez względu na przyjęte rozwiązanie średnia długość kodu pozostaje taka sama. Na podstawie drzewa Huffmana tworzone są słowa kodowe; algorytm jest następujący:
Długość słowa kodowego jest równa głębokości symbolu w drzewie, wartość binarna zależy od jego położenia w drzewie. PrzykładMamy symbole A,B,C,D o prawdopodobieństwach wystąpienia odpowiednio [0.1, 0.2, 0.3, 0.4].
Jak łatwo sprawdzić średnia długość kodu wyniesie: Jest to mniej niż 2 bity potrzebne w trywialnym kodowaniu o stałej długości znaku. Z kolei entropia źródła wynosi: E = − 0.1log20.1 − 0.2log20.2 − 0.3log20.3 − 0.4log20.4 = 1.8464 - optymalne kodowanie powinno charakteryzować się taką właśnie średnią długością kodu. Jednak widać, że jest ona większa - efektywność wynosi w tym przypadku Praktyczne zastosowanieJednym z głównych problemów stosowania statycznego algorytmu Huffmana jest konieczność transmisji całego drzewa lub całej tablicy prawdopodobieństw (zwykle to drugie jako bardziej efektywne). Lepszą kompresję, kosztem jednak bardzo szybkiego wzrostu wymagań pamięciowych, uzyskuje się kodując kilka kolejnych znaków na raz, nawet jeżeli nie są one skorelowane. Przykład kodowania po 2 znaki naraz
Zatem odpowiednim parom znaków odpowiadają:
Średnia liczba bitów przypadająca na parę symboli to 3.73, a więc średnia liczba bitów na symbol to 1.865. Jest to znacznie lepsza kompresja (6.75% zamiast 5% przy maksymalnej możliwej 7.68%) niż poprzednio. Używając większej liczby znaków można dowolnie przybliżyć się do kompresji maksymalnej, jednak znacznie wcześniej wyczerpie się pamięć, ponieważ wymagania pamięciowe rosną wykładniczo do liczby kompresowanych jednocześnie symboli. Przykładowa implementacja algorytmu budowy drzewaZwracana jest tablica (2*N-1) węzłów, z czego N pierwszych odpowiada odpowiednim symbolom, a ostatni korzeniowi drzewa. Algorytm wyszukiwania węzłów o najmniejszej wartości nie jest specjalnie efektywny. W rzeczywistym kodzie raczej należałoby utworzyć posortowaną tablicę wolnych węzłów i efektywnie zakodować operację jednoczesnego usunięcia z niej 2 skrajnych elementów i wprowadzenia 1 nowego.
struct Node {
int value;
struct Node *left, *right, *parent;
};
struct Node* make_huffman_tree (int symbols, int *values)
{
struct Node *nodes = malloc (sizeof (struct Node) * (symbols * 2 - 1));
int first_free_node = symbols;
struct Node *tmp1, *tmp2;
int i;
for (i=0; i<symbols; ++i)
{
nodes[i].left = NULL;
nodes[i].right = NULL;
nodes[i].parent = NULL;
nodes[i].value = values[i];
}
while (first_free_node != (symbols * 2 - 1))
{
tmp1 = NULL;
tmp2 = NULL;
for (i=0; i<first_free_node; ++i)
{
if (nodes[i].parent)
continue;
if (!tmp1)
{
tmp1 = &nodes[i];
}
else if (!tmp2)
{
tmp2 = &nodes[i];
}
else if (tmp1->value > nodes[i].value)
{
tmp2 = tmp1;
tmp1 = &nodes[i];
}
}
nodes[first_free_node].left = tmp1;
nodes[first_free_node].right = tmp2;
nodes[first_free_node].parent = NULL;
nodes[first_free_node].value = tmp1->value + tmp2->value;
tmp1->parent = &nodes[first_free_node];
tmp2->parent = &nodes[first_free_node];
first_free_node ++;
}
return nodes;
}
int main()
{
struct Node *nodes;
int value_table[4] = {1,2,3,4};
nodes = make_huffman_tree (4, value_table);
return 0;
}
Kodowanie Huffmana z mniejszymi wymaganiami pamięciowymiJeśli kodowane są pary symboli (tak jak w przykładzie powyżej), albo trójki symboli, czy ogólnie n-tki symboli, to rozmiar drzewa Huffmana rośnie znacząco - drzewo Huffmana należy zapisać razem z zakodowanym komunikatem, aby można go było zdekodować; także im większe drzewo, tym dłuższe stają się kody rzadziej występujących symboli. C. Weaver zaproponował modyfikację algorytmu Huffmana, która redukuje pamięć potrzebną do zapamiętania drzewa. Pomysł został opracowany przez Michaela Hankamera, który opublikował wyniki w artykule "A modified Huffman procedure with reduced memory reqiument" (IEEE Transactions on Communication COM-27, 1979, s. 930-932). Modyfikacja polega na wprowadzeniu dodatkowego symbolu nazywanego ELSE, który zastępuje wszystkie rzadko występujące symbole - jeśli pojedynczy symbol opisuje N bitów, to symbol trafi do zbioru ELSE, gdy jego prawdopodobieństwo Dzięki temu drzewo staje się mniejsze, ponieważ zachowuje tylko symbole nie należące do ELSE i to w zupełności wystarczy, ponieważ symbole ze zbioru ELSE są bezpośrednio zapisane w komunikacie. Zastosowanie tej modyfikacji może nawet polepszyć nieco stopień kompresji w stosunku do niezmodyfikowanej wersji algorytmu. Algorytm dynamicznego kodowania HuffmanaDynamiczne kodowanie Huffmana to kodowanie danych o nieznanej statystyce. Statystykę buduje się w miarę napływania danych i co znak lub co daną liczbę znaków poprawia drzewo Huffmana. Zaletą kodowania dynamicznego jest to, że nie ma potrzeby przesyłania drzewa kodów. Zamiast tego identyczną procedurę poprawiania drzewa muszą przeprowadzać zarówno koder jak i dekoder. Istnieją dwa algorytmy pozwalające poprawić drzewo Huffmana:
U podstaw algorytmu FGK leżą następujące założenie co do formy drzewa:
W algorytmie Vittera zaostrzone zostało ostatnie założenie:
Gdy licznik w jakimś liściu zwiększy się, algorytmy modyfikują (przemieszczając niektóre węzły) jedynie niewielki fragment drzewa, zachowując wyżej wymienione własności. Algorytm Vittera jest nieco bardziej złożony, jednak daje lepsze wyniki, tj. krótsze kody, niż algorytm FKG. Inne algorytmy kompresji bezstratnejZobacz też |
This article is from Wikipedia. All text is available under the terms of the GNU Free Documentation License.
Mercedes Car
This site monitored by SitePinger.net