Kompresja Huffmana

Wprowadzenie

Kodowanie Huffmana (ang. Huffman Coding) to algorytm kompresji danych opracowany przez Davida Huffmana w 1952 roku. Jest to optymalne kodowanie prefiksowe - żaden inny algorytm kodowania prefiksowego nie może osiągnąć lepszej kompresji.

Idea: Przydziel krótsze kody znakom występującym częściej, a dłuższe znakom rzadszym.

Kluczowa własność: Kodowanie prefiksowe - żaden kod nie jest prefiksem innego kodu.

Kodowanie Huffmana jest wykorzystywane w:

  • Kompresji plików (ZIP, GZIP, DEFLATE)
  • Formatach graficznych (JPEG, PNG)
  • Kompresji wideo (MPEG)
  • Protokołach sieciowych (HTTP/2, HPACK)
  • Kompresji tekstu (archiwa, ebook'i)
  • Przesyłaniu danych (modemy, satelity)

Problem kodowania

Kodowanie o stałej długości vs zmiennej długości

Wyjście:

=== Kodowanie o stałej vs zmiennej długości ===

Tekst: 'ABRACADABRA'
Długość: 11 znaków

Częstości znaków:
  'A': 5 (45.5%)
  'B': 2 (18.2%)
  'C': 1 (9.1%)
  'D': 1 (9.1%)
  'R': 2 (18.2%)

============================================================
KODOWANIE O STAŁEJ DŁUGOŚCI:
============================================================

Liczba unikalnych znaków: 5
Bitów na znak: 3

Kody:
  'A': 000
  'B': 001
  'C': 010
  'D': 011
  'R': 100

Zakodowany tekst:
  000001100000010000011000001100000
  Długość: 33 bitów

============================================================
KODOWANIE O ZMIENNEJ DŁUGOŚCI (przykład):
============================================================

Kody (krótsze dla częstszych):
  'A': 0      (częstość: 5)
  'B': 110    (częstość: 2)
  'C': 1110   (częstość: 1)
  'D': 1111   (częstość: 1)
  'R': 10     (częstość: 2)

Zakodowany tekst:
  0110100111001111011100
  Długość: 22 bitów

============================================================
PORÓWNANIE:
============================================================

Stała długość:  33 bitów
Zmienna długość: 22 bitów
Oszczędność:    11 bitów (33.3%)

Kodowanie prefiksowe

Właściwość prefiksu

Definicja: Kodowanie jest prefiksowe jeśli żaden kod nie jest prefiksem innego kodu.

Wyjście:

=== Kodowanie prefiksowe ===

Test 1: Kodowanie prefiksowe (dobre)
Kody: {'A': '0', 'B': '110', 'C': '1110', 'D': '1111', 'R': '10'}
Jest prefiksowe? ✓ TAK

Test 2: Kodowanie NIE-prefiksowe (złe)
Kody: {'A': '0', 'B': '01', 'C': '011', 'D': '1'}
Jest prefiksowe? ✗ NIE
Konflikty:
  '0' (A) jest prefiksem '01' (B)
  '0' (A) jest prefiksem '011' (C)
  '01' (B) jest prefiksem '011' (C)

============================================================
PROBLEM Z DEKODOWANIEM (nie-prefiksowe):
============================================================

Zakodowany ciąg: '011'

Możliwe dekodowania:
  1. '0' + '11' → A + ??  (brak '11' w kodach)
  2. '01' + '1' → B + D
  3. '011' → C

✗ Niejednoznaczne! Nie można poprawnie zdekodować!

Drzewo Huffmana

Reprezentacja kodowania prefiksowego

Kluczowa idea: Każde kodowanie prefiksowe można reprezentować jako drzewo binarne.

  • Liście = symbole do zakodowania
  • Ścieżka od korzenia do liścia = kod symbolu
  • Lewo = '0', Prawo = '1'

Struktura węzła


Algorytm Huffmana

Budowa drzewa (zachłanna)

Algorytm:

1. Stwórz liść dla każdego symbolu z jego częstością
2. Umieść wszystkie liście w kolejce priorytetowej (min-heap)
3. Dopóki w kolejce jest więcej niż 1 węzeł:
   a) Wyciągnij dwa węzły o najmniejszych częstościach
   b) Stwórz nowy węzeł z nimi jako dziećmi
   c) Częstość nowego węzła = suma częstości dzieci
   d) Wstaw nowy węzeł do kolejki
