Serwis Edukacyjny w I-LO w Tarnowie Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej Autor artykułu: mgr Jerzy Wałaszek |
©2025 mgr Jerzy Wałaszek |
ProblemDany łańcuch należy przedstawić za pomocą możliwie małej liczby bitów. |
Z kompresją danych spotykamy się często w informatyce i jej zastosowaniach. Pliki kompresuje się, aby zajmowały mniej miejsca na nośnikach danych lub były szybciej transmitowane poprzez sieć. Aby zrozumieć ideę kompresji, spójrz na poniższy przykład:
Załóżmy, że mamy pewien ciąg znaków zbudowany z dużych liter od A do Z. Wygląda on następująco:
ABJHLHJAAAAOOOOAOALKAJJJJAKUAMMMMMUANGGGHAJJJAKKKKKKAKKKKKKKAB
Czy można go zapisać w krótszy sposób, czyli czy można go skompresować? Na początek zauważamy, że w ciągu występują powtarzające się znaki. Można je zapisać bardziej efektywnie, wprowadzając prostą zasadę: ciąg powtarzających się znaków zapisujemy jako liczbę i znak, który się powtarza, a liczba określa ilość tych powtórzeń. Gdy zastosujemy tę regułę, otrzymamy poniższy ciąg znaków:
ABJHLHJ4A4OAOALKA4JAKUA6MUAN3GHA3JA6KA7KAB
Tekst jest wyraźnie krótszy. Co więcej, możemy go z powrotem przetworzyć na tekst pierwotny, zastępując liczby odpowiednią ilością znaków. Zwróć jednakże uwagę, że taka kompresja była możliwa po wprowadzeniu dodatkowych znaków i reguł, mianowicie cyfr z umieszczonym tuż za nimi znakami do powtarzania (zaznaczyliśmy te pary czarnym kolorem tła). Te dodatkowe pary liczba-znak są interpretowane inaczej od reszty znaków tworzących nasz tekst.
Taki system kompresji był kiedyś stosowany, np. w grafice na komputerach 8-bitowych, gdzie na obrazie występują obszary w jednolitym kolorze (to jakby powtarzające się ciągi znaków). Oczywiście jest on prymitywny, ponieważ nie rozpoznaje powtarzających się struktur wieloznakowych, np: ABABABABABABABABAB. Znacznie lepszy system kompresji opracował amerykański informatyk David Huffman. Polega on na zastępowaniu znaków ciągami bitów o różnej długości – znaki występujące najczęściej posiadają kody najkrótsze, dzięki czemu sumaryczna liczba bitów jest mniejsza niż przy stosowaniu kodów o stałej długości. Zanim przejdziemy do omówienia metody kodowania Huffmana, przyjrzyjmy się tzw. kodom o stałej długości (ang. fixed length codes) i kodom bezprzystankowym (ang. prefix-free codes).
Załóżmy, że chcemy kodować 4 znaki: A, B, C i D. Znakom tym przypisujemy kody dwubitowe:
A → 00 B → 01 C → 10 D → 11
Załóżmy dalej, że mamy zakodować tekst:
ABACCDABBD
Każdą literę zastępujemy odpowiadającą jej grupą dwóch bitów:
ABACCDABBD → 00010010101100010111
Proces odwrotny jest równie prosty. Ciąg bitów dzielimy na grupy dwubitowe i każdą z tych grup zamieniamy na odpowiadającą jej literę:
01001000 → 01 00 10 00 → BACA
Tak działa kodowanie liter w standardzie ASCII, gdzie każdy znak to 8 bitów. Jak widzimy, kody o stałej liczbie bitów nie sprawiają kłopotów.
Uwaga techniczna:
W tekście odwołujemy się do bitów w cudzysłowach, aby dać do zrozumienia, że faktycznie u nas są to znaki 0 lub 1 kodu ASCII, a nie prawdziwe bity. Pamiętaj o tym!
Jeśli chcemy stosować kody o różnej liczbie bitów, to muszą one spełniać jeden podstawowy warunek: żaden kod nie może być przedrostkiem (czyli początkowym fragmentem) innego kodu. Dlaczego to jest takie ważne? Wyobraź sobie, że masz dwa kody:
A → 0 B → 00
Dostałeś ciąg bitów:
0000000…
Jaki znak jest na początku? A czy B? Zatem każdy kod dłuższy nie może posiadać na początku tych samych bitów co kod krótszy, inaczej czasami nie będziemy w stanie ich rozróżnić.
Kody bezprzystankowe reprezentuje się drzewem binarnym. Interpretacja takiego drzewa jest bardzo prosta. Liść określa zawsze kodowany znak. Ścieżka prowadząca od korzenia drzewa do danego liścia określa kod znaku: gałąź lewa ma wartość bitową 0, gałąź prawa ma wartość bitową 1. Idąc po ścieżce, zbieramy kolejne bity aż dojdziemy do liścia. W ten sposób otrzymamy kod znaku w liściu. Oto przykład:
A → 0 B → 10 C → 110 D → 111 |
Zwróć uwagę, że węzeł nie będący liściem posiada zawsze dwóch synów. Czy potrafisz uzasadnić tę własność drzewa kodu bezprzystankowego? Druga cecha to niezrównoważenie drzewa. Skoro kody mają posiadać różną długość, to ścieżki również muszą być różnej długości, a taką cechę posiadają tylko drzewa niezrównoważone.
Węzeł drzewa powinien posiadać trzy pola: left, right (wskazania lewego i prawego syna) oraz key (kodowany znak).
Pascaltype PTnode = ^Tnode; Tnode = record left : PTnode; right : PTnode; key : char; end; |
struct Tnode { Tnode * left; Tnode * right; char key; }; |
BasicType Tnode left As Tnode Ptr right As Tnode Ptr key As String * 1 End Type |
Python (dodatek) |
class Tnode: def __init__(self, k): self.left = None self.right = None self.key = k |
Zasada tworzenia drzewa kodu bezprzystankowego jest następująca:
Zapamiętujemy kodowany znak i przystępujemy do odczytu kolejnych bitów jego kodu w pętli, ustawiając węzeł bieżący na korzeń drzewa. Jeśli nie ma dalszych bitów, to zapamiętany znak umieszczamy w bieżącym węźle. Inaczej odczytujemy bit kodu. Jeśli ma on wartość 0 i nie istnieje lewy syn bieżącego węzła, to tworzymy lewego syna. Jeśli ma wartość 1 i prawy syn nie istnieje, to tworzymy go. Przechodzimy do lewego syna (dla bitu 0) lub do prawego syna (dla bitu 1) i kontynuujemy pętlę odczytu bitów.
Gdy pętla się zakończy, przechodzimy do definicji kolejnego znaku i całość procedury powtarzamy. Jeśli brak dalszych definicji, kończymy. Drzewo jest gotowe.
K01: Dla i = 1, 2, …, n: wykonuj kroki K02…K19 K02: Czytaj s, b ; odczytujemy znak oraz jego kod ; ze strumienia wejściowego K03: p ← root ; ustawiamy węzeł bieżący na węzeł startowy K04: Dla j = 0, 1, …, |b|-1: ; przetwarzamy kolejne "bity" kodu wykonuj kroki K05…K18 K05: Jeśli b[j] = "1", ; przetwarzamy "bit" 0 to idź do kroku K13 K06: Jeśli p→left ≠ nil, ; sprawdzamy, czy bieżący węzeł to idź do kroku K11 ; ma lewego syna K07: Utwórz nowy węzeł ; jeśli nie, to go tworzymy K08: p→left ← adres nowego węzła K09: p→left→left ← nil ; ustawiamy pola nowego węzła K10: p→left→right ← nil K11: p ← p→left ; przechodzimy do lewego syna K12: Następny obieg pętli K04 ; i kontynuujemy pętlę K13: Jeśli p→right ≠ nil, ; to samo dla "bitu" 1 to idź do kroku K18 K14: Utwórz nowy węzeł K15: p→right ← adres nowego węzła K16: p→right→left ← nil K17: p→right→right ← nil K18: p ← p→right K19: p→key ← s ; znak umieszczamy w węźle K20: Zakończ
Zasada odczytu informacji zakodowanej kodem bezprzystankowym jest następująca (przy założeniu, że informacja nie zawiera błędów typu zbyt mała liczba bitów lub przekłamania bitów):
W pętli odczytujemy "bit" kodu i przechodzimy wzdłuż odpowiedniej krawędzi drzewa: dla "bitu" 0 wzdłuż krawędzi lewej, dla "bitu" 1 wzdłuż krawędzi prawej. Następnie sprawdzamy, czy doszliśmy do liścia. Jeśli tak, wypisujemy znak przechowywany w liściu i ustawiamy wskaźnik bieżącego węzła z powrotem na korzeń drzewa. Pętlę kontynuujemy aż do wyczerpania wszystkich bitów w ciągu wejściowym.
K01: Czytaj b ; odczytujemy ciąg "bitów" 0 i 1 ; ze strumienia wejściowego K02: p ← root ; ustawiamy wskaźnik ; na węzeł startowy drzewa K03: Dla i = 0, 1, …, |b|-1: ; w pętli przetwarzamy wykonuj kroki K04…K07 ; kolejne "bity" K04: Jeśli b[i] = "0", ; przechodzimy wzdłuż to p ← p→left ; odpowiedniej krawędzi Inaczej p ← p→right K05: Jeśli p→left ≠ nil, ; sprawdzamy, czy to następny obieg pętli K03 ; osiągnęliśmy liść K06: Pisz p→key ; jeśli tak, piszemy znak z węzła K07: p ← root ; wracamy na początek drzewa K08: Zakończ
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program jest przykładową aplikacją
dydaktyczną, która wczytuje definicję kodu bezprzystankowego, tworzy
na jej podstawie odpowiednie drzewo kodu, po czym wczytuje ciąg zer
i jedynek, w których zapisana jest wiadomość. Następnie odtwarza
wiadomość, korzystając z utworzonego drzewa kodu. Format danych
wejściowych jest następujący: Pierwsza liczba określa ilość słów kodowych n. Następnie występuje n wierszy typu znak, spacja, kod binarny. W ostatnim wierszu jest łańcuch z zakodowaną tym kodem wiadomością. |
Dane przykładowe (przekopiuj do schowka i
wklej do okna konsoli):
6 A 0 B 100 C 101 D 110 E 1110 F 1111 110111010101111010011100 |
Pascal// Kody bezprzystankowe // Data: 26.06.2013 // (C)2013 mgr Jerzy Wałaszek //--------------------------- program pfcodes; // Definicja węzła drzewa // kodu bezprzystankowego //----------------------- type PTnode = ^Tnode; Tnode = record left : PTnode; right : PTnode; key : char; end; // Tworzy z danych wejściowych // drzewo kodu bezprzystankowego // R - wskazanie korzenia //------------------------------ procedure make_t(var R : PTnode); var n, i, j : integer; s, x : char; b : string; p : PTnode; begin // Tworzymy węzeł startowy new(R); // Wypełniamy pola R^.left := nil; R^.right := nil; // Odczytujemy liczbę definicji readln(n); // W pętli tworzymy drzewo for i := 1 to n do begin // Czytamy znak, spację x i kod readln(s, x, b); // Rozpoczynamy od korzenia p := R; // Przetwarzamy kolejne bity kodu for j := 1 to length(b) do // Czy bit = 0? if b[j] = '0' then begin // Czy istnieje lewy syn? if p^.left = nil then begin // Jeśli nie, to go tworzymy new(p^.left); p^.left^.left := nil; p^.left^.right := nil; end; // Przechodzimy do lewego syna p := p^.left; end else // To samo dla bit = 1 begin // Czy istnieje prawy syn? if p^.right = nil then begin // Jeśli nie, to go tworzymy new(p^.right); p^.right^.left := nil; p^.right^.right := nil; end; // Przechodzimy do prawego syna p := p^.right; end; // Do liścia wstawiamy znak p^.key := s; end; end; // Dekoduje wiadomość // w kodzie bezprzystankowym // R - korzeń //-------------------------- procedure decode_t(R : PTnode); var p : PTnode; b : string; i : integer; begin // Odczytujemy kod readln(b); writeln; // Bieżący węzeł ustawiamy // na początek drzewa p := R; // Przetwarzamy kolejne bity kodu for i := 1 to length(b) do begin if b[i] = '0' then p := p^.left else p := p^.right; if p^.left = nil then begin write(p^.key); p := R; end; end; writeln; end; // Procedura DFS:postorder // usuwająca drzewo //------------------------ procedure dfs_release(v : PTnode); begin if v <> nil then begin // usuwamy lewe poddrzewo dfs_release(v^.left); // usuwamy prawe poddrzewo dfs_release(v^.right); // usuwamy sam węzeł dispose(v); end end; // ********************** // *** PROGRAM GŁÓWNY *** // ********************** var // Korzeń drzewa root : PTnode; begin // Inicjujemy korzeń root := nil; // Tworzymy drzewo // kodu bezprzystankowego make_t(root); // Dekodujemy wiadomość decode_t(root); // Usuwamy drzewo z pamięci dfs_release(root); end. |
// Kody bezprzystankowe // Data: 26.06.2013 // (C)2013 mgr Jerzy Wałaszek //--------------------------- #include <iostream> #include <string> using namespace std; // Definicja węzła drzewa // kodu bezprzystankowego //----------------------- struct Tnode { Tnode * left; Tnode * right; char key; }; // Tworzy z danych wejściowych // drzewo kodu bezprzystankowego //------------------------------ void make_t(Tnode * & root) { int n, i, j; char s; string b; Tnode * p; // Tworzymy węzeł startowy root = new Tnode; // Wypełniamy pola root->left = NULL; root->right = NULL; // Odczytujemy liczbę definicji cin >> n; // W pętli tworzymy drzewo for(i = 0; i < n; i++) { // Czytamy znak i kod cin >> s >> b; // Rozpoczynamy od korzenia p = root; // Przetwarzamy kolejne bity kodu for(j = 0; j < int(b.length()); j++) // Czy bit = 0? if(b [j] == '0') { // Czy istnieje lewy syn? if(!p->left) { // Jeśli nie, to go tworzymy p->left = new Tnode; p->left->left = NULL; p->left->right = NULL; } // Przechodzimy do lewego syna p = p->left; } else // To samo dla bit = 1 { // Czy istnieje prawy syn? if(!p->right) { // Jeśli nie, to go tworzymy p->right = new Tnode; p->right->left = NULL; p->right->right = NULL; } // Przechodzimy do prawego syna p = p->right; } // Do liścia wstawiamy znak p->key = s; } } // Dekoduje wiadomość // w kodzie bezprzystankowym //-------------------------- void decode_t(Tnode * root) { Tnode * p; string b; int i; // Odczytujemy kod cin >> b; cout << endl; // Bieżący węzeł ustawiamy // na początek drzewa p = root; // Przetwarzamy kolejne bity kodu for(i = 0; i < int(b.length()); i++) { if(b [i] == '0') p = p->left; else p = p->right; if(!p->left) { cout << p->key; p = root; } } cout << endl; } // Procedura DFS:postorder // usuwająca drzewo //------------------------ void dfs_release(Tnode * v) { if(v) { // usuwamy lewe poddrzewo dfs_release(v->left); // usuwamy prawe poddrzewo dfs_release(v->right); // usuwamy sam węzeł delete v; } } // ********************** // *** PROGRAM GŁÓWNY *** // ********************** int main() { // Korzeń drzewa Tnode * root; // Tworzymy drzewo kodu bezprzystankowego make_t(root); // Dekodujemy wiadomość decode_t(root); // Usuwamy drzewo z pamięci dfs_release(root); return 0; } |
Basic' Kody bezprzystankowe ' Data: 26.06.2013 ' (C)2013 mgr Jerzy Wałaszek '--------------------------- ' Definicja węzła ' drzewa kodu bezprzystankowego '------------------------------ Type Tnode left As Tnode Ptr right As Tnode Ptr key As String * 1 End Type ' Tworzy z danych wejściowych ' drzewo kodu bezprzystankowego '------------------------------ Sub make_t(ByRef root As Tnode Ptr) Dim As Integer n, i, j Dim As String * 1 s, x Dim As String b Dim As Tnode Ptr p ' Tworzymy węzeł startowy root = new Tnode ' Wypełniamy pola root->left = 0 root->right = 0 ' Odczytujemy liczbę definicji Input #1, n ' W pętli tworzymy drzewo For i = 1 To n ' Czytamy znak i spację s = Input(2, #1) ' Czytamy kod Input #1, b ' Rozpoczynamy od korzenia p = root ' Przetwarzamy kolejne bity kodu For j = 1 To Len(b) ' Czy bit = 0? If Mid(b, j, 1) = "0" Then ' Czy istnieje lewy syn? If p->Left = 0 Then ' Jeśli nie, to go tworzymy p->left = new Tnode p->left->left = 0 p->left->right = 0 End If ' Przechodzimy do lewego syna p = p->Left Else ' To samo dla bit = 1 ' Czy istnieje prawy syn? if p->Right = 0 Then ' Jeśli nie, to go tworzymy p->right = new Tnode p->right->left = 0 p->right->right = 0 End If ' Przechodzimy do prawego syna p = p->Right End If Next ' Do liścia wstawiamy znak p->key = s Next End Sub ' Dekoduje wiadomość ' w kodzie bezprzystankowym '-------------------------- Sub decode_t(ByVal root As Tnode Ptr) Dim As Tnode Ptr p Dim As String b Dim As Integer i ' Odczytujemy kod Input #1, b Print ' Bieżący węzeł ustawiamy ' na początek drzewa p = root ' Przetwarzamy kolejne bity kodu For i = 1 To Len(b) If Mid(b, i, 1) = "0" Then p = p->Left Else p = p->Right End If If p->Left = 0 then print p->key; p = root End If Next Print End Sub ' Procedura DFS:postorder usuwająca drzewo '----------------------------------------- Sub dfs_release (ByVal v As Tnode Ptr) If v Then ' usuwamy lewe poddrzewo dfs_release(v->left) ' usuwamy prawe poddrzewo dfs_release(v->right) ' usuwamy sam węzeł delete v End If End Sub ' ********************** ' *** PROGRAM GŁÓWNY *** ' ********************** ' Korzeń drzewa Dim As Tnode Ptr root Open Cons For Input As #1 ' Tworzymy drzewo kodu bezprzystankowego make_t(root) ' Dekodujemy wiadomość decode_t(root) ' Usuwamy drzewo z pamięci dfs_release(root) Close #1 End |
Python (dodatek) |
# Kody bezprzystankowe # Data: 9.08.2024 # (C)2024 mgr Jerzy Wałaszek #--------------------------- # Klasa węzła # drzewa kodu bezprzystankowego class Tnode: def __init__(self, k): self.left = None self.right = None self.key = k # Tworzy z danych wejściowych # drzewo kodu bezprzystankowego # r - korzeń def make_t(): # Tworzymy pusty węzeł startowy r = Tnode('') # Odczytujemy liczbę definicji n = int(input()) # W pętli tworzymy drzewo for i in range(n): # Czytamy znak kod arr = input().split() s = arr[0] b = arr[1] # Rozpoczynamy od korzenia p = r # Przetwarzamy kolejne bity kodu for j in b: # Czy bit = 0? if j == "0": # Czy istnieje lewy syn? if not p.left: # Jeśli nie, to go tworzymy p.left = Tnode('') # Przechodzimy do lewego syna p = p.left else: # To samo dla bit = 1 # Czy istnieje prawy syn? if not p.right: # Jeśli nie, to go tworzymy p.right = Tnode('') # Przechodzimy do prawego syna p = p.right # Do liścia wstawiamy znak p.key = s return r # Dekoduje wiadomość # w kodzie bezprzystankowym # r - korzeń drzewa def decode_t(r): # Odczytujemy kod b = input() print() # Bieżący węzeł ustawiamy # na początek drzewa p = r # Przetwarzamy kolejne bity kodu for i in b: if i == "0": p = p.left else: p = p.right if not p.left: print(p.key, end="") p = r print() # Procedura DFS:postorder # usuwająca drzewo def dfs_release(v): if v: # usuwamy lewe poddrzewo dfs_release(v.left) # usuwamy prawe poddrzewo dfs_release(v.right) # zerujemy odwołania v.left = None v.right = None # ********************** # *** PROGRAM GŁÓWNY *** # ********************** # Tworzymy drzewo # kodu bezprzystankowego root = make_t() # Dekodujemy wiadomość decode_t(root) # Usuwamy drzewo z pamięci dfs_release(root) |
Wynik: |
6 A 0 B 100 C 101 D 110 E 1110 F 1111 110111010101111010011100 DECAFABEA |
Tłumaczenie wiadomości na kod bezprzystankowy polega zawsze na znajdowaniu odpowiedniego kodu dla tłumaczonego znaku. Jeśli mamy do dyspozycji drzewo kodu, to możemy na przykład wykorzystać rekurencyjny algorytm przechodzenia drzewa, np. preorder. Może to działać następująco:
Metoda taka praktycznie nie byłaby stosowana, posiada ona jedynie wartość
dydaktyczną. W praktyce drzewo kodu przystankowego zostałoby przekształcone
w odpowiednie drzewo poszukiwań binarnych, w którym kluczami byłyby kodowane
znaki. Dodatkowo węzły przechowywałyby informację o kodzie znaku. Kodowanie
polegałoby na wyszukaniu węzła z kodowanym znakiem i użyciu kodu
przechowywanego w tym węźle. Takie podejście skracałoby czas poszukiwań do klasy
K01: Jeśli p→left ≠ nil, ; sprawdzamy, czy węzeł p jest liściem to idź do kroku K05 K02: Jeśli c ≠ p→key, ; sprawdzamy, czy węzeł przechowuje kodowany znak to zakończ z wynikiem false K03: Pisz b ; jeśli tak, to na wyjście wyprowadzamy kod K04: Zakończ z wynikiem true K05: Jeśli code_t(c, p→left, b+"0") = true, ; wywołanie rekurencyjne dla lewego syna to zakończ z wynikiem true K06: Zakończ z wynikiem code_t(c, p→right, b+"1") ; wywołanie rekurencyjne dla prawego syna
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program jest przykładową aplikacją
dydaktyczną, która wczytuje definicję kodu bezprzystankowego, tworzy
na jej podstawie odpowiednie drzewo kodu, po czym wczytuje łańcuch z wiadomością zbudowaną ze znaków kodu. Znaki te zostają zakodowane
zgodnie z definicją drzewa kodu, a wynik jest wyświetlany w oknie
konsoli. Format danych wejściowych jest następujący: Pierwsza liczba określa ilość słów kodowych n. Następnie występuje n wierszy typu znak, spacja, kod binarny. W ostatnim wierszu jest łańcuch z wiadomością do zakodowania. |
Dane przykładowe (przekopiuj do schowka i
wklej do okna konsoli):
6 A 0 B 100 C 101 D 110 E 1110 F 1111 DECAFABEA |
Pascal// Kodowanie kodem bezprzystankowym // Data: 27.06.2013 // (C)2013 mgr Jerzy Wałaszek //--------------------------------- program pfcodes; // Definicja węzła drzewa // kodu bezprzystankowego //----------------------- type PTnode = ^Tnode; Tnode = record left : PTnode; right : PTnode; key : char; end; // Tworzy z danych wejściowych // drzewo kodu bezprzystankowego //------------------------------ procedure make_t(var root : PTnode); var n, i, j : integer; s, x : char; b : string; p : PTnode; begin // Tworzymy węzeł startowy new(root); // Wypełniamy pola root^.left := nil; root^.right := nil; // Odczytujemy liczbę definicji readln(n); // W pętli tworzymy drzewo for i := 1 to n do begin // Czytamy znak, spację x i kod readln(s, x, b); // Rozpoczynamy od korzenia p := root; // Przetwarzamy kolejne bity kodu for j := 1 to length(b) do // Czy bit = 0? if b[j] = '0' then begin // Czy istnieje lewy syn? if p^.left = nil then begin // Jeśli nie, to go tworzymy new(p^.left); p^.left^.left := nil; p^.left^.right := nil; end; // Przechodzimy do lewego syna p := p^.left; end else // To samo dla bit = 1 begin // Czy istnieje prawy syn? if p^.right = nil then begin // Jeśli nie, to go tworzymy new (p^.right); p^.right^.left := nil; p^.right^.right := nil; end; // Przechodzimy do prawego syna p := p^.right; end; // Do liścia wstawiamy znak p^.key := s; end; end; // Koduje wiadomość za pomocą // kodu bezprzystankowego //--------------------------- function code_t(c : char; p : PTnode; b : string) : boolean; begin if p^.left = nil then begin if c <> p^.key then code_t := false else begin write(b); code_t := true; end; end else code_t := code_t(c, p^.left, b+'0') or code_t(c, p^.right, b+'1'); end; // Procedura DFS:postorder // usuwająca drzewo //------------------------ procedure dfs_release(v : PTnode); begin if v <> nil then begin // usuwamy lewe poddrzewo dfs_release(v^.left); // usuwamy prawe poddrzewo dfs_release(v^.right); // usuwamy sam węzeł dispose(v); end end; // ********************** // *** PROGRAM GŁÓWNY *** // ********************** var // Korzeń drzewa root : PTnode; // Kodowana wiadomość s : string; i : integer; begin root := nil; // Tworzymy drzewo // kodu bezprzystankowego make_t(root); // Odczytujemy wiadomość readln(s); for i := 1 to length(s) do // Kodujemy poszczególne znaki code_t(s[i], root, ''); writeln; // Usuwamy drzewo z pamięci dfs_release(root); end. |
// Kodowanie kodem bezprzystankowym // Data: 27.06.2013 // (C)2013 mgr Jerzy Wałaszek //--------------------------------- #include <iostream> #include <string> using namespace std; // Definicja węzła drzewa // kodu bezprzystankowego //----------------------- struct Tnode { Tnode * left; Tnode * right; char key; }; // Tworzy z danych wejściowych // drzewo kodu bezprzystankowego //------------------------------ void make_t(Tnode * & root) { int n, i, j; char s; string b; Tnode * p; // Tworzymy węzeł startowy root = new Tnode; // Wypełniamy pola root->left = NULL; root->right = NULL; // Odczytujemy liczbę definicji cin >> n; // W pętli tworzymy drzewo for(i = 0; i < n; i++) { // Czytamy znak, spację x i kod cin >> s >> b; // Rozpoczynamy od korzenia p = root; // Przetwarzamy kolejne bity kodu for(j = 0; j < (int)b.length(); j++) // Czy bit = 0? if(b[j] == '0') { // Czy istnieje lewy syn? if(!p->left) { // Jeśli nie, to go tworzymy p->left = new Tnode; p->left->left = NULL; p->left->right = NULL; } // Przechodzimy do lewego syna p = p->left; } else // To samo dla bit = 1 { // Czy istnieje prawy syn? if(!p->right) { // Jeśli nie, to go tworzymy p->right = new Tnode; p->right->left = NULL; p->right->right = NULL; } // Przechodzimy do prawego syna p = p->right; } // Do liścia wstawiamy znak p->key = s; } } // Koduje wiadomość za pomocą // kodu bezprzystankowego //--------------------------- bool code_t(char c, Tnode * p, string b) { if(!p->left) { if(c != p->key) return false; else { cout << b; return true; } } else return code_t(c, p->left, b+"0") || code_t(c, p->right, b+"1"); } // Procedura DFS:postorder // usuwająca drzewo //------------------------ void dfs_release(Tnode * v) { if(v) { // usuwamy lewe poddrzewo dfs_release(v->left); // usuwamy prawe poddrzewo dfs_release(v->right); // usuwamy sam węzeł delete v; } } // ********************** // *** PROGRAM GŁÓWNY *** // ********************** int main() { // Korzeń drzewa Tnode * root; // Kodowana wiadomość string s; unsigned int i; // Tworzymy drzewo // kodu bezprzystankowego make_t(root); // Odczytujemy wiadomość cin >> s; // Kodujemy poszczególne znaki for(i = 0; i < s.length(); i++) code_t(s[i], root, ""); cout << endl; // Usuwamy drzewo z pamięci dfs_release(root); return 0; } |
Basic' Kodowanie kodem bezprzystankowym ' Data: 27.06.2013 ' (C)2013 mgr Jerzy Wałaszek '--------------------------------- ' Definicja węzła drzewa ' kodu bezprzystankowego '----------------------- Type Tnode left As Tnode Ptr right As Tnode Ptr key As String * 1 End Type ' Tworzy z danych wejściowych ' drzewo kodu bezprzystankowego '------------------------------ Sub make_t(ByRef root As Tnode Ptr) Dim As Integer n, i, j Dim As String * 1 s, x Dim As String b Dim As Tnode Ptr p ' Tworzymy węzeł startowy root = new Tnode ' Wypełniamy pola root->left = 0 root->right = 0 ' Odczytujemy liczbę definicji Input #1, n ' W pętli tworzymy drzewo For i = 1 To n ' Czytamy znak i spację s = Input(2, #1) ' Czytamy kod Input #1, b ' Rozpoczynamy od korzenia p = root ' Przetwarzamy kolejne bity kodu For j = 1 To Len(b) ' Czy bit = 0? If Mid(b, j, 1) = "0" Then ' Czy istnieje lewy syn? If p->Left = 0 Then ' Jeśli nie, to go tworzymy p->left = new Tnode p->left->left = 0 p->left->right = 0 End If ' Przechodzimy do lewego syna p = p->Left Else ' To samo dla bit = 1 ' Czy istnieje prawy syn? if p->Right = 0 Then ' Jeśli nie, to go tworzymy p->right = new Tnode p->right->left = 0 p->right->right = 0 End If ' Przechodzimy do prawego syna p = p->Right End If Next ' Do liścia wstawiamy znak p->key = s Next End Sub ' Koduje wiadomość za pomocą ' kodu bezprzystankowego '--------------------------- Function code_t(c As String, _ p As Tnode Ptr, _ b As String) _ As Integer If p->Left = 0 Then If c <> p->key Then Return 0 Else print b; return 1 End If Else Return code_t(c, p->left, b+"0") OrElse _ code_t(c, p->right, b+"1") End If End Function ' Procedura DFS:postorder ' usuwająca drzewo '------------------------ Sub dfs_release(ByVal v As Tnode Ptr) If v Then ' usuwamy lewe poddrzewo dfs_release(v->left) ' usuwamy prawe poddrzewo dfs_release(v->right) ' usuwamy sam węzeł delete v End If End Sub ' ********************** ' *** PROGRAM GŁÓWNY *** ' ********************** ' Korzeń drzewa Dim As Tnode Ptr root Dim As String s Dim As Integer i Open Cons For Input As #1 ' Tworzymy drzewo ' kodu bezprzystankowego make_t(root) ' Odczytujemy wiadomość Input #1, s ' Kodujemy poszczególne znaki For i = 1 To Len(s) code_t(Mid(s, i, 1), root, "") Next ' Usuwamy drzewo z pamięci dfs_release(root) Close #1 End |
Python (dodatek) |
# Kodowanie kodem bezprzystankowym # Data: 10.08.2024 # (C)2024 mgr Jerzy Wałaszek #--------------------------------- # Klasa węzła drzewa # kodu bezprzystankowego class Tnode: def __init__(self, k): self.left = None self.right = None self.key = k # Tworzy z danych wejściowych # drzewo kodu bezprzystankowego # r - korzeń def make_t(): # Tworzymy pusty węzeł startowy r = Tnode('') # Odczytujemy liczbę definicji n = int(input()) # W pętli tworzymy drzewo for i in range(n): # Czytamy znak kod arr = input().split() s = arr[0] b = arr[1] # Rozpoczynamy od korzenia p = r # Przetwarzamy kolejne bity kodu for j in b: # Czy bit = 0? if j == "0": # Czy istnieje lewy syn? if not p.left: # Jeśli nie, to go tworzymy p.left = Tnode('') # Przechodzimy do lewego syna p = p.left else: # To samo dla bit = 1 # Czy istnieje prawy syn? if not p.right: # Jeśli nie, to go tworzymy p.right = Tnode('') # Przechodzimy do prawego syna p = p.right # Do liścia wstawiamy znak p.key = s return r # Koduje wiadomość za pomocą # kodu bezprzystankowego def code_t(c, p, b): if not p.left: if c != p.key: return 0 else: print(b, end="") return 1 else: return code_t(c, p.left, b+"0") or \ code_t(c, p.right, b+"1") # Procedura DFS:postorder # usuwająca drzewo def dfs_release(v): if v: # usuwamy lewe poddrzewo dfs_release(v.left) # usuwamy prawe poddrzewo dfs_release(v.right) # zerrujemy odwołania v.left = None v.right = None # ********************** # *** PROGRAM GŁÓWNY *** # ********************** # Tworzymy drzewo # kodu bezprzystankowego root = make_t() # Odczytujemy wiadomość s = input() # Kodujemy poszczególne znaki for i in s: code_t(i, root, "") # Usuwamy drzewo z pamięci dfs_release(root) |
Wynik: |
6 A 0 B 100 C 101 D 110 E 1110 F 1111 DECAFABEA 110111010101111010011100 |
Kodowanie Huffmana polega na przypisywaniu znakom występującym najczęściej w kodowanej wiadomości kodów krótkich. Znaki pojawiające się rzadziej otrzymują kody dłuższe. To zróżnicowanie długości pozwala otrzymać kod o mniejszej liczbie bitów, niż gdybyśmy stosowali kody o stałej długości. W rozdziale tym opiszemy algorytm tworzenia drzewa kodu bezprzystankowego Huffmana. Reszta operacji została opisana powyżej.
Załóżmy, że chcemy zakodować następującą wiadomość:
ACBECAHCADFEGAFAGACBBADAAFAAEAGACAFABEFBCCFA
Obliczamy ilość wystąpień każdego znaku:
A : 16 B : 5 C : 7 D : 2 E : 4 F : 6 G : 3 H : 1
Gdybyśmy zakodowali ten tekst kodem o stałej długości, to przy 8 znakach potrzebowalibyśmy dla każdego z nich kodów 3-bitowych. W sumie tekst ten zająłby:
3×(16+5+7+2+4+6+3+1) = 132 bity
Tworzymy listę węzłów drzewa kodu
bezprzystankowego. Na liście umieszczamy kolejne węzły, które w docelowym drzewie będą liśćmi. Każdy węzeł przechowuje kodowany znak oraz częstość wystąpień tego znaku w kodowanym tekście. Lista jest uporządkowana wg rosnącej liczby wystąpień. Dla naszego tekstu pierwszy węzeł zawiera znak H, który w tekście pojawia się jeden raz. Ostatni węzeł zawiera znak A o największej liczbie wystąpień, równej 16. |
|
Dopóki na liście jest więcej niż jeden element,
będziemy powtarzać następującą operację: Z listy pobieramy dwa pierwsze węzły. Węzły te zawsze zawierają znaki o najmniejszej liczbie wystąpień. U nas są to węzły H:1 i D:2. |
|
Tworzymy nowy węzeł i dołączamy do niego dwa
pobrane z listy węzły. W nowym węźle zapamiętujemy sumę wystąpień znaków w dwóch pobranych węzłach. Węzeł nie musi pamiętać żadnego znaku, ponieważ w drzewie kodu bezprzystankowego będzie on węzłem wewnętrznym. Tutaj stosujemy znak '?". |
|
Utworzony węzeł dodajemy do listy, tak aby była
wciąż uporządkowana rosnąco. Tutaj trafi na początek listy. |
|
Powtarzamy powyżej opisane operacje dla dwóch pierwszych węzłów na liście: Odłączamy je od listy. |
|
Tworzymy nowy węzeł z sumą wystąpień odłączonych węzłów. Dołączamy do niego te dwa węzły. |
|
Węzeł wstawiamy na listę. | |
Powtarzamy te same operacje dla węzłów
E:4 i B:5. Odłączamy je od listy. Tworzymy nowy węzeł. Umieszczamy w nim sumę wystąpień znaków E i B. |
|
Po czym węzeł ten dołączamy do listy, zachowując jej uporządkowanie. Wykonujemy to samo dla pary węzłów ?:6 i F:6. |
|
Nowy węzeł ?:12 wstawiamy na listę przed
A:16. To samo dla C:7 i ?:9. |
|
Nowy węzeł ?:16 znów trafia przed
A:16. To samo dla ?:12 i ?:16. |
|
Tym razem nowy węzeł ?:28 trafia za
A:16. Na liście zostały tylko dwa węzły, A:16 i ?:26. Po raz ostatni wykonujemy dla nich opisane operacje. |
|
W ich wyniku na liście pozostaje jeden węzeł, który jest korzeniem drzewa kodu bezprzystankowego Huffmana. |
|
Z drzewa możemy odczytać kody dla poszczególnych znaków: A → 0 B → 1111 C → 110 D → 10001 E → 1110 F → 101 G → 1001 H → 10000 |
Kody Huffmana mają różną długość, jeśli w kodowanym tekście znaki różnią się częstością występowania – znak A ma najkrótszy kod, ponieważ występuje najczęściej. Nasza wiadomość zostanie zakodowana za pomocą następującej liczby bitów:
16×1+5×4+7×3+2×5+4×4+6×3+3×4+1×5 = 16+20+21+10+16+18+12+5 = 118 bitów zamiast 132 bitów przy kodzie o stałej długości. Współczynnik kompresji 89, 39%.
Algorytm kodowania kodem bezprzystankowym Huffmana składa się z trzech etapów:
Pierwszy etap jest dosyć prosty. Tworzymy pustą listę. Przeglądamy kolejne znaki tekstu. Dla każdego znaku sprawdzamy, czy na liście znajduje się węzeł z takim znakiem. Jeśli tak, to zwiększamy liczbę wystąpień znaku, która jest przechowywana w węźle. Jeśli nie, to na początku listy dopisujemy nowy węzeł z przetwarzanym znakiem, w którym liczba wystąpień jest ustawiona na 1. Gdy przeglądniemy wszystkie znaki tekstu, otrzymamy listę węzłów ze znakami występującymi w tekście oraz z liczbą ich wystąpień. Listę sortujemy rosnąco względem liczb wystąpień znaków. W naszym algorytmie wykorzystujemy sortowanie bąbelkowe, lecz można tutaj zastosować dowolny inny algorytm sortujący. Jednakże dla małej liczby elementów sortowanie bąbelkowe jest całkiem efektywne.
Drugi i trzeci etap opisaliśmy powyżej.
Ponieważ węzeł Huffmana jest elementem listy oraz drzewa, to musi posiadać następujące pola:
Pascaltype PHTnode = ^HTnode; HTnode = record next : PHTnode; left : PHTnode; right : PHTnode; ch : char; count : integer; end; |
struct HTnode { HTnode * next; HTnode * left; HTnode * right; char ch; int count; }; |
BasicType HTnode next As HTnode Ptr left As HTnode Ptr right As HTnode Ptr ch As String * 1 count As Integer End Type |
Python (dodatek) |
class HTnode: def __init__(self): self.next = None self.left = None self.right = None self.ch = '' self.count = 0 |
next : wskazuje następny element listy.
left, right : wskazuje lewego, prawego syna.
ch : kodowany znak.
count : ilość wystąpień znaku w tekście.
K01: root ← nil ; rozpoczynamy z pustą listą. ; Zmienna root wskazuje zawsze jej początek. K02: Dla i = 0, 1, …, |s|-1, ; w pętli zliczamy wykonuj kroki K03…K14 ; znaki tekstu K03: p ← root K04: Dopóki p ≠ nil p→ch ≠ s[i]: ; szukamy znaku wykonuj: p ← p→next ; w węzłach na liście K05: Jeśli p ≠ nil, to idź do kroku K14 K06: Utwórz nowy węzeł ; jeśli na liście nie ma węzła ; ze znakiem, to taki węzeł tworzymy K07: p ← adres nowego węzła K08: p→next ← root ; ustawiamy pola węzła, ; będzie on pierwszym elementem listy K09: p→left ← nil ; węzeł nie posiada synów K10: p→right ← nil K11: p→ch ← s[i] ; zapamiętujemy w węźle ; przetwarzany znak K12: p→count ← 0 ; liczba wystąpień początkowo ; równa zero K13: root ← p ; węzeł staje się pierwszym ; elementem listy K14: p→count ← p→count+1 ; zwiększamy o 1 ; liczbę wystąpień znaku K15: t ← true ; zakładamy, że lista jest uporządkowana K16: p ← root ; p wskazuje pierwszy element K17: Dopóki p→next ≠ nil: wykonuj kroki K18…K22 K18: Jeśli p→count ≤ p→next→count, ; sortujemy to idź do kroku K22 ; bąbelkowo K19: p→ch ↔ p→next→ch ; wymieniamy ze sobą ; zawartości węzłów (tylko pola ch i count) K20: p→count ↔ p→next→count K21: t ← false ; zaznaczamy, że lista ; nie była uporządkowana K22 p ← p→next ; przesuwamy się na kolejne ; dwa elementy listy K23: Jeśli t = false, ; jeśli lista nie była posortowana, to idź do kroku K15 ; to kolejny obieg sortujący K24: Zakończ
K01: v1 ← root ; pobieramy z listy dwa węzły K02: v2 ← v1→next K03: Jeśli v2 = nil, ; na liście tylko jeden węzeł? to zakończ K04: root ← v2→next ; nowy początek listy ; za pobranymi węzłami K05: Utwórz nowy węzeł K06: p ← adres nowego węzła K07: p→left ← v1 ; pobrane węzły stają się ; synami nowego węzła K08: p→right ← v2 K09: p→count ← v1→count+v2→count ; wyliczamy ; nową częstość K10: Jeśli root = nil p→count ≤ root→count, to p→next ← root ; węzeł wstawiamy root ← p ; z powrotem na listę Idź do kroku K01 K11: r ← root ; szukamy na liście miejsca dla węzła p K12: Dopóki r→next) ≠ nil p→count) > r→next→count, wykonuj: r ← r→next K13: p→next ← r→next ; węzeł p wstawiamy ; za węzłem wskazywanym przez r K14: r→next ← p K15: Idź do kroku K01
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program jest przykładową aplikacją dydaktyczną, która demonstruje zastosowanie kodowania Huffmana do kompresji danych tekstowych. Na wejściu należy podać dowolny łańcuch tekstowy zbudowany z przynajmniej dwóch różnych znaków. Następnie program utworzy kod bezprzystankowy Huffmana dla każdego znaku w łańcuchu i zakoduje łańcuch tym kodem. Na wyjściu otrzymujemy kody dla poszczególnych znaków oraz zakodowany tym kodem łańcuch wejściowy. |
Pascal// Kodowanie Huffmana // Data: 5.07.2013 // (C)2013 mgr Jerzy Wałaszek //--------------------------- program Huffman; // Definicja węzła drzewa kodu bezprzystankowego //---------------------------------------------- type PHTnode = ^HTnode; HTnode = record next : PHTnode; // Pole dla listy left : PHTnode; // Pola dla drzewa right : PHTnode; ch : char; count : integer; end; // Tworzy listę wierzchołków //-------------------------- procedure make_list(var root : PHTnode; s : string); var i, x : integer; cx : char; p : PHTnode; t : boolean; begin // Tworzymy pustą listę root := nil; // Tworzymy węzły i zliczamy znaki for i := 1 to length(s) do begin // Szukamy na liście znaku s[i] p := root; while(p <> nil) and (p^.ch <> s[i]) do p := p^.next; if p = nil then // Jeśli go nie ma, to tworzymy // dla niego nowy węzeł begin new(p); // Węzeł trafi na początek listy p^.next := root; // Ustawiamy pola węzła p^.left := nil; p^.right := nil; p^.ch := s[i]; p^.count := 0; // Wstawiamy węzeł na początek listy root := p; end; // Zwiększamy licznik wystąpień znaku inc(p^.count); end; // Listę sortujemy rosnąco względem count repeat // Zakładamy posortowanie listy t := true; // Sortujemy od pierwszego elementu p := root; while p^.next <> nil do begin if p^.count > p^.next^.count then begin // Wymieniamy zawartość // dwóch kolejnych elementów cx := p^.ch; p^.ch := p^.next^.ch; p^.next^.ch := cx; x := p^.count; p^.count := p^.next^.count; p^.next^.count := x; // Sygnalizujemy nieposortowanie listy t := false; end; // Przechodzimy do następnego elementu p := p^.next; end; until t; end; // Tworzy z listy drzewo Huffmana //------------------------------- procedure make_tree(var root : PHTnode); var p, r, v1, v2 : PHTnode; begin while true do begin // Pobieramy z listy dwa pierwsze węzły v1 := root; v2 := v1^.next; // Jeśli tylko jeden element, zakończ if v2 = nil then break; // Lista bez v1 i v2 root := v2^.next; // Tworzymy nowy węzeł new(p); // Dołączamy do niego v1 i v2 p^.left := v1; // i wyliczamy nową częstość p^.right := v2; p^.count := v1^.count+v2^.count; // Węzeł wstawiamy z powrotem na listę tak, // aby była uporządkowana if(root = nil) or (p^.count <= root^.count) then begin p^.next := root; root := p; continue; end; r := root; while(r^.next <> nil) and (p^.count > r^.next^.count) do r := r^.next; p^.next := r^.next; r^.next := p; end; end; // Drukuje zawartość drzewa Huffmana //---------------------------------- procedure print_tree(p : PHTnode; b : string); begin if p^.left = nil then writeln(p^.ch, ' ', b) else begin print_tree(p^.left, b+'0'); print_tree(p^.right, b+'1'); end; end; // Koduje znak //------------ function code_t(c : char; p : PHTnode; b : string) : boolean; begin if p^.left = nil then begin if c <> p^.ch then code_t := false else begin write (b); code_t := true; end; end else code_t := code_t(c, p^.left, b+'0') or code_t(c, p^.right, b+'1'); end; // Koduje tekst kodem Huffmana //---------------------------- procedure code_huffman(root : PHTnode; s : string); var i : integer; begin // Kodujemy poszczególne znaki for i := 1 to length(s) do code_t(s[i], root, ''); end; // Procedura DFS:postorder usuwająca drzewo //----------------------------------------- procedure dfs_release(v : PHTnode); begin if v <> nil then begin // usuwamy lewe poddrzewo dfs_release(v^.left); // usuwamy prawe poddrzewo dfs_release(v^.right); // usuwamy sam węzeł dispose(v); end end; // ********************** // *** Program główny *** // ********************** var // Początek listy / korzeń drzewa root : PHTnode; // Przetwarzany łańcuch s : string; begin // Czytamy łańcuch wejściowy readln(s); // Tworzymy listę wierzchołków make_list(root, s); // Tworzymy drzewo kodu Huffmana make_tree(root); // Wyświetlamy kody znaków print_tree(root, ''); // Kodujemy i wyświetlamy wynik code_huffman(root, s); // Usuwamy drzewo z pamięci dfs_release(root); writeln; writeln; end. |
// Kodowanie Huffmana // Data: 5.07.2013 // (C)2013 mgr Jerzy Wałaszek //--------------------------- #include <iostream> #include <string> using namespace std; // Definicja węzła drzewa // kodu bezprzystankowego //----------------------- struct HTnode { HTnode * next; HTnode * left; HTnode * right; char ch; int count; }; // Tworzy listę wierzchołków //-------------------------- void make_list(HTnode * & root, string s) { unsigned int i, x; char cx; HTnode * p; bool t; // Tworzymy pustą listę root = NULL; // Tworzymy węzły i zliczamy znaki for(i = 0; i < s.length(); i++) { // Szukamy na liście znaku s[i] p = root; while(p && (p->ch != s[i])) p = p->next; // Jeśli go nie ma, to if(!p) { // tworzymy dla niego nowy węzeł p = new HTnode; // Węzeł trafi na początek listy p->next = root; // Ustawiamy pola węzła p->left = NULL; p->right = NULL; p->ch = s[i]; p->count = 0; // Wstawiamy węzeł na początek listy root = p; } // Zwiększamy licznik wystąpień znaku p->count++; } // Listę sortujemy rosnąco względem count do { // Zakładamy posortowanie listy t = true; // Sortujemy od pierwszego elementu p = root; while(p->next) { if(p->count > p->next->count) { // Wymieniamy zawartość dwóch // kolejnych elementów cx = p->ch; p->ch = p->next->ch; p->next->ch = cx; x = p->count; p->count = p->next->count; p->next->count = x; // Sygnalizujemy nieposortowanie listy t = false; } // Przechodzimy do następnego elementu p = p->next; } } while(!t); } // Tworzy z listy drzewo Huffmana //------------------------------- void make_tree(HTnode * & root) { HTnode *p, *r, *v1, *v2; while(true) { // Pobieramy z listy dwa pierwsze węzły v1 = root; v2 = v1->next; // Jeśli tylko jeden element, zakończ if(!v2) break; // Lista bez v1 i v2 root = v2->next; // Tworzymy nowy węzeł p = new HTnode; // Dołączamy do niego v1 i v2 p->left = v1; p->right = v2; // i wyliczamy nową częstość p->count = v1->count+v2->count; // Węzeł wstawiamy z powrotem na listę, // tak aby była uporządkowana if(!root || (p->count <= root->count)) { p->next = root; root = p; continue; } r = root; while(r->next && (p->count > r->next->count)) r = r->next; p->next = r->next; r->next = p; } } // Drukuje zawartość drzewa Huffmana //---------------------------------- void print_tree(HTnode * p, string b) { if(!p->left) cout << p->ch << " " << b << endl; else { print_tree(p->left, b+"0"); print_tree(p->right, b+"1"); } } // Koduje znak //------------ bool code_t(char c, HTnode * p, string b) { if(!p->left) { if(c != p->ch) return false; else { cout << b; return true; } } else return code_t(c, p->left, b+"0") || code_t(c, p->right, b+"1"); } // Koduje tekst kodem Huffmana //---------------------------- void code_huffman(HTnode * root, string s) { unsigned int i; // Kodujemy poszczególne znaki for(i = 0; i < s.length(); i++) code_t(s [i], root, ""); } // Procedura DFS:postorder // usuwająca drzewo //------------------------ void dfs_release(HTnode * v) { if(v) { // usuwamy lewe poddrzewo dfs_release(v->left); // usuwamy prawe poddrzewo dfs_release(v->right); // usuwamy sam węzeł delete v; } } // ********************** // *** Program główny *** // ********************** int main() { // Początek listy / korzeń drzewa HTnode * root; // Przetwarzany łańcuch string s; // Czytamy łańcuch wejściowy getline(cin, s); // Tworzymy listę wierzchołków make_list(root, s); // Tworzymy drzewo kodu Huffmana make_tree(root); // Wyświetlamy kody znaków print_tree(root, ""); // Kodujemy i wyświetlamy wynik code_huffman(root, s); // Usuwamy drzewo z pamięci dfs_release (root); cout << endl << endl; return 0; } |
Basic' Kodowanie Huffmana ' Data: 5.07.2013 ' (C)2013 mgr Jerzy Wałaszek '--------------------------- ' Definicja węzła drzewa ' kodu bezprzystankowego '----------------------- Type HTnode next As HTnode Ptr left As HTnode Ptr right As HTnode Ptr ch As String * 1 count As Integer End Type ' Tworzy listę wierzchołków '-------------------------- Sub make_list(ByRef root As HTnode Ptr,_ s As String) Dim As Integer i, x, t Dim As string cx Dim As HTnode Ptr p ' Tworzymy pustą listę root = 0 ' Tworzymy węzły i zliczamy znaki For i = 1 To Len(s) ' Szukamy na liście znaku s[i] p = root while (p <> 0) AndAlso _ (p->ch <> Mid(s, i, 1)) p = p->next Wend ' Jeśli go nie ma, to If p = 0 Then ' tworzymy dla niego nowy węzeł p = new HTnode ' Węzeł trafi na początek listy p->next = root ' Ustawiamy pola węzła p->left = 0 p->right = 0 p->ch = Mid(s, i, 1) p->count = 0 ' Wstawiamy węzeł na początek listy root = p End If ' Zwiększamy licznik wystąpień znaku p->count += 1 Next ' Listę sortujemy rosnąco względem count do ' Zakładamy posortowanie listy t = 1 ' Sortujemy od pierwszego elementu p = root While p->Next If p->count > p->next->count Then ' Wymieniamy zawartość ' dwóch kolejnych elementów cx = p->ch p->ch = p->next->ch p->next->ch = cx x = p->count p->count = p->next->count p->next->count = x ' Sygnalizujemy nieposortowanie listy t = 0 End If ' Przechodzimy do następnego elementu p = p->Next Wend loop Until t = 1 End Sub ' Tworzy z listy drzewo Huffmana '------------------------------- sub make_tree(ByRef root As HTnode Ptr) Dim As HTnode Ptr p, r, v1, v2 While 1 ' Pobieramy z listy dwa pierwsze węzły v1 = root v2 = v1->Next ' Jeśli tylko jeden element, zakończ If v2 = 0 Then Exit While ' Lista bez v1 i v2 root = v2->Next ' Tworzymy nowy węzeł p = new HTnode ' Dołączamy do niego v1 i v2 p->left = v1 p->right = v2 ' i wyliczamy nową częstość p->count = v1->count+v2->count ' Węzeł wstawiamy z powrotem na listę, ' tak aby była uporządkowana if (root = 0) OrElse _ (p->count <= root->count) Then p->next = root root = p Continue While End If r = root while (r->Next <> 0) AndAlso _ (p->count > r->next->count) r = r->Next Wend p->next = r->Next r->next = p Wend End sub ' Drukuje zawartość drzewa Huffmana '---------------------------------- sub print_tree(p As HTnode Ptr, _ b As String) If p->Left = 0 Then Print p->ch; " ";b Else print_tree(p->left, b + "0") print_tree(p->right, b + "1") End If End Sub ' Koduje znak '------------ Function code_t(c As String, _ p As HTnode Ptr, _ b As String) _ As Integer If p->Left = 0 Then If c <> p->ch Then Return 0 Else print b; return 1 End If Else Return code_t(c, p->left, b+"0") OrElse _ code_t(c, p->right, b+"1") End If End Function ' Koduje tekst kodem Huffmana '---------------------------- sub code_huffman(root As HTnode Ptr, _ s As String) Dim As Integer i ' Kodujemy poszczególne znaki For i = 1 To Len(s) code_t(Mid(s, i, 1), root, "") Next End Sub ' Procedura DFS:postorder usuwająca drzewo '----------------------------------------- Sub dfs_release(ByVal v As HTnode Ptr) If v Then ' usuwamy lewe poddrzewo dfs_release(v->left) ' usuwamy prawe poddrzewo dfs_release(v->right) ' usuwamy sam węzeł delete v End If End Sub ' ********************** ' *** Program główny *** ' ********************** ' Początek listy / korzeń drzewa Dim root as HTnode Ptr ' Przetwarzany łańcuch Dim s As String ' Czytamy łańcuch wejściowy Line Input s ' Tworzymy listę wierzchołków make_list(root, s) ' Tworzymy drzewo kodu Huffmana make_tree(root) ' Wyświetlamy kody znaków print_tree(root, "") ' Kodujemy i wyświetlamy wynik code_huffman(root, s) ' Usuwamy drzewo z pamięci dfs_release(root) Print Print End |
Python (dodatek) |
# Kodowanie Huffmana # Data: 5.07.2013 # (C)2013 mgr Jerzy Wałaszek #--------------------------- # Definicja węzła drzewa # kodu bezprzystankowego #----------------------- class HTnode: def __init__(self,s): self.next = None self.left = None self.right = None self.ch = s self.count = 0 # Tworzy listę wierzchołków # s - tekst #-------------------------- def make_list(s): # Tworzymy pustą listę r = None # Tworzymy węzły i zliczamy znaki for i in range(len(s)): # Szukamy na liście znaku s[i] p = r while p and p.ch != s[i]: p = p.next # Jeśli go nie ma, to if not p: # tworzymy dla niego nowy węzeł p = HTnode(s[i]) # Węzeł trafi na początek listy p.next = r # Ustawiamy pola węzła p.left = None p.right = None p.count = 0 # Wstawiamy węzeł na początek listy r = p # Zwiększamy licznik wystąpień znaku p.count += 1 # Listę sortujemy rosnąco względem count while True: # Zakładamy posortowanie listy t = True # Sortujemy od pierwszego elementu p = r while p.next: if p.count > p.next.count: # Wymieniamy zawartość # dwóch kolejnych elementów p.ch, p.next.ch = p.next.ch, p.ch p.count, p.next.count = \ p.next.count, p.count # Sygnalizujemy nieposortowanie listy t = False # Przechodzimy do następnego elementu p = p.next if t: break return r # Tworzy z listy drzewo Huffmana # r - korzeń #------------------------------- def make_tree(r): while True: # Pobieramy z listy dwa pierwsze węzły v1 = r v2 = v1.next # Jeśli tylko jeden element, zakończ if not v2: break # Lista bez v1 i v2 r = v2.next # Tworzymy nowy węzeł p = HTnode('') # Dołączamy do niego v1 i v2 p.left = v1 p.right = v2 # i wyliczamy nową częstość p.count = v1.count+v2.count # Węzeł wstawiamy z powrotem na listę, # tak aby była uporządkowana if not r or p.count <= r.count: p.next = r r = p continue rr = r while rr.next and p.count > rr.next.count: rr = rr.next p.next = rr.next rr.next = p return r # Drukuje zawartość drzewa Huffmana #---------------------------------- def print_tree(p, b): if not p.left: print(p.ch, b) else: print_tree(p.left, b+"0") print_tree(p.right,b+"1") # Koduje znak #------------ def code_t(c, p, b): if not p.left: if c != p.ch: return 0 else: print(b, end="") return 1 else: return code_t(c, p.left, b+"0") or \ code_t(c, p.right, b+"1") # Koduje tekst kodem Huffmana # r - korzeń # s - tekst #---------------------------- def code_huffman(r, s): # Kodujemy poszczególne znaki for i in range(len(s)): code_t(s[i], r, "") # Procedura DFS:postorder usuwająca drzewo #----------------------------------------- def dfs_release(v): if v: # usuwamy lewe poddrzewo dfs_release(v.left) # usuwamy prawe poddrzewo dfs_release(v.right) # usuwamy sam węzeł v.left = None v.right = None # ********************** # *** Program główny *** # ********************** # Czytamy łańcuch wejściowy s = input() # Tworzymy listę wierzchołków root = make_list(s) # Tworzymy drzewo kodu Huffmana root = make_tree(root) # Wyświetlamy kody znaków print_tree(root, "") # Kodujemy i wyświetlamy wynik code_huffman(root, s) # Usuwamy drzewo z pamięci dfs_release(root) print() |
Wynik: |
ACBECAHCADFEGAFAGACBBADAAFAAEAGACAFABEFBCCFA A 0 H 10000 D 10001 G 1001 F 101 C 110 E 1110 B 1111 01101111111011001000011001000110111101001010101001011011111111010001001010011100 10010110010101111111010111111101101010 |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2025 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.