4. Pozostały węzeł to korzeń drzewa Huffmana

Implementacja

Wyjście:

=== Budowanie drzewa Huffmana ===

Budowanie drzewa Huffmana dla 5 unikalnych znaków

Początkowe węzły (liście):
  'C': częstość = 1
  'D': częstość = 1
  'B': częstość = 2
  'R': częstość = 2
  'A': częstość = 5

Krok 1:
  Połącz: Leaf('C', freq=1) + Leaf('D', freq=1)
  Nowy węzeł: freq = 2

Krok 2:
  Połącz: Node(freq=2) + Leaf('B', freq=2)
  Nowy węzeł: freq = 4

Krok 3:
  Połącz: Leaf('R', freq=2) + Node(freq=4)
  Nowy węzeł: freq = 6

Krok 4:
  Połącz: Leaf('A', freq=5) + Node(freq=6)
  Nowy węzeł: freq = 11

============================================================
WYGENEROWANE KODY HUFFMANA:
============================================================

Znak     Kod          Długość    Częstość
---------------------------------------------
'A'      0            1          5
'R'      10           2          2
'B'      110          3          2
'C'      1110         4          1
'D'      1111         4          1

Kodowanie i dekodowanie

Kodowanie tekstu

Wyjście:

=== Kodowanie tekstu ===

Tekst oryginalny: 'ABRACADABRA'
Długość: 11 znaków

Zakodowany (bity): 0110100111001111011100
Długość: 22 bitów

Porównanie:
  Kodowanie Huffmana: 22 bitów
  Kodowanie stałe:    33 bitów
  Oszczędność:        11 bitów (33.3%)

Dekodowanie tekstu

Wyjście:

=== Dekodowanie ===

Zakodowane bity: 0110100111001111011100
Długość: 22 bitów

Zdekodowany tekst: 'ABRACADABRA'
Długość: 11 znaków

Oryginalny tekst:  'ABRACADABRA'
Zdekodowany tekst: 'ABRACADABRA'
Poprawne? ✓ TAK

Wizualizacja drzewa Huffmana

Wyjście:

=== Wizualizacja drzewa Huffmana ===

Drzewo:
[11]
├── 0: 'A' (freq=5)
└── 1: [6]
    ├── 0: 'R' (freq=2)
    └── 1: [4]
        ├── 0: 'B' (freq=2)
        └── 1: [2]
            ├── 0: 'C' (freq=1)
            └── 1: 'D' (freq=1)

============================================================
Interpretacja:
============================================================

Ścieżki od korzenia do liści to kody:
  'A': 0
  'R': 10
  'B': 110
  'C': 1110
  'D': 1111

Optymalnść algorytmu Huffmana

Dowód optymalności


Obliczanie średniej długości kodu

Wyjście:

=== Średnia długość kodu ===

Tekst: 'ABRACADABRA'
Liczba znaków: 11

Analiza kodów:
Znak     Częstość     P(char)      Kod      L(code)    P × L
----------------------------------------------------------------------
'A'      5            0.455        0        1          0.455
'B'      2            0.182        110      3          0.545
'C'      1            0.091        1110     4          0.364
'D'      1            0.091        1111     4          0.364
'R'      2            0.182        10       2          0.364

Średnia długość kodu: 2.091 bitów/znak
Kodowanie stałe:      3 bitów/znak
Oszczędność:          0.909 bitów/znak (30.3%)

Praktyczne zastosowania

1. Kompresja pliku tekstowego

Wyjście (fragment):

=== Kompresja pliku tekstowego ===

Oryginalny tekst:
  "TO BE OR NOT TO BE THAT IS THE QUESTION"

[... budowanie drzewa ...]

======================================================================
WYNIKI KOMPRESJI:
======================================================================

Rozmiar oryginalny:    312 bitów (39 bajtów)
Rozmiar skompresowany: 127 bitów
Narzut drzewa:         160 bitów
Całkowity rozmiar:     287 bitów (35.9 bajtów)

Współczynnik kompresji: 8.0%

2. Kompresja vs częstości znaków

Wyjście:

=== Wpływ rozkładu częstości na kompresję ===

Typ rozkładu                   Oryg (b)     Komp (b)     Kompresja %
======================================================================
Równomierny (1 znak)           80           26            67.5%
Równomierny (4 znaki)          80           84            -5.0%
Nierównomierny (A dominuje)    80           51            36.2%
Bardzo nierównomierny          80           43            46.2%
Całkowicie różnorodny          80          106           -32.5%

Wnioski: - Kompresja najlepsza dla nierównomiernych rozkładów - Dla równomiernych rozkładów może być ujemna (plik większy!)


Warianty algorytmu Huffmana

Adaptive Huffman Coding


Złożoność obliczeniowa

Analiza algorytmu

Operacja Złożoność Opis
Liczenie częstości O(n) Jeden przebieg przez tekst
Budowa drzewa O(k log k) k = liczba unikalnych znaków, operacje na kopcu
Generowanie kodów O(k) Przejście po drzewie
Kodowanie O(n) Jeden przebieg przez tekst
Dekodowanie O(n × L) L = max długość kodu
Całkowita O(n + k log k) Zazwyczaj k << n

gdzie: - n = długość tekstu - k = liczba unikalnych znaków


Podsumowanie

Kluczowe koncepcje

Pojęcie Definicja
Kodowanie prefiksowe Żaden kod nie jest prefiksem innego
Drzewo Huffmana Reprezentacja kodowania jako drzewa binarnego
Strategia zachłanna Łącz dwa najmniej częste symbole
Optymalnść Minimalna średnia długość kodu

Właściwości algorytmu Huffmana

Zalety: - Optymalne kodowanie prefiksowe - Prosty algorytm zachłanny - Wydajny (O(n + k log k)) - Dekodowanie jednoznaczne

Kompresja najlepsza gdy: - Rozkład częstości nierównomierny - Niektóre symbole bardzo częste - Długie teksty

Ograniczenia: - Wymaga przesłania drzewa - Słaba kompresja dla równomiernych rozkładów - Dla małych plików narzut drzewa może być duży

Zastosowania w praktyce

Formaty kompresji: - DEFLATE (ZIP, GZIP) - JPEG (kodowanie AC/DC) - MP3 (kodowanie danych)

Protokoły sieciowe: - HTTP/2 (HPACK - kompresja nagłówków) - WebSockets (kompresja wiadomości)

Algorytmy pokrewne: - Arithmetic coding (lepsza kompresja) - LZW (Lempel-Ziv-Welch) - Run-Length Encoding


Co dalej?

Po opanowaniu kodowania Huffmana, następne tematy to:

  1. Zaawansowane algorytmy kompresji:
  2. LZ77, LZ78, LZW
  3. Arithmetic Coding
  4. Burrows-Wheeler Transform

  5. Algorytmy grafowe zachłanne:

  6. Dijkstra (najkrótsza ścieżka)
  7. Prim i Kruskal (minimalne drzewo rozpinające)
  8. Kolorowanie grafów

  9. Teoria informacji:

  10. Entropia Shannona
  11. Granice kompresji
  12. Kodowanie kanałów

Zadania praktyczne

Zadanie 1: Kompresja pliku

Zaimplementuj pełny kompresor i dekompresor plików używający kodowania Huffmana. Zapisz zakodowane dane i drzewo do pliku.

Zadanie 2: Porównanie z RLE

Porównaj kompresję Huffmana z Run-Length Encoding dla różnych typów danych (tekst, obrazy binarne).

Zadanie 3: Adaptive Huffman

Zaimplementuj adaptacyjną wersję algorytmu Huffmana, która aktualizuje drzewo podczas kodowania.

Zadanie 4: Canonical Huffman

Zaimplementuj Canonical Huffman Coding - wariant który wymaga przesłania tylko długości kodów, nie całego drzewa.

Zadanie 5: Analiza entropii

Oblicz entropię Shannona dla tekstu i porównaj ze średnią długością kodu Huffmana. Czy są blisko siebie?


Następna lekcja: Gratuluję ukończenia sekcji o algorytmach zachłannych!