You are on page 1of 79

Arbori Huffman.

Implementare n C++

CUPRINS
-1-

INTRODUCERE.................................................................................................. CAPITOLUL 1 - CODIFICAREA HUFFMAN................................................ 1.1 Concepte generale despre compresia datelor.......................................... 1.2 Algoritmi entropici ................................................................................. 1.3 Compresie Huffman................................................................................. 1.4 Construcia arborelui Huffman................................................................ 1.4.1 Arborele binar Huffman............................................................. 1.4.2 Definiia arborilor binari optimali.............................................. 1.4.3 Construcia arborilor binari optimali......................................... 1.4.4 Algoritm de construcie a arborelui Huffman optimal........................................................................................ 1.5 Coduri Huffman........................................................................................ 1.5.1 Codificarea caracterelor............................................................ 1.5.2 Obinerea codificrii textului.................................................... 1.6 Realizarea algoritmul Huffman prin metoda greedy................................ 1.6.1 Tehnica greedy.......................................................................... 1.6.2 Codificarea Huffman prin metoda greedy................................ CAPITOLUL 2-ANALIZA ALGORITMULUI DE CODIFICARE HUFFMAN........................................................................... 2.1 Corectitudinea algoritmului Huffman....................................................... 2.2 Coduri prefix............................................................................................ 2.2.1 Coduri prefix. Arbore de codificare......................................... 2.2.2 Construcia codificrii prefix a lui Huffman............................. 2.3 Variante ale algoritmului lui Huffman......................................................................... 2.3.1 Metoda de codare dinamic Huffman...................................... 2.3.2 Algoritmul FGK.........................................................................
2.3.3 Algoritmul V..............................................................................

3 4 4 8 1 0 1 2 1 2 1 5 1 6 1 7 1 9 2 0 2 2 2 4 2 4 2 7 3 1 3 1 3 3 3 3 3 5 3 7 3 8 4 4 4 7 4

CAPITOLUL 3-IMPLEMENTAREA ALGORITMULUI DE


-2-

CODIFICARE/DECODIFICARE HUFFMAN............... 3.1. Implementarea algoritmului de compresie.............................................. 3.2 Implementarea algoritmului de expandare (decompresie)....................... 3.3 Rezultate obinute....... CONCLUZII......................................................................................................... . BIBLIOGRAFIE................................................................................................... ANEXA 1 Program C........................................................................................ ANEXA 2 Program C++....................................................................................

8 4 8 5 4 5 6 5 7 5 9 7 3 7 8

INTRODUCERE

n lucrarea de faa tratez metodele Huffman de codificare i comprimare a datelor, necesare pentru elaborarea unor algoritmi optimi care s micoreze spaiul necesar memorrii unui fiier. Tehnicile de compresie sunt utile pentru fiiere text, n care anumite caractere apar cu o frecven mai mare dect altele, pentru fiiere ce codific imagini sau sunt reprezentri digitale ale sunetelor ori ale unor semnale analogice, ce pot conine numeroase motive care se repet. Chiar dac astzi capacitatea dispozitivelor de memorare a crescut foarte mult, algoritmii de compresie a fiierelor rmn foarte importani, datorit volumului tot mai mare de informaii ce trebuie stocate. n plus, compresia este deosebit de util n comunicaii, transmiterea informaiilor fiind mult mai costisitoare dect prelucrarea lor. Una dintre cele mai rspndite tehnici de compresie a fiierelor text, care, n funcie de caracteristicile fiierului ce urmeaz a fi comprimat, conduce la reducerea spaiului de memorie necesar cu 20%-90%, a fost descoperit de D. Huffman n 1952 i poart numele de codificare (cod) Huffman. n loc de a utiliza un cod n care fiecare caracter s fie reprezentat pe 7 sau pe 8 bii (lungime fix), se utilizeaz un cod mai scurt pentru caracterele care sunt mai frecvente i coduri mai lungi pentru

-3-

cele care apar mai rar. n unele cazuri aceast metoda poate reduce dimensiunea fiierelor cu peste 70%, n special n cazul fiierelor de tip text. Lucrarea este structurat n trei capitole. Primul capitol prezint conceptul de compresie a datelor i metoda Huffman de compresie static. Tot aici am exemplificat aplicarea metodei pe un text i am construit arborele binar Huffman corespunztor textului respectiv. n al doilea capitol am demonstrat corectitudine algoritmului Huffman dedus n primul capitol, am exemplificat modul de codificare i decodificare a datelor cu metoda Huffman static i am prezentat alte variante ale algoritmului Huffman. n capitolul trei am implementat algoritmul de codificare i decodificare Huffman n limbajul C++. Tot aici am prezentat structurile de date utilizate pentru implementare i am explicat n detaliu modul de construcie al algoritmului.

CAPITOLUL 1 CODIFICAREA HUFFMAN

1.1 Concepte generale despre compresia datelor Noiunea de comprimare a datelor a aprut din necesitatea evident de a atinge rate mari de transfer n reele sau de a stoca o cantitate ct mai mare de informaii folosind ct mai puin spaiu. Compresia datelor este necesar n zilele noastre i este foarte des folosit, mai ales n domeniul aplicaiilor multimedia. Un compresor de date este o aplicaie care, pe baza unuia sau mai multor algoritmi de compresie, diminueaz spaiul necesar stocrii informaiei utile coninute de un anumit set de date. Pentru orice compresor de date este necesar condiia de existen a cel puin unui decompresor care, pe baza informaiilor furnizate de compresor, s poat reconstitui informaia care a fost comprimat. n cazul n care nu exist un decompresor, atunci datele comprimate devin inutile pentru utilizator deoarece acesta nu mai are acces la informaia stocat n arhiv (o arhiv reprezint rezultatul obinut n urma utilizrii unui compresor). Un compresor de date este format din urmtoarele elemente:
-4-

una sau mai multe surse de informaie; unul sau mai muli algoritmi de compresie; una sau mai multe transformri.

Sursa de informaie pentru un compresor poate fi constituit de unul sau mai multe fiiere sau de un flux de date care a fost transmis compresorului prin intermediul unei reele. Datele arhivate urmeaz s fie stocate ntr-o arhiv care urmeaz s fie pstrat local sau urmeaz s fie transmis mai departe. Exist dou categorii principale de algoritmi pentru compresia datelor: 1. algoritmi de compresie fr pierderi de date 1.1. algoritmi entropici de compresie a datelor (algoritmi care elimin redundana din cadrul seturilor de date): algoritmul Huffman algoritmul Shannon-Fano algoritmi de compresie aritmetic (acesta este cel mai performant algoritm entropic cunoscut). 1.2. algoritmi bazai pe dicionare Lempel Ziff 77 (LZ77), Lempel Ziff 78 (LZ78) variante ale algoritmilor din categoria LZ Run Length Encoding (RLE) Burrow-Wheeler Transform (BWT) transformarea delta

1.3. transformri fr pierderi de date

2. algoritmi de compresie cu pierderi de date 2.1. algoritmi folosii pentru compresia audio care se bazeaz pe proprietile undelor sonore i perceperea lor de ctre om; 2.2. algoritmi utilizai pentru compresia imaginilor digitale; 2.3. algoritmi utilizai pentru compresia datelor video. Algoritmii de compresie aparinnd celei de-a doua clase (cea cu pierderi de date) se folosesc mpreun cu cei din prima pentru atingerea unor rate mari de compresie .

-5-

Metodele de compresie text pot fi mprite n doua categorii: statistice i nestatistice. Metodele statistice utilizeaz un model probabilistic al sursei de informaie. Astfel de coduri sunt Shannon-Fano sau Huffman. Metodele ne-statistice utilizeaz alte principii de codare cum sunt cele bazate pe dicionare, deci nu folosesc probabilitile simbolurilor. In cazul metodelor statistice, un punct important n compresia datelor fr pierdere de informaie este dat de posibilitatea organizrii/descompunerii compresiei n dou funcii: modelare i codificare. Metoda statistic are la baz folosirea probabilitilor simbolurilor n stabilirea cuvintelor de cod. Daca modelul sursei este stabilit naintea etapei de codare, i rmne neschimbat n timpul codrii, metoda de compresie este una static. Algoritmul de compresie este unul mecanic prin nlocuirea fiecrui simbol la sursei cu cuvntul de cod corespunztor, i transmiterea acestuia pe canal. Metodele statice presupun transmiterea pe canal a informaiilor despre modelului sursei i/sau a codului folosit, astfel nct decodorul s poate efectua operaia de decodare. Canalul de comunicaie se considera fr erori. O metod static este aceea n care transformarea mulimii mesajelor n cuvinte de cod este fixat nainte de nceperea transmisiei, astfel nct un mesaj dat este reprezentat prin acelai cuvnt de cod de fiecare data cnd apare n mesajul global. Un exemplu de codare static, bazat de cuvinte de cod, este codarea Huffman (static). Aici asignarea cuvintelor la mesajele sursei este bazat pe probabilitile cu care mesajele sursei apar n mesajul global. Mesajele care apar mai frecvent sunt reprezentate prin cuvinte de cod mai scurte; mesajele cu probabiliti mici sunt reprezentate de cuvinte de cod mai lungi. La fiecare pas de codare este necesar s se estimeze probabilitile de utilizare a simbolurilor sursei. Codorul preia distribuia de probabilitate i stabilete codul folosit pentru compresie. Se estimeaz probabilitile de utilizare a simbolurilor sursei. Codorul preia distribuia de probabilitate i stabilete codul folosit pentru compresie.

-6-

Figura 1.1 Structura compresiei statice fr pierdere de informaie

Figura 1.2 Structura compresiei adaptive fr pierdere de informaie (lossless compression)

Un cod este dinamic (dynamic) dac codarea mulimii mesajelor sursei primare se schimb n timp. De exemplu, codarea Huffman dinamic presupune estimarea probabilitilor de apariie n timpul codrii, n timp ce mesajul este procesat. Asignarea cuvintelor de cod mesajului de transmis (de codificat) se bazeaz pe frecvenele de apariie la fiecare moment de timp. Un mesaj x poate fi reprezentat printr-un cuvnt de cod scurt la nceputul transmisiei pentru c apare frecvent la nceputul transmisiei, chiar dac probabilitatea sa de apariie raportat la ntreg ansamblu este mic. Mai trziu, cuvintele de cod scurte pot fi asignate altor mesaje dac frecvena de apariie se schimb. Codurile dinamice se mai numesc i coduri adaptive, care va fi folosit mai departe, ntruct ele se adapteaz la schimbrile mesajului n timp. Toate metodele adaptive sunt metode ntr-un singur pas: numai o singura trecere (scan) a realizrii
-7-

sursei este necesar. n opoziie, codarea Huffman static este n doi pai: un pas pentru determinarea probabilitilor i determinarea codului, i al doilea par, pentru codare. Metodele ntr-un singur pas sunt mai rapide. n plus, n cazul metodelor statice, codul determinat la primul pas trebuie transmis decodorului, mpreun cu mesajul codat. Codul se poate transmite la nceputul fiecrei transmisii sau acelai cod poate fi folosit pentru mai multe transmisii. n metodele adaptive, codorul definete i redefinete dinamic cuvintele de cod, n timpul transmisiei. Decodorul trebui s defineasc i s redefineasc codarea n acelai mod, n esena prin nvarea cuvintelor de cod n momentul recepionrii acestora. Dificultatea primar a utilizrii codrii adaptive este necesitatea folosirii unui buffer ntre surs i canal. Reelele de date aloc, deci, resurse de comunicaie sursei, n sensul alocrii de memorie (buffere) ca parte a sistemului de comunicaie. n final exist i metode hibride, care nu sunt complet statice sau complet dinamice. ntr-un astfel de caz simplu, emitorul i receptorul folosesc o mulime de coduri statice (codebooks). La nceputul fiecrei transmisii, emitorul alege i trebuie s transmit numrul (numele, identificatorul) codului folosit. Compresia textelor este un subset al compresiei datelor i se ocup cu acei algoritmi care au proprietatea ca toat informaia prezent n fiierul original, fiier necompresat, este prezent n fiierul comprimat i deci n fiierul expandat. Nu se accept o pierdere de informaie, chiar dac algoritmul de compresie poate aduga informaie redundant necesar pentru a efectua n bune condiii decompresia (expandarea). Aceste tehnici garanteaz obinerea unui fiier expandat identic cu cel original, existent naintea compresiei. Aceti algoritmi se numesc fr pierderi (lossless), reversibili sau fr zgomot (noiseless). Termenul text trebuie interpretat n sens larg. Este clar c un text poate fi scris n limbaj natural sau poate fi generat de translatoare (aa cum fac diverse compilatoare). Un text poate fi considerat c o imagine (rezultat dintr-o scanare, aa cum este cazul telefaxului) sau alte tipuri de structuri ce furnizeaz date n fiiere liniare.

-8-

1.2 Algoritmi entropici Majoritatea surselor de informaie din domeniul calculatoarelor i al aplicaiilor internet sunt discrete. Pentru a descrie o surs discret fr memorie (SDFM) sunt necesare dou mrimi: alfabetul sursei i probabilitile de furnizare a fiecrui simbol: s1 s 2 ... s N S : p p ... p ; 2 N 1
N p ( s k ) =1 k =1

(1.1)

Dac numrul de simboluri este finit, sursa se numete discret. Dac la un moment dat se emite sigur un simbol atunci sursa este complet. Sursa este fr memorie dac evenimentele sk sunt independente, adic furnizarea unui simbol la un moment dat nu depinde de simbolurile furnizate anterior. Totalitatea simbolurilor unei surse formeaz alfabetul sursei. Orice succesiune finit de simboluri, n particular un singur simbol, se numete cuvnt. Totalitatea cuvintelor formate cu un anumit alfabet se numete limbaj. Informaia furnizat de un simbol al sursei este:
i(s k ) = log p ( s k )

[bii]

(1.2)

Entropia este informaia medie pe simbol sau, altfel formulat, este incertitudinea medie asupra simbolurilor sursei S, sau informaia medie furnizat de un simbol.
N N H ( S ) = p ( s i )i ( s i ) = p ( s i ) log( p ( s i )) i =1 i =1

[bit/simbol]

(1.3)

Noiunea de informaie trebuie legat i de timp, ntruct, cel puin din punct de vedere al utilizatorului informaiei, nu este indiferent dac furnizarea unui simbol are loc ntr-o or sau ntr-un an. n acest sens, se definete debitul de informaie al unei surse discrete. Definiie - Debitul de informaie cantitatea medie de informaie furnizat n unitatea de timp.
H t (S ) = H (S ) 1 = H (S )

[ biti/s ]
-9-

(1.4)

unde este durata medie de furnizare a unui simbol:


= p ( s i ) i
i =1 N

[s/simbol]

(1.5)

Definiie: Redundana unei surse discrete de informaie este diferena dintre valoarea maxim posibil a entropiei i entropia sa:
R(S) = max{H(S)} H(S) biti [ / simbol ]

(1.6)

In cadrul compresiei se are n vedere sistemul de transmitere a informaiei din figura 1.1.1. Procesul de transformare a mesajelor sursei ntr-un mesaj codat se numete codare (coding sau encoding, n engleza). Algoritmul care construiete transformarea se numete codor (encoder, n engleza). Decodorul realizeaz transformarea invers, genernd mesajul n forma sa original. Canalul se consider fr erori, deci este canal fr perturbaii. Alfabetul codului este, n general, considera cazul binar. Codificarea a aprut ca o necesitate de schimbare a formei de prezentare a informaiei n scopul prelucrrii sau transmiterii acesteia. Codificarea poate fi uniform (bloc), dac se folosete aceeai lungime a cuvintelor de cod, sau neuniform (variabil), cnd lungimea cuvintelor de cod nu este constant. Operaia invers codificrii se numete decodificare, adic transformarea invers ce permite obinerea formei iniiale de reprezentare a informaiei. 1.3 Compresie Huffman Tehnicile de compresie sunt utile pentru fiiere text, n care anumite caractere apar cu o frecven mai mare dect altele, pentru fiiere ce codific imagini sau sunt reprezentri digitale ale sunetelor ori ale unor semnale analogice, ce pot conine numeroase motive care se repet. Chiar dac astzi capacitatea dispozitivelor de memorare a crescut foarte mult, algoritmii de compresie a fiierelor rmn foarte importani, datorit volumului tot mai mare de informaii ce trebuie stocate. n plus, compresia este deosebit de util n comunicaii, transmiterea informaiilor fiind mult mai costisitoare dect prelucrarea lor.
-10-

X={xk | k=1,2,...,D}. Pentru cazul compresiei sa va

Una dintre cele mai rspndite tehnici de compresie a fiierelor text, care, n funcie de caracteristicile fiierului ce urmeaz a fi comprimat, conduce la reducerea spaiului de memorie necesar cu 20%-90%, a fost descoperit de D. Huffman n 1952 i poart numele de codificare (cod) Huffman. n loc de a utiliza un cod n care fiecare caracter s fie reprezentat pe 7 sau pe 8 bii (lungime fix), se utilizeaz un cod mai scurt pentru caracterele care sunt mai frecvente i coduri mai lungi pentru cele care apar mai rar. S presupunem c avem un fiier de 100.000 de caractere din alfabetul {a,b,c,d,e,f}, pe care dorim s-l memorm ct mai compact. Dac am folosi un cod de lungime fix, pentru cele 6 caractere, ar fi necesari cte 3 bii. De exemplu, pentru codul:

a cod fix 000

b 001

c 010

d 011

e 100

f 101

ar fi necesari n total 300.000 bii. S presupunem acum c frecvenele cu care apar n text cele 6 caractere sunt :

a frecven 45

b 13

c 12

d 16

e 9

f 5

Considernd urmtorul cod de lungime variabil : a cod variabil 0 b 101 c 100 d 111 e 1101 f 1100

ar fi necesari doar 224.000 bii (deci o reducere a spaiului de memorie cu aproximativ 25%).
-11-

Problema se reduce deci la a asocia fiecrui caracter un cod binar, n funcie de frecven, astfel nct s fie posibil decodificarea fiierului comprimat, fr ambiguiti. De exemplu, dac am fi codificat a cu 1001 i b cu 100101, cnd citim n fiierul comprimat secvena 1001 nu putem decide dac este vorba de caracterul a sau de o parte a codului caracterului b. Ideea de a folosi separatori ntre codurile caracterelor pentru a nu crea ambiguiti ar conduce la mrirea dimensiunii codificrii. Pentru a evita ambiguitile este necesar ca nici un cod de caracter s nu fie prefix al unui cod asociat al unui caracter (un astfel de cod se numete cod prefix). 1.4 Constructia arborelui Huffman D. Huffman a elaborat un algoritm care construiete un cod prefix optimal, numit cod Huffman. Prima etap n construcia codului Huffman este calcularea numrului de apariii ale fiecrui caracter n text. 1.4.1 Arborele binar Huffman Exist situaii n care putem utiliza frecvenele standard de apariie a caracterelor, calculate n funcie de limb sau de specificul textului. Fie C={c1,c2,...,cn} mulimea caracterelor dintr-un text, iar f 1,f2,...,fn, respectiv, numrul lor de apariii. Dac li ar fi lungimea irului ce codific simbolul c i, atunci lungimea total a reprezentrii ar fi :

L = li fi
i =1

Scopul nostru este de a construi un cod prefix care s minimizeze aceast expresie. Pentru aceasta, construim un arbore binar complet n manier bottom-up astfel : -Iniial, considerm o partiie a mulimii C={ {c1,f1},{c2,f2}, ..., {cn,fn}}, reprezentat printr-o pdure de arbori formai dintr-un singur nod. -Pentru a obine arborele final, se execut n-1 operaii de unificare.

-12-

Unificarea a doi arbori A1 i A2 const n obinerea unui arbore A, al crui subarbore stng este A1, subarbore drept A2, iar frecvena rdcinii lui A este suma frecvenelor rdcinilor celor doi arbori. La fiecare pas unificm 2 arbori ale cror rdcini au frecvenele cele mai mici. De exemplu, arborele Huffman asociat caracterelor {a,b,c,d,e,f} cu frecvenele {45,13,12,16,9,5} se construiete pornind de la cele cinci noduri din figura 1:

Fig. 1.3 Pas 1: Unific arborii corespunztori lui e i f, deoarece au frecvenele cele mai mici:

Fig. 1.4 Pas 2: Unific arborii corespunztori lui b i c:

Fig. 1.5 Pas 3: Unific arborele corespunztor lui d i arborele obinut la primul pas, cu rdcina ce are frecvena 14:

Fig. 1.6 Pas 4: Unific arborii ce au frecvenele 25 i 30.


-13-

Fig. 1.7 Pas 5: Unificnd ultimii doi arbori, obin arborele Huffman asociat acestui set de caractere cu frecvenele specificate iniial.

Fig. 1.8 Nodurile terminale vor conine un caracter i frecvena corespunztoare caracterului; nodurile interioare conin suma frecvenelor caracterelor corespunztoare nodurilor terminale din subarbori. Arborele Huffman obinut va permite asocierea unei codificri binare fiecrui caracter. Caracterele fiind frunze n arborele obinut, se va asocia pentru fiecare deplasare la stnga pe drumul de la rdcin la nodul terminal corespunztor caracterului un 0, iar pentru fiecare deplasare la dreapta un 1. Obinem codurile : a cod 0 b 100 c 101 d 110 e 1110 f 1111

Observaii
-14-

- caracterele care apar cel mai frecvent sunt mai aproape de rdcin i astfel lungimea codificrii va avea un numr mai mic de bii. - la fiecare pas am selectat cele mai mici dou frecvene, pentru a unifica arborii corespunztori. Pentru extragerea eficient a minimului vom folosi un minheap. Astfel timpul de execuie pentru operaiile de extragere minim, inserare i tergere va fi, n cazul cel mai defavorabil, de O(log n). - arborii Huffman nu sunt unici. Adic, poi avea arbori diferii, pentru acelai set de frunze cu aceleai frecvene. De exemplu,dac ai frunze cu aceeai frecven, poziia unora fat de celelalte nu este important. 1.4.2 Definiia arborilor binari optimali Uneori se cunosc probabilitile de acces la cheile dintr-un arbore, astfel nct organizarea arborelui binar le poate lua n calcul. Se pune problema ca numrul total al pailor de cutare contorizat pentru un numr suficient de ncercri, s fie minim. Se definete drumul ponderat ca fiind suma tuturor drumurilor de la rdcin la fiecare nod, ponderate cu probabilitile de acces la noduri:

p i = p i hi
i =1

unde p i - ponderea nodului i


hi - nivelul nodului i.

-15-

Se urmrete minimizarea lungimii drumului ponderat, pentru o distribuie dat.

Fig. 1.9 Daca organizarea arborelui binar ia n considerare i probabilitile (ponderile) cutrilor nereuite, deci notnd cu q i probabilitatea cutrii unei chei cu valoarea ntre cheile k i i k i + 1 , lungimea drumului ponderat va fi:
P = pi hi + q j h j
i =1 j =0 n n

unde

pi + q j = 1
i =1 j =0

Se consider toate structurile de arbori binari care pot fi alctuii pornind de la setul de chei k i , avnd probabilitile asociate p i i q j . Se numete arbore binar optim, arborele a crui structur conduce la un cost minim. 1.4.3 Construcia arborilor binari optimali Arborii optimali au proprietatea c toi subarborii lor sunt optimali, sugernd algoritmul care construiete sistematic arbori din ce n ce mai mari, pornind de la nodurile individuale (subarbori de nlime 1), arborii crescnd de la frunze spre rdcin, conform metodei "bottom-up".

-16-

Notnd cu P lungimea drumului ponderat al arborelui, cu PS i PD lungimile drumurilor ponderate ale subarborilor stng i drept ai rdcinii i cu W ponderea arborelui, deci numrul de treceri prin rdcin (tocmai numrul total de cutari n arbore):
P = PS + W + PD W = ai + b j
i =1 j =0 n n

Media lungimii drumului ponderat e P/W.

1.4.4 Algoritm de construcie a arborelui Huffman optimal S-a realizat un algoritm cu o eficien O(n log n) pentru construirea unui arbore Huffman, datorit lui Hu i a lui Tucker. Se folosete un vector de noduri ce conin o frecven i pointeri ctre dreapta i stnga. Frunze Numr 1 2 . . . . . . . . . . . . f(i) f1 f2 f3 Stnga 0 0 0 Dreapta 0 0 0

-17-

. . . G I V E N n+1 Noduri interne n+2 . . . Rdcina 2n-1 . fn . . . . . . . . . N

. . . . . . . . . . . . . 0 0 . . . . . .

. . . . . . . . . . . . . 0 0 . . . . . .

n jumtatea de sus a tabelei, numerotat de la 1 la n, avem frunzele unui arbore Huffman. Acestea au anumite frecvene date, i nu au successor stng sau drept, De aceea se pune 0 pe coloanele respective. n partea de jos a tabelei, ntre n+1 i 2n-1, avem nodurile interne,incluznd rdcina, de pe poziia 2n-1.Frecvenele lor pot fi calculate de succesorii lor stng, respectiv drept. Dup cum vom vedea mai departe, cel mai slab caz este de complexitate O(n log n).

-18-

Alfabetul este aranjat ntr-un heap, care are, prin definiie, cele mai mici elemente mai sus. Cheile dup care este ordonat heap-ul sunt frecvenele caracterelor. Heap-ul este iniial format din n perechi (i, f(i)) corespunztoare frunzelor. La fiecare iteraie, dou elemente de frecvene minime sunt terse i este creat un nod intern.Frecvena acestui nod intern nou format este suma frecvenelor succesorilor si, i este reprezentat printr-un nou supercaracter. Acest nod este inserat n heap i procesul se repet.

Algoritmul Hu Tucker O(n) Se folosete heap-ul ce contine perechile (1,f(1)),..,(n,f(n)) FOR i = n+1 TO 2n-1 DO (l,fl) <--- Deletemin(Heap) (r,fr) <--- Deletemin(Heap) O(n log n) fi <--- fl + fr Insert ((i,fi), Heap) Left[i] <--- l Right[i] <--- r RETURN Instruciunea for care este executat de n-1 ori pentru un alfabet de n litere extrage n mod repetat cele dou noduri cu cele mai mici frecvene i le nlocuiete cu un alt nod, avnd frecvena suma frecventelor celorlalte dou noduri. Acest nou nod are drept descendeni nodurile anterioare cu frecvenele cele mai mici. Cel mai slab caz are complexitatea O(n log n).

-19-

1.5 Coduri Huffman Codurile Huffman constituie un exemplu de utilizare a arborilor binari ca structuri de date i reprezint o variant de codificare a unor caractere ce apar ntr-un limbaj, fiind determinat de probabilitile de apariie ale caracterelor, ale cror valori se cunosc.

1.5.1 Codificarea caracterelor Codul oricrui caracter e o secven de 0 sau 1, astfel nct s nu fie prefix pentru codul nici unui alt caracter. Se impune cerina ca lungimea medie a codurilor s fie minim, deci i lungimea mesajului codificat s fie minim. Algoritmul Huffman selecteaz dou caractere a i b care au probabilitile cele mai sczute de apariie i le nlocuiete cu un caracter imaginar x cu probabilitatea egala cu suma probabilitilor lui a i b. Aplicnd recursiv aceasta procedur, se reduce n final setul de caractere la dou, cel iniial cu probabilitatea cea mai mare i cel imaginar cu probabilitatea egal cu suma probabilitilor celorlalte caractere, cele dou caractere codificndu-se prin 0 i 1. Codul pentru setul original de caractere se obine adugnd la codul lui x un 0 la ntlnirea caracterului a i un 1 pentru b. Un cod prefix se poate asimila cu un drum ntr-un arbore binar, considernd c trecerea la fiul stng al unui nod adaug un 0 codului, la cel drept, 1. n implementarea algoritmului lui Huffman se folosete o colecie de arbori binari, ale cror noduri terminale sunt caractere i ale cror rdcini au asociat suma probabilitilor de apariie a caracterelor corespunztoare tuturor nodurilor terminale, numita greutatea (ponderea) arborelui. n continuare se descrie o variant de structuri de date necesare implementrii algoritmului. Pentru reprezentarea arborelui binar se folosete un tablou ARBORE, fiecrui nod rezervndui-se cte o intrare cu cursorii la fiul stng, la cel drept i la printe. Variabila de tip indice (cursor) ULTIM_NOD indica ultimul element ocupat al tabloului. Tabloul ALFABET nregistreaz pentru fiecare simbol probabilitatea sa de apariie i cursorul la nodul terminal asociat. Tabloul ZONA nregistreaz colecia
-20-

de arbori binari, fiecare nregistrare referindu-se la un arbore i cuprinznd greutatea arborelui i un cursor la rdcina sa din ARBORE; ULTIM_NOD indica ultima intrare ocupata din ZONA. var ARBORE:array[1..max1] of record fiu_stng, fiu_drept, parinte :integer {cursori n ARBORE} end; ULTIM_NOD:integer; ALFABET:array[1..max2] of record simbol:char; probabilit:real; terminal:integer {cursor n ARBORE} end; ZONA:array[1..max3] of record greutate:real; radacina:integer {cursor n ARBORE} end; ULTIM_ARB:integer; {cursor n ARBORE}

Exemplu: Se considera urmtorul alfabet, format din simbolurile a,b,c,d, avnd probabilitile: P(a)=15% P(b)=52% P(c)=10% P(d)=23%. Construcia arborelui de C Codificare Huffman cuprinde etapele ilustrate n figur.

-21-

Fig. 1.9

Pentru codificarea obinuit a unui alfabet compus din 4 simboluri sunt necesari cte 2 bii pentru fiecare simbol. Se observ c, aplicnd algoritmul lui Hufman, simbolurilor care au frecvena de apariie maxim li se atribuie coduri scurte, iar celor rar folosite coduri lungi. Astfel, simbolului "b", care este cel mai des utilizat, i se atribuie un cod format dintr-un bit, iar simbolurilor "a" i "c" coduri pe trei bii. La decodificarea unui mesaj, pe msur ce biii sunt citii, se parcurge arborele de coduri de la rdcin pn la ntlnirea unei frunze, calea urmnd fiul stng dac bitul citit este 0 i fiul drept dac bitul citit este 1. De exemplu, dac se citete mesajul codificat 010110011, se poate reconstitui mesajul iniial abbdbb. Proprietatea de prefix a codului Hufmann este deosebit de important, permind decodificarea neambigu a mesajului. 1.5.2 Obinerea codificrii textului Un cod Huffman este un cod unic decodabil (UD). Aceasta nseamn dou lucruri: 1. Se poate codifica un mesaj uitndu-ne la fiecare caracter introdus n secven. La fiecare pas n parte fiecare caracter pe care-l citim ori intr n compunerea unei litere, ori corespunde exact uneia. 2. Mesajul codificat este cel mai scurt posibil printre toate schemele de codare care au proprietatea 1.

-22-

S vedem un exemplu. Presupunem c vrem s codm mesajul urmtor: "(C) 2002 Directionsmag.com". Avem codarea Huffman pentru mesaj (n figura i tabelul de mai jos): Mesajul codificat este : 10110 10011 01101 000 1100 1110 1110 1100 000 11010 1000 11011 01111 1010 10111 1000 001 01100 11110 010 01110 10010 11111 1010 001 010. Lungimea lui este de 110 bii, sau n jur de 4,24 bii/caracter. Reprezentarea original n codul ASCII necesita 8 bii/caracter, totaliznd 208 bii. Compresia este aproape 50%.Ideea din spatele acestui cod este evideniat n graful urmtor: caracterele care apar mai frecvent sunt reprezentate de coduri mai scurte. Pentru ca un cod s fie unic decodabil trebuie s nu existe ambiguiti n decodarea mesajului. Astfel, codul pentru un caracter nu va fi niciodat nceputul codului pentru alt caracter. Ca exemplu, asignnd (atribuind) 000 caracterului spaiu a nsemnat c nici un alt cod nu poate ncepe cu 000, putndu-se verifica acest lucru. Un asemenea cod este denumit cod prefix. n arborele descris mai jos, zerourile trimise codorului sunt pe ramurile de sus, iar secvenele de 1 sunt pe ramurile de jos. Proprietatea unic decodabil a codului Huffman corespunde faptului c fiecare cale prin arbore se termin la o liter unic. Cele mai scurte ci corespund literelor cu frecvenele cele mai mari. Caracter Frecven Cod '' '(' ')' '.' '0' '2' 'C' 'D' 2 1 1 1 2 2 1 1 000 10110 01101 11111 1110 1100 10011 11010 Arbore

-23-

'a' 'c' 'e' 'g' 'i' 'm' 'n' 'o' 'r' 's'

1 2 1 1 2 2 1 2 1 1

01110 1010 01111 10010 1000 010 01100 001 11011 11110

't'

10111

Arborele pentru codul binar Huffman este construit ntr-un mod direct i simplu. Literele cu cele mai mici frecvene de apariie sunt localizate, combinate ca plecri spre un nod, i atunci acel nod va fi tratat ca frecvena combinata (nsumat) a celor dou. Algoritmul necesit n primul rnd s gseasc cele mai mici frecvene ntr-o list potenial lung, s o fac n mod repetat, i apoi s insereze fiecare frecven nou napoi n list. O eficient structur de date pentru acest lucru este coada de prioriti, care este uor implementat cu array sau list. Utiliznd coada de prioriti, ntregul algoritm pentru construirea codului Huffman este foarte simplu. Cea mai mare problem n implementarea codului Huffman este preocuparea pentru frecvene. Dac codm un singur mesaj, o dat i nc o dat, de mai multe ori, ca o simpl copiere, asta-i simplu. Dar dac vrem un mesaj mult mai lung ntr-un pachet de mesaje este mai dificil. n acest caz, o soluie bun este s folosim frecvene tipice pe care dorim s le scriem. Putem implementa rutine pentru

-24-

analizarea setului de fiiere text pentru frecvenele caracterelor lor. Rezultatele sunt nregistrate ntr-un fiier special de frecvene cu care codul creat Huffman poate fi folosit pentru citirea i scrierea fiierelor. 1.6 Realizarea algoritmul Huffman prin metoda greedy O aplicaie a strategiei greedy i a arborilor binari cu lungime extern ponderat minim este obinerea unei codificri ct mai compacte a unui text. 1.6.1 Tehnica greedy Algoritmii greedy (greedy = lacom) sunt n general simpli i sunt folosii la probleme de optimizare, cum ar fi: s se gseasc cea mai bun ordine de executare a unor lucrri pe calculator, s se gseasc cel mai scurt drum ntr-un graf etc. n cele mai multe situaii de acest fel avem: o mulime de candidai ( lucrri de executat, vrfuri ale grafului etc); o funcie care verific dac o anumit mulime de candidai constituie o soluie posibil, nu neaprat optim, a problemei; o funcie care verific dac o mulime de candidai este fezabil, adic dac este posibil s completm aceast mulime astfel nct s obinem o soluie posibil, nu neaprat optim, a problemei; o funcie de selecie care indic la orice moment care este cel mai promitor dintre candidaii nc nefolosii; o funcie obiectiv care d valoarea unei soluii (timpul necesar executrii tuturor lucrrilor ntr-o anumit ordine, lungimea drumului pe care l-am gsit etc); aceasta este funcia pe care urmrim s o optimizm (minimizm/maximizm). Pentru a rezolva problema noastr de optimizare, cutam o soluie posibil care s optimizeze valoarea funciei obiectiv. Un algoritm greedy construiete soluia pas cu pas. Iniial, mulimea candidailor selectai este vid. La fiecare pas, ncercm s adugm acestei mulimi cel mai promitor candidat, conform funciei de selecie. Dac, dup o astfel de adugare, mulimea de candidai selectai nu mai este fezabil,

-25-

eliminm ultimul candidat adugat; acesta nu va mai fi niciodat considerat. Dac, dup adugare, mulimea de candidai selectai este fezabil, ultimul candidat adugat va rmne de acum ncolo n ea. De fiecare dat cnd lrgim mulimea candidailor selectai, verificm dac aceast mulime nu constituie o soluie posibil a problemei noastre. Dac algoritmul greedy funcioneaz corect, prima soluie gsit va fi totodat o soluie optim a problemei. Soluia optim nu este n mod necesar unic: se poate ca funcia obiectiv s aib aceeai valoare optim pentru mai multe soluii posibile. Descrierea formal a unui algoritm greedy general este: function greedy(C) {C este mulimea candidailor} S {S este mulimea n care construim soluia} while not soluie(S) and C do x un element din C care maximizeaz/minimizeaz select(x) C C \ {x} if fezabil(S {x}) then S S {x} if solutie(S) then return S else return nu exista soluie Este de neles acum de ce un astfel de algoritm se numete lacom (am putea s-i spunem i nechibzuit). La fiecare pas, procedura alege cel mai bun candidat la momentul respectiv, fr s-i pese de viitor i fr s se rzgndeasc. Dac un candidat este inclus n soluie, el rmne acolo; dac un candidat este exclus din soluie, el nu va mai fi niciodat reconsiderat. Asemenea unui ntreprinztor rudimentar care urmrete ctigul imediat n dauna celui de perspectiv, un algoritm greedy acioneaz simplist. Totui, ca i n afaceri, o astfel de metod poate da rezultate foarte bune tocmai datorit simplitii ei. Funcia select este de obicei derivat din funcia obiectiv; uneori aceste dou funcii sunt chiar identice. Un exemplu simplu de algoritm greedy este algoritmul folosit pentru rezolvarea urmtoarei probleme. S presupunem c dorim s dm restul unui client, folosind un numr ct mai mic de monezi. n acest caz, elementele problemei sunt:

-26-

candidaii: mulimea iniial de monezi de 1, 5, i 25 uniti, n care presupunem c din fiecare tip de moned avem o cantitate nelimitat;

o soluie posibil: valoarea total a unei astfel de mulimi de monezi selectate trebuie s fie exact valoarea pe care trebuie s o dm ca rest;

o mulime fezabil: valoarea total a unei astfel de mulimi de monezi selectate nu este mai mare dect valoarea pe care trebuie s o dam ca rest; funcia de selecie: se alege cea mai mare moned din mulimea de candidai rmas;

funcia obiectiv: numrul de monezi folosite n soluie; se dorete minimizarea acestui numr. Se poate demonstra c algoritmul greedy va gsi n acest caz mereu soluia

optim (restul cu un numr minim de monezi). Pe de alt parte, presupunnd c exist i monezi de 12 uniti sau c unele din tipurile de monezi lipsesc din mulimea iniial, se pot gsi contraexemple pentru care algoritmul nu gsete soluia optim, sau nu gsete nici o soluie cu toate c exist soluie. Evident, soluia optim se poate gsi ncercnd toate combinrile posibile de monezi. Acest mod de lucru necesit ns foarte mult timp. Un algoritm greedy nu duce deci ntotdeauna la soluia optim, sau la o soluie. Este doar un principiu general, urmnd ca pentru fiecare caz n parte s determinm dac obinem sau nu soluia optim. 1.6.2 Codificarea Huffman prin metoda greedy Un principiu general de codificare a unui ir de caractere este urmtorul: se msoar frecvena de apariie a diferitelor caractere dintr-un eantion de text i se atribuie cele mai scurte coduri celor mai frecvente caractere, i cele mai lungi coduri celor mai puin frecvente caractere. Acest principiu st, de exemplu, la baza codului Morse. Pentru situaia n care codificarea este binar, exist o metod elegant pentru a obine codul respectiv. Aceast metod, descoperit de Huffman (1952) folosete o strategie greedy i se numete codificarea Huffman. O vom descrie pe baza unui exemplu.
-27-

Ex: Fie un text compus din urmtoarele litere (n paranteze figureaz frecvenele de apariie): S(10), I(29), P(4), O(9), T(5). Conform metodei greedy, construim un arbore binar fuzionnd cele dou litere cu frecvenele cele mai mici. Valoarea fiecrui vrf este dat de frecvena pe care o reprezint.

Etichetm muchia stnga cu 1 i muchia dreapt cu 0. Rearanjm tabelul de frecvene: S(10), I(29), O(9), {P, T}(45 = 9)

Mulimea {P, T} semnific evenimentul reuniune a celor dou evenimente independente corespunztoare apariiei literelor P i T. Continum procesul, obinnd arborele

n final, ajungem la arborele din figur, n care fiecare vrf terminal corespunde unei litere din text. Pentru a obine codificarea binar a literei P, nu avem dect s scriem secvena de 0-uri i 1-uri n ordinea apariiei lor pe drumul de la rdcina ctre vrful corespunztor lui P: 1011. Procedm similar i pentru restul literelor: S(11), I(0), P(1011), O(100), T(1010)

Pentru un text format din n litere care apar cu frecvenele f1, f2, ..., fn, un arbore de codificare este un arbore binar cu vrfurile terminale avnd valorile f1, f2, ..., fn, prin care se obine o codificare binar a textului. Un arbore de codificare
-28-

nu trebuie n mod necesar s fie construit dup metoda greedy a lui Huffman, alegerea vrfurilor care sunt fuzionate la fiecare pas putndu-se face dup diverse criterii. Lungimea extern ponderat a unui arbore de codificare este:

a
i =1

fi

unde ai este adncimea vrfului terminal corespunztor literei i.

Fig. 1.10 Arborele de codificare Huffman. Se observ c lungimea extern ponderat este egal cu numrul total de caractere din codificarea textului considerat. Codificarea cea mai compact a unui text corespunde deci arborelui de codificare de lungime extern ponderat minim. Se poate demonstra c arborele de codificare Huffman minimizeaz lungimea extern ponderat pentru toi arborii de codificare cu vrfurile terminale avnd valorile f1, f2, ..., fn. Prin strategia greedy se obine deci ntotdeauna codificarea binar cea mai compact a unui text. Arborii de codificare pe care i-am considerat n aceast seciune corespund unei codificri de tip special: codificarea unei litere nu este prefixul codificrii nici unei alte litere. O astfel de codificare este de tip prefix. Codul Morse nu face parte din aceasta categorie. Codificarea cea mai compact a unui ir de caractere poate fi ntotdeauna obinut printr-un cod de tip prefix. Deci, concentrndu-ne atenia asupra acestei categorii de coduri, nu am pierdut nimic din generalitate.

-29-

Algoritmul greedy de construcie a arborelui Huffman este urmtorul: Pas 1. Iniializare :


- fiecare caracter reprezint un arbore format dintr-un singur nod; - organizm caracterele ca un min-heap, n funcie de frecvenele de

apariie; Pas 2. Se repet de n-1 ori :


- extrage succesiv X i Y, dou elemente din heap - unific arborii X i Y : -

creaz Z un nou nod ce va fi rdcina arborelui ZA.st := X ZA.dr := Y ZA.frecv := XA.frecv+YA.frecv

- insereaz Z n heap;

Pas 3. Singurul nod rmas n heap este rdcina arborelui Huffman. Se genereaz codurile caracterelor, parcurgnd arborele Huffman. Iniializarea heapului este liniar. Pasul 2 se repet de n-1 ori i presupune dou operaii de extragere a minimului dintr-un heap i de inserare a unui element n heap, care au timpul de execuie de O(log n). Deci complexitatea algoritmului de construcie a arborelui Huffman este de O(n log n).

-30-

CAPITOLUL 2 ANALIZA ALGORITMULUI DE CODIFICARE HUFFMAN

2.1 Corectitudinea algoritmului Huffman Trebuie s demonstrm c algoritmul lui Huffman produce un cod prefix optimal. Lema 1. Fie x, y e C, dou caractere care au frecvena cea mai mic. Atunci exist un cod prefix optimal n care x i y au codificri de aceeai lungime i care difer doar prin ultimul bit. Demonstraie: Fie T arborele binar asociat unui cod prefix optimal oarecare i fie a, b dou noduri de pe nivelul maxim, care sunt fiii aceluiai nod. Putem presupune fr a restrnge generalitatea c f[a] < f[b] i f[x] f[y]. Cum x i y au cele mai mici
-31-

frecvene rezult c f[x] < f[a] i f[y] < f[b]. Transformm arborele T n T', schimbnd pe a cu x :

Fig. 2.1 Notm cu C(T) costul arborelui T: C ( T )


nivT (c) = I f(c)

ceC C (T) C(T') = f (x)

nivT (x) + f

(a)

nivT (a) - f

(x)

nivT (x) - f

( a)

nivT (a) ^ C(T )-C ( T ') = ( f(a)- f (x)) (nivr(a)-nivr(x)) > 0, pentru c {V(X) n mod analog, construim arborele T, schimbnd pe x cu y. Obinem: C(T ')C(T '') = ( f (b)- f (y)) (nivr(b)- nivr(y)) > 0. Deci C(T) > C(T''), dar cum T era optimal deducem c T" este arbore optimal i n plus x i y sunt noduri terminale situate pe nivelul maxim, fii ai aceluiai nod. Observaie: Din lem deducem c pentru construcia arborelui optimal putem ncepe prin a selecta dup o strategie Greedy caracterele cu cele mai mici frecvene. Lema 2. Fie T un arbore binar complet corespunzator unui cod prefix optimal pentru alfabetul C. Dac x i y sunt dou noduri, fii ai aceluiai nod z n T, atunci arborele T' obinut prin eliminarea nodurilor x i y este arborele binar complet asociat unui cod prefix optimal pentru alfabetul C' = (C-{x,y}) u { z}, f[z] fiind f[x]+f[y]. Demonstraie: VceC-Qr, yV.nivr (c)=nivr(c) nivr (x)=nivr (y)=nivr (z)+lrV
f (X)

^(X) + f (^ nV (y) =

= ( f (x) + f (y)) (nivr(z) +1) = f (z) nivr(z) + f (x) + f (y) = = f (z) nivr (z) + ( f (x) + f (y)). Deci, C(T) = E f (c) nvr(c) = C(T ') + ( f (x) + f (y))
-32-

Dac presupunem prin reducere la absurd c arborele T' nu este optimal pentru alfabetul C' = (C-{x,y}) u {z} ^ $ T", ale crui frunze sunt caractere din C', astfel nct C(T") < C(T). Cum z este nod terminal n T", putem construi un arbore Ti pentru alfabetul C, atrnnd pe x [i y ca fii ai lui z. C(Ti) = C(T" )+f(x)+f(y) < C(T), ceea ce contrazice ipoteza c T este arbore optimal pentru C. Deci, T' este un arbore optimal pentru C'. Corectitudinea algoritmului lui Huffman este o consecin imediat a celor dou leme. Observaie Metoda de compresie bazat pe arbori Huffman statici are o serie de dezavantaje:

1. Fiierul de compactat trebuie parcurs de dou ori: o dat pentru a calcula numrul
de apariii ale caracterelor n text, a doua oar pentru a comprima efectiv fiierul. Deci metoda nu poate fi folosit pentru transmisii de date pentru care nu este posibil reluarea.

2. Pentru o dezarhivare ulterioar a fiierului, aa cum este i firesc, este necesar


memorarea arborelui Huffman utilizat pentru comprimare, ceea ce conduce la mrirea dimensiunii codului generat.

3. Arborele Huffman generat este static, metoda nefiind capabil s se adapteze la


variaii locale ale frecvenelor caracterelor. Aceste dezavantaje au fost n mare parte eliminate n metodele care folosesc arbori Huffman dinamici, ideea fiind ca la fiecare nou codificare, arborele Huffman s se reorganizeze astfel nct caracterul respectiv s aib eventual un cod mai scurt. 2.2 Coduri prefix Vom considera n continuare doar codificarile n care nici-un cuvant nu este prefixul altui cuvnt. Aceste codificri se numesc codificri prefix. Codificarea prefix este utila deoarece simplifica atat codificarea (deci compactarea) ct i decodificarea. Pentru orice codificare binar a caracterelor se concateneaza cuvintele de cod reprezentand fiecare caracter al fisierului.

-33-

Decodificarea este de asemenea simpla pentru codurile prefix. Cum nici un cuvnt de cod nu este prefixul altuia, nceputul oricarui fisier codificat nu este ambiguu. Putem sa identificm cuvantul de cod de la nceput, sa l convertim n caracterul original, sa-l ndepartam din fisierul codificat i s repetm procesul pentru fisierul codificat ramas. 2.2.1 Coduri prefix.Arbore de codificare Algoritmul lui Huffman apartine familiei de algoritmi ce realizeaza codificari cu
lungime variabil. Aceasta nseamna ca simbolurile individuale (ca de exemplu

caracterele ntr-un text) sunt nlocuite de secvente de biti (cuvinte de cod) care pot avea lungimi diferite. Astfel, simbolurilor care se ntalnesc de mai multe ori n text (fisier) li se atribuie o secventa mai scurta de biti n timp ce altor simboluri care se ntlnesc mai rar li se atribuie o secventa mai mare. Pentru a ilustra principiul, sa presupunem ca vrem sa compactam urmtoarea secventa :AEEEEBEEDEC DD. Cum avem 13 caractere (simboluri), acestea vor ocupa n memorie 13 x 8 = 104 biti. Cu algoritmul lui Huffman, fisierul este examinat pentru a vedea care simboluri apar cel mai frecvent (n cazul nostru E apare de sapte ori, D apare de trei ori iar A , B i C apar cate o data). Apoi se construieste (vom vedea n cele ce urmeaz cum) un arbore care nlocuieste simbolurile cu secvente (mai scurte) de biti. Pentru secventa propusa, algoritmul va utiliza substituie (cuvintele de cod) A = 1 1 1 , B = 1101, C = 1100, D = 10, E = 0 iar secventa compactata (codificata) obinuta prin concatenare va fi 111000011010010011001010. Aceasta nseamna c s-au utilizat 24 biti n loc de 104, raportul de compresie fiind 24/104. n cazul exemplului prezentat mai nainte sirul se desparte n 111 - 0 - 0 - 0 - 0 - 1101 - 0 - 0 - 10 - 0 - 1100 - 10 - 10, secvena care se decodific n AEEEBEEDEC DD. Procesul de decodificare necesit o reprezentare convenabil a codificrii prefix astfel nct cuvantul iniial de cod s poat fi uor identificat. O astfel de reprezentare poate fi dat de un arbore binar de codificare, care este un arbore complet (adica un arbore n care fiecare nod are 0 sau 2 f i i , ale carui frunze sunt caracterele date. Interpretam un cuvnt de cod binar ca fiind o secventa de bi obtinuta etichetnd cu 0 sau 1 muchiile drumului de la rdacin pn la frunza ce
-34-

conine caracterul respectiv: cu 0 se eticheteaz muchia ce unete un nod cu fiul stang iar cu 1 se eticheteaz muchia ce unete un nod cu fiul drept. n figura (2.1) este prezentat arborele de codificare al lui Huffman corespunztor exemplului nostru. Notnd cu C alfabetul din care fac parte simbolurile (caracterele), un arbore de codificare are exact |C| frunze, una pentru fiecare litera (simbol) din alfabet i asa cum se tie din teoria grafurilor, exact |C| 1 noduri interne (notm cu |C| , cardinalul multimii C).

Figura 2.2: Exemplu de arbore Huffman Dndu-se un arbore T, corespunztor unei codificri prefix, este foarte simplu sa calculm numrul de bii necesari pentru a codifica un fiier. Pentru fiecare simbol c
C,

fie f (c) frecventa (numarul de aparii) lui c n fisier i

s notm cu dT (c) adancimea frunzei n arbore. Numrul de biti necesar pentru a codifica fisierul este numit costul arborelui T i se calculeaz cu formula :

2.2.2 Construcia codificrii prefix a lui Huffman Huffman a inventat un algoritm greedy care construieste o codificare prefix optima numit codul Huffman. Algoritmul construieste arborele corespunzator codificarii optime (numit arborele lui Huffman) pornind de jos n sus. Se ncepe cu o multime de |C| frunze i se realizeaz o secven de |C| 1 operaii de fuzionri
-35-

pentru a crea arborele final. n algoritmul scris n pseudocod care urmeaz, vom presupune ca C este o multime de n caractere i fiecare caracter are frecventa f (c). Asimilm C cu o pdure constituita din arbori formai dintr-o singur frunz.Vom folosi o stiva S formata din noduri cu mai multe cmpuri; un cmp pstreaz ca informaie pe f (c), alt cmp pastreaz rdacina c iar un cmp suplimentar pstreaz adresa nodului anterior (care indic un nod ce are ca informatie pe f (c') cu proprietatea ca f (c) < f (c'). Extragem din stiv vrful i nodul anterior (adic obiectele cu frecventa cea mai redusa) pentru a le face sa fuzioneze. Rezultatul fuzionrii celor dou noduri este un nou nod care n cmpul informatiei are f (c) + f (c') adic suma frecventelor celor doua obiecte care au fuzionat. De asemenea n al doilea cmp apare rdcina unui arbore nou format ce are ca subarbore stng, respectiv drept, arborii de rdcini c i c'. Acest nou nod este introdus n stiva dar nu pe pozitia vrfului ci pe pozia corespunzatoare frecventei sale. Se repet operaia pn cnd n stiv ramne un singur element. Acesta va avea n campul radacinilor chiar radcina arborelui Huffman. Urmeaz programul n pseudocod. inand cont de descrierea de mai sus a algoritmului numele instructiunilor programului sunt suficient de sugestive. n |C| SC cat timp (S are mai mult dect un nod) {
z

ALOCA-NOD( )

x stanga[z] EXTRAGE-MIN(S) y dreapta[z] EXTRAGE-MIN(S) f

(z) f (x) + f (y)

-36-

INSEREAZA(S, z) } returneaz EXTRAGE-MIN( S ) .

Figura 2.3 Construirea arborelui Huffman

n cazul exemplului deja considerat, avem f (E) = 7, f (D) = 3, f ( A ) = f


(B) =

f (C) = 1. Putem, pentru comoditate sa etichetam frunzele nu cu simbolurile

corespunztoare, ci cu frecventele lor. n figura (2.2) prezentam arborii aflai n nodurile stivei, dup fiecare repetare a instructiunilor din ciclul while. Se pleac cu o pdure de frunze, ordonat descresctor dupa frecvenele simbolurilor i se ajunge la arborele de codificare.
-37-

2.3 Variante ale algoritmului lui Huffman Exist trei variante des aplicate ale acestui algoritm:

algoritmul Huffman static; algoritmul Huffman semi-static; algoritmul Huffman dinamic. Diferenele dintre cele trei variante sunt urmtoarele:

n cazul variantei statice, att compresorul, ct i decom- presorul dein acelai arbore de compresie calculat pe baza unor frecvene fixe de apariie i nu mai este necesar calcularea unui arbore nou i nici transmiterea aces tuia. Dezavantajul acestei metode este c, dac frecvenele de apariie ale simbolurilor generate de o surs difer foarte mult de cele fixe utilizate, atunci s-ar putea ca pentru simboluri cu frecven mare de apariie s fie transmise coduri foarte lungi i astfel cantitatea de informaie comprimat poate s depeasc cu mult cantitatea de informaie care a fost generat de o surs. varianta semi-static utilizeaz algoritmul de construire a arborelui de compresie prezentat anterior. Are ca dezavantaj faptul c irul simbolurilor generate de o surs de informaie trebuie parcurse de dou ori, o dat pentru calcularea frecvenelor necesare construirii arborelui i o dat pentru codificarea irului simbolurilor; n cazul n care simbolurile au aproximativ aceeai frecven de apariie, algoritmul nu ofer o compresie bun. Parcurgerea de dou ori a irului simbolurilor generate de o surs de informaie este un inconvenient deoarece acesta trebuie stocat, iar dimensiunea datelor care trebuie comprimate, n zilele noastre, este foarte mare i calculatoarele personale (de cele mai multe ori) nu dein resursele necesare stocrii datelor. pentru varianta dinamic exist doi algoritmi performani: FGK (Faller, Gallager, Knuth) i V (Vitter). Acetia au n comun faptul c arborele se construiete dinamic pe msur ce o surs de informaie genereaz simboluri, deci este necesar o singur parcurgere a irului simbolurilor i nu este necesar stocarea lor. Doar o mic parte dintre ele sunt stocate cu scopul de a optimiza procesul de compresie. Rata de compresie a acestor doi algoritmi variaz. n anumite cazuri rezultatele obinute sunt cu mult mai bune dect cele date de varianta static, dar n cazul cel

-38-

mai ru pentru varianta static, rezultatele variantelor dinamice sunt optime de cele mai multe ori. Primele dou variante nu mai necesit explicaii suplimentare, aadar, n continuare, vom prezenta n detaliu varianta dinamic FGK, varianta dinamic Vitter fiind similar. 2.3.1 Metoda de codare dinamic Huffman

Compresia dinamic (sau adaptiv) Huffman folosete un arbore de codare ce este actualizat de fiecare dat cnd un simbol este citit din fiierul de intrare. Arborele creste (se dezvolt, evolueaz) la fiecare simbol citit din fisierul de intrare. Modul de evolutie al arborelui este identic la compresie i la decompresie. Eficiena metodei se bazeaz pe o proprietate a arborilor Huffman, cunoscut sub numele de proprietatea fiilor (sibling property) Proprietate (sibling): Fie T un arbore Huffman cu n frunze. Atunci nodurile lui T pot fi aranjate intr-o secventa (x0, x1, ..., x2n-2) astfel incat: 1. Secvena ponderilor (weight(x0), weight(x1), ..., weight(x2n-2)) sa fie n ordine descrescatoare; 2. Pentru orice i (0 i n-1), nodurile consecutive x2i+1 i x2i+2 sunt siblings (fii ai aceluiasi parinte). Compresia i decompresia utilizeaz arborele dinamic Huffman pornind de la un caracter virtual, notat de exemplu prin VIRT, sau ART. Acesta este ntotdeauna nod terminal (frunz). Descrierea codrii De fiecare dat cnd un simbol este citit din fiierul de intrare se efectueaz urmatoarele operatii: 1). se scrie n fisierul destinatie codul simbolului: daca simbolul citit este nou, atunci se scrie codul caracterului virtual (VIRT) (determinat din arborele de codare) urmat de codificarea n binar (ASCII) pe 9 biti a simbolului respectiv; n caz contrar, se scrie cuvantul de cod al simbolului, determinat din arborele de codare. Numai la primul simbol se scrie direct codul ASCII al acestuia pe 9 bii.
-39-

2). Se introduce simbolul citit n arborele de codare: dac simbolul curent citit este nou, atunci din nodul virtual vor pleca doi fii: unul pentru simbolul citit i unul pentru simbolul virtual. Nodul care a fost folosit ca nod virtual devine nod intermediar; n caz contrar, se incrementeaza frecventa de aparitie a acestuia, prin marirea ponderii nodului corespunzator simbolului citit; 3) Se ajusteaza arborele pentru indeplinirea proprietatii sibling. Trebuie considerate urmatoarele doua aspecte: o la creterea ponderii unui nod, frunz sau intermediar, trebuie modificate i ponderile ancestorilor sai; o pentru a pastra ordinea descrescatoare a ponderilor se judeca astfel: fie nodul frunza n ce are ponderea cu unu mai mare decat valoarea anterior, deci el este cel care s-a modificat la pasul curent. Pentru indeplinirea conditiei (1) a proprietatii sibling, nodul n este schimbat cu cel mai apropiat nod m (m < n) din lista, astfel incat weight(m) < weight(n). Apoi, aceeasi operatie este efectuat asupra parintelui lui n, pana cand se obine nodul radacina sau este indeplinit condiia (1). De obicei se numeroteaz nodurile iar corespondenta numar nod eticheta se obine folosind un vector ale carui elemenete sunt chiar etichetele arborelului de codare. Descrierea decodrii La decompresie, textul comprimat este verificat cu arborele de codare. Tehnica se numeste parsing. La inceputul decompresiei, nodul curent este iniializat cu radacina, ca la algoritmul de compresie, i apoi arborele evolueaza simetric. La fiecare citire a unui simbol 0 se parcurge arborele n jos spre stanga; se parcurge arborele spre dreapta daca simbolul citit este 1. Cand se ajunge la un nod terminal (frunza) simbolul asociat este scris n fisierul de iesire i se ajusteaza arborele, la fel ca n faza de compresie. Exemplul : Sa se comprime mesajul astazi cu Huffman dinamic.

-40-

1). Se initializeaza arborele de codare i variabila pentru textul comprimat.

2). Se citeste primul simbol, a . Fiind primul simbol nu se scrie codul lui VIRT (de fapt, nici nu are cod) i se scrie codul lui a pe 9 biti. Se actualizeaza arborele de codare.

3). Se citeste urmatorul simbol, s . Fiind simbol nou se scrie codul lui VIRT i codul lui s pe 9 biti. Se actualizeaza arborele de codare. Se observa ca dupa introducerea nodului nou i numerotarea nodurilor ponderile nodurilor nu sunt monoton descrescatoare. Trebuie inversate nodurile 1 i 2. Se obtine arborele din partea dreapta.

4). Se citeste urmatorul simbol, t . Fiind simbol nou, n secventa de iesire se scrie codul lui VIRT, 01, i codul lui t pe 9 biti, 0.0111.0100. Se actualizeaza arborele
-41-

de codare. Trebuie inversate nodurile 2 i 4 pentru ca ponderile sa fie ordonate n sens descrescator.

5). Se citete urmtorul simbol, a . Nefiind simbol nou, n secventa de ieire se scrie codul acestuia asa cum rezult din arbore, 01. Se actualizeaza arborele de codare. Trebuie inversate nodurile 2 i 4 pentru ca ponderile sa fie ordonate n sens descresctor.

6). Se citete urmtorul simbol, z . Fiind simbol nou, n secvena de ieire se scrie codul lui VIRT, 11, urmat de codul pe 9 biti al lui z, 0.0111.1010. Se actualizeaz arborele de codare. Trebuie inversate nodurile 2 i 4 pentru ca ponderile sa fie ordonate n sens descresctor.

-42-

7). Se citete urmatorul simbol, i . Fiind simbol nou, n secvena de ieire se scrie codul lui VIRT, 011, urmat de codul pe 9 biti al acetuia, 0.0110.1001. Se actualizeaz arborele de codare. Trebuie inversate nodurile 2 i 4 i apoi 5 cu 6, pentru ca ponderile sa fie ordonate n sens descresctor.

-43-

7). Se citete urmtorul simbol, END , deci marcatorul sfaritului de text . Fiind simbol nou, n secventa de iesire se scrie codul lui VIRT, 101, urmat de codul pe 9 bii al simbolului citit, 1.0000.0000.
code = [ code + cod ( VIRT ) + cod ( END ,9 )] = = [ code + 101 + 1.000.000]

n total, secvena comprimat conine 6*9+14=54+14=68 biti. Mesajul original, reprezentat pe 8 biti/simbol, are o marime de 6*8=48 biti. Raportul de compresie este 48/68 =0.7. 2.3.2 Algoritmul FGK Algoritmul FGK precum i algoritmul V se bazeaz pe proprietatea frailor enunat i demonstrat de Gallager n anul 1978: "Un arbore binar c u p frunze care au ponderi (n cazul de fa frecvene) nenegative este arbore Huffman dac i numai dac urmtoarele afirmaii sunt adevrate:

cele p frunze au ponderile nenegative w1, w2, ..., w i ponderea fiecrui nod intern este egal cu suma ponderilor celor doi fii;

-44-

nodurilor interne li se pot ataa numere de ordine n ordinea cresctoare a

ponderilor, astfel nct nodurile cu numerele 2 j - 1 i 2 j s fie frai pentru 1 < j < p - 1 i printele lor comun s aib un numr de ordine mai mare. Dac un arbore este construit pe baza algoritmului prezentat la seciunea

anterioar, atunci acesta este un arbore Huffman i respect proprietatea frailor. La nceput vom avea un arbore A care va conine un singur nod a crui pondere (greutate) este 0 sau 1 n funcie de implementarea folosit. Acest nod ine locul tuturor simbolurilor care pot fi generate de o surs de informaie S i care nu au fost generate nc pn la un pas k. Pe acest nod i l vom numi nodul zero. Vom considera c pondera acestui nod este 0, deci simbolurile care nu au fost nc generate de sursa S au aprut de 0 ori (sau n 0% din cazuri). n concluzie, dup un pas k, acest nod este frunz, iar ponderea rdcinii este egal cu k (sau cu 1 n cazul n care se folosesc frecvenele de apariie ale simbolurilor generate pn atunci). La nceput arborele A, care conine doar nodul zero, este un arbore Huffman, deci respect proprietatea frailor. Considerm c la al k-lea simbol generat de o surs avem un arbore Huffman. Sursa S genereaz al (k + 1)-lea simbol care trebuie codificat. n cazul n care simbolul nu a mai fost nc generat, se va transmite la ieire codul nodului zero i simbolul care tocmai a fost generat. Nodul zero va fi nlocuit cu un nod cu ponderea 1 i ai crui fii vor fi nodul zero i un alt nod corespunztor simbolului nou aprut, a crui pondere va fi tot 1. n cazul n care sursa a emis un simbol care a mai fost generat, la ieire se transmite codul acestuia i se incrementeaz ponderea nodului corespunztor simbolului generat. Codurile simbolurilor din arbore sunt construite la fel ca n cazul variantelor semistatice,

adic sunt date de drumul parcurs de la rdcin pn la frunza

corespunztoare simbolului care trebuie codificat. n continuare trebuie actualizate ponderile celorlalte noduri interne deoarece dup adugarea unui nod nu mai este respectat proprietatea frailor fiindc ponderea rdcinii nu mai este egal cu suma ponderilor frunzelor sub-arborilor ei.

-45-

Aceste dou cazuri de apariie ale unui simbol sunt tratate n mod identic de algoritmii FGK i V. n continuare vom prezenta modul n care se actualizeaz ponderile nodurilor interne n cadrul algoritmului FGK. Pentru a menine proprietatea frailor, trebuie parcurs drumul de la frunza actualizat ultima dat pn la rdcin. Ponderea fiecrui nod din drum trebuie incrementat cu 1 pentru a fi respectat prima condiie a proprietii frailor, iar pentru a doua condiie, la fiecare pas trebuie interschimbat nodul curent cu un nod din arbore care are aceeai pondere cu cea a nodului curent i care are cel mai mare numr de ordine. Printele nodului curent devine printele noului nod i invers. Trebuie avut n vedere faptul c un nod nu se poate interschimba cu un strmo de-al su.

-46-

Figura 2.4 n figura alturat este prezentat modul de construcie i actualizare a arborelui folosind algoritmul FGK, pentru irul "aa bbb c", unde simbolul spaiu este reprezentat prin "_". La fiecare pas, arborii din figur sunt arbori Huffman, deci respect i a doua condiie a proprietii frailor. Pentru a verifica acest lucru se numeroteaz nodurile din arbore n ordine creasctoare ncepnd de la ultimul nivel pn la primul i de la stnga la dreapta. 2.3.3 Algoritmul V Algoritmul V difer de algoritmul FGK prin faptul c, n momentul n care se realizeaz actualizarea arborelui, se ncearc minimizarea expresiilor SUM(l.) i
MAX(l.),

unde SUM(l.) reprezint suma lungimilor drumurilor de la rdcin pn la

frunze, iar MAX(li) reprezint lungimea drumului de la rdcin pn la cea mai ndeprtat frunz. Cu alte cuvinte, se ncearc minimizarea adncimii maxime a arborelui i a lungimii drumului extern. Complexitatea celor doi algoritmi este aceeai, dar algoritmul V este mai performant n cazul n care probabilitile de apariie ale simbolurilor sunt aproximativ aceleai. Jeffrey S. Vitter a demonstrat faptul c n cel mai ru caz, algoritmul V transmite la fiecare simbol un bit n plus fa de metoda semi-static, n timp ce algoritmul FGK transmite n cel mai ru caz de dou ori mai muli bii pe simbol relativ la metoda
semi-static.

-47-

CAPITOLUL 3 IMPLEMENTAREA ALGORITMULUI DE CODIFICARE/DECODIFICARE HUFFMAN

3.1. Implementarea algoritmului de compresie

Structura de baz folosit la implementarea algoritmului de compresie este structura arbore. Fiecare nod al arborelui este caracterizat de o pondere i o eticheta. Fiecare nod are doi fii, reprezentati recursiv prin aceeasi structura, de tip arbore.

-48-

Structura lista_nodurilor este o list liniar nlantuita. Fiecare nod din lista conine un arbore. n tabelul 1. se prezint principalele funcii folosite la implementarea compresiei, n pseudocod i partial n C. n Anexa 1 se prezinta codul surs complet al programului de compresie. Tabel 3.1 Pseudocod Structura arbore(nod): pondere, nivel, valoare - nr. intregi; arbore *stanga, * dreapta, *tatal; END. Exemplu (partial) n C++ typedef struct huffman_nod_t { int valoare; frecventa_t frecventa; int nivel struct huffman_nod_t *stanga, *dreapta, *tatal; } huffman_nod_t

Structura lista_nodurilor: nod *t; lista_nodurilor * urmatorul; END.

typedef struct cod_lista_t { octet_t codLungime } cod_lista_t;

Program Compresie #1: Stabileste modelul sursei. #2: Construieste arborele. #3: Construieste cuvintele de cod. #4: Codifica arborele i salveaza. #5: Codifica mesajul i salveaza. END.

int main(void) { arboreHuffman = ArboreGeneratDinFisier (inFisier); if (mod == COMPRESIE){ CodareFisier(arboreHuffman, inFisier, outFisier); } else { TiparireCod(arboreHuffman, outFisier); } }

-49-

Functia construieste arborele

int BuildTree(listnode *&list) { BuildList(lleaves, M, freq); #1: Initializeaza lista de noduri cu listnode *ln1,*ln2,*ln3; tree *t; simbolurile alfabetului while (Nodes>1) { ln1=list; // nodul 1 #2: Cat timp (nr noduri) > 1: ln2=list->next; // nodul 2 ln3=ln2->next; // nodul 3 #2.1: Citeste nodul (arbore) 1. t->weight=(ln1->t->weight)+ (ln2->t->weight); t->label=NODE; #2.2: Citeste nodul (arbore) 2. t->left=ln1->t; //fiu stang t->right=ln2->t; //fiu drept #2.3: Citeste nodul (arbore) 3. list = ln3; InsertInList(list,t); #2.4: Uneste primii doi arbori (cu Nodes -= 2; // actualizez lista } cele mai mici ponderi) return 0; } #2.5: Pune eticheta nodului. #2.6: Defineste sub-arborele stang. #2.7: Defineste sub-arborele drept. #2.7: Defineste sub-arborele drept. #2.8: lista(inserare+sortare); nr_noduri + 1; #2.9: nr_noduri = nr_noduri - 2; END. Actualizeaza =

nr_noduri

Functia construiste cuvintele de cod huffman_nod_t *ConstruireArboreHuffman(huffman_nod_t (BCW) corespunzatoare arborelui T **ah, int elemente) { #1: Pentru un nod i al arborelui int min1, min2; for (;;) DACA i nu este frunza { min1 = FrecventaMinimuluiGasit(ah,
-50-

ATUNCI k = k + 1; cod_stanga[k]=0; Parcurg fiul stang al lui i (BCW(fiu stang)) cod_dreapta[k] = 1 Parcurg fiul drept al lui i (BCW(fiu drept)) k = k - 1; ALTFEL cuvant[i]=cod[1:k];

elemente); if (min1 == NIMIC) { break; } ah[min1]->ignorat = ADEVARAT; min2 = FrecventaMinimuluiGasit(ah, elemente); if (min2 == NIMIC) { break; } ah[min2]->ignorat = ADEVARAT; ah[min1] = AlocareNodCombinatHuffman(ah[min1], ah[min2]); ah[min2] = NULL; } return ah[min1]; }

END.

Functia codifica arborele (CT) #1: Incepe cu nodul radacina. #2: Pentru un nod k din arbore DACA k este frunza ATUNCI - scrie 1; - scrie codul binar al etichetei ALTFEL

int CodeTree(tree *t) { if (t->label!=NODE){//t este frunza fprintf(f,"1",t->label); binary(t->label,9,Bits); } else { fprintf(f,"0"); if (t->left) CodeTree(t->left); if (t->right)CodeTree(t->right); } return 0; }

-51-

- scrie 0; - Parcurg fiul stang al lui k (CT(fiu stang)) - Parcurg fiul drept al lui k (CT(fiu drept)) END.

Functia codifica mesajul

int CodeMessage(char *txt) { #1: Pentru toate simbolurile s[i] ale for (i= 0;i < strlen(txt);i++){ for (j = 0;j < NWords;j++) mesajului if (Label[j]!=END && txt[i]==Label[j]) break; #1.1: Cauta eticheta label[j] = s[i]; fprintf(f,"%s",CodeWords[j]); } #1.2: Scrie cuvantul de cod cuvant[j]; for (j = 0;j < NWords;j++) if (Label[j]==END) break; fprintf(f,"%s",CodeWords[j]); #2: Scrie codul caracterului sfarsit; return 0; } END.

3.2 Implementarea algoritmului de expandare (de-compresie)

Pseudocod Program de-compresie #1: Reconstruieste fisierul de iesire. #2: Decodifica mesajul. END.

Exemplu partial C if (mod == DECOMPRESIE) { DecodareFisier(huffmanTablou, inFisier, outFisier); } if (inFisier == NULL){ fprintf(stderr, "Fisierul de intrare trebuie furnizat\n"); fprintf(stderr, "Intrare \"huffman -?\" pentru ajutor.\n"); exit (EXIT_FAILURE); } void ReBuildTree(tree *T) { fscanf(f,"%c",&ch);
-52-

Functia re-construieste arborele (RBT)

#1: Citeste primul caracter din fisier #2: DACA este nod frunza (valoare 1) ATUNCI pondere := 1; eticheta = citeste urmatorii 9 biti; nu are fii; ALTFEL Construiesc fiiul stang (RBT(fiu stang)) Construiesc fiul drept (RBT(fiu drept)) END.

if (ch=='0') Bit=0; else Bit=1; if (Bit) { T->left = T->right = NULL; T->weight = 1; T->label = ConvertLabel(); } else { tree *r=new tree,*l=new tree; T->left=l; T->right=r; T->label=NODE; T->weight=1; ReBuildTree(T->left); ReBuildTree(T->right); } }

Functia decodifica_mesajul

void DecodeText(tree *T) { #1: Pentru fiecare caracter al mesajului, ch2=DecodeChar(T); while (ch2 != END){ diferit de caracterul fictiv END, efectueaza printf("%c",(char)ch2); ch2=DecodeChar(T); Decodificarea. } } END.

Functia decodifica_caracter (DC)

int DecodeChar(tree *T) { tree *p=T; #1: Pleaca din nodul radacina while (p->label == NODE){ fscanf(f,"%c",&ch); #2: CAT TIMP T nu este frunza, if (ch=='0') p=p->left; else p=p->right; #2.1: Citeste urmatorul bit b din } return p->label; mesajul codificat } #2.2: DACA b este 0,
-53-

ATUNCI - parcurgem fiul drept(DC(fiu stang)) ALTFEL: - parcurgem fiul stang (DC(fiu drept)) #3: T este frunza, i contine caracterul decodificat END.

Functia conversie 9 biti intreg corespunzator codului ASCII al caracterului #1: PENTRU fiecare bit i, incepand de la stanga la dreapta(MSB pana la LSB): - citesc un bit b din fisier - adun la numarul total 2i*b END.

int ConvertLabel() { int P=256, Result=0,i; for (i = 8;i >= 0;i--,P /= 2) { fscanf(f,"%c",&ch); if (ch!='0') Bit = 1; else Bit = 0; Result += P*Bit; } return Result; }

3.3 Rezultate obinute

Algoritmul de compresie Huffman este unul relativ simplu de neles i de implementat, iar n cele ce urmeaza vom arata modul n care se va face codarea. Presupunem ca fiier de intrare chiar programul n cauz, <huffman.c> i ca fiier de ieire <fisier.out>. Se vor afia codurile folosite n comprimarea fiierului. Ca rezultat avem coninutul fiierului < fisier.out >: Caracter Frecventa Codat -------- --------- -----------0x6F 668 00000

-54-

0x2D 0x67 0x3A 0x5F 0x4D 0x5C 0x3F 0x25 0x6A 0x32 0x55 0x61 0x3E 0x2C 0x62 0x6D 0x3D 0x7B 0x7D 0x49 0x4F 0x09 0x50 0x58 0x48

165 84 85 180 45 22 5 3 3 12 92 1417 93 96 192 382 196 98 98 104 50 54 27 27 55

0000100 00001010 00001011 0000110 000011100 0000111010 000011101100 0000111011010 0000111011011 00001110111 00001111 0001 00100000 00100001 0010001 001001 0010100 00101010 00101011 00101100 001011010 001011011 0010111000 0010111001 001011101

Observm caracterele cel mai des ntlnite fiind cele corespunztoare n cod ASCII lui 0x20 i 0x2A care va fi codate doar prin doi bii 10, respectiv trei bii 010. Fiierul comprimat va avea o compresie din 30 kbii n 17 kbii, de aproximativ 57%. In Anexa 2 este prezentat un program C++ care codifica i decodifica un text cunoscandu-se ponderile caracterelor (doar pentru caracterele alfabetice) implementand acelasi algoritm Huffman . Arborele binar Huffman este reprezentat printr-o clasa C++ iar operatiile prin functii membre. Pentru codarea textului examen de licenta se obtine urmatorul rezultat:
-55-

Fig. 3.1

CONCLUZII

n compresia datelor un rol important il are teoria codarii i teoria informaiei. Compresia se refer la micorarea redundanei mesajului de transmis. Compresia de poate face: cu pierdere de informaie, aa cum este cazul surselor continue (analogice) discretizate, reprezentate numeric n calculator; fara pierdere de informatie, aa cum este n cazul textelor. Ca masura a compresiei se foloeste - cel mai des marimea numit raportul de compresie, ca raport intre marimea fiierului comprimat i mrimea fiierului iniial, necomprimat. Un criteriu complet al calitii compresiei trebuie s se considere ns i complexitatea algoritmilor de codare/decodare.

-56-

Compresia datelor este necesar n zilele noastre i este foarte des folosit, mai ales n domeniul aplicaiilor multimedia. O metod static este aceea n care transformarea mulimii mesajelor n cuvinte de cod este fixat nainte de nceperea transmisiei Metodele de compresie text pot fi mprite n doua categorii: statistice i nestatistice. O metod static este aceea n care transformarea mulimii mesajelor n cuvinte de cod este fixat nainte de nceperea transmisiei. Un exemplu de codare static, bazat de cuvinte de cod, este codarea Huffman (static). Un cod este dinamic (dynamic) dac codarea mulimii mesajelor sursei primare se schimb n timp. Codarea Huffman dinamic presupune estimarea probabilitilor de apariie n timpul codrii, n timp ce mesajul este procesat. Cea mai mare problem n implementarea codului Huffman este preocuparea pentru frecvene. Dac codm un singur mesaj, o dat i nc o dat, de mai multe ori, ca o simpl copiere, asta-i simplu. Dar dac vrem un mesaj mult mai lung ntr-un pachet de mesaje este mai dificil. n acest caz, o soluie bun este s folosim frecvene tipice pe care dorim s le scriem. Putem implementa rutine pentru analizarea setului de fiiere text pentru frecvenele caracterelor lor. Rezultatele sunt nregistrate ntr-un fiier special de frecvene cu care codul creat Huffman poate fi folosit pentru citirea i scrierea fiierelor. Structura de baz folosit la implementarea algoritmului de compresie este structura arbore. Algoritmul de compresie Huffman este unul relativ simplu de neles i de implementat. Pentru implementarea n limbajele de programare se pot utilize tipurile de date structurate cum ar fi tipul struct n C sau class n C++. Metoda de compresie bazat pe arbori Huffman statici are i o serie de dezavantaje: 1. Fiierul de compactat trebuie parcurs de dou ori: o dat pentru a calcula numrul de apariii ale caracterelor n text, a doua oar pentru a comprima efectiv fiierul. Deci metoda nu poate fi folosit pentru transmisii de date pentru care nu este posibil reluarea. 2. Pentru o dezarhivare ulterioar a fiierului, aa cum este i firesc, este necesar memorarea arborelui Huffman utilizat pentru comprimare, ceea ce conduce la mrirea dimensiunii codului generat.

-57-

3. Arborele Huffman generat este static, metoda nefiind capabil s se adapteze la variaii locale ale frecvenelor caracterelor. Aceste dezavantaje au fost n mare parte eliminate n metodele care folosesc arbori Huffman dinamici, asa cum am aratat n capitolul 2, ideea fiind ca la fiecare nou codificare, arborele Huffman s se reorganizeze astfel nct caracterul respectiv s aib eventual un cod mai scurt. Un algoritm de compresie poate fi evaluat n funcie de necesarul de memorie pentru implementarea algoritmului, viteza algoritmului pe o anumit main, raportul de compresie, calitatea reconstruciei. De obicei, ultimele dou criterii sunt eseniale n adoptarea algoritmului de compresie, iar eficiena lui este caracterizat de compromisul criteriilor enumerate. Despre metodele de compresie Huffman, n special cele dinamice, putem spune ca satisfac aceste cerine. Dei este cea mai veche metoda de comprimare , algoritmii Huffman sunt folosii de ctre programele de arhivare cu o rat de compresie ridicat mai ales la comprimarea fiierelor de tip text.

ANEXA 1 - Program C -

/*************************************************************************** * Codare i decodare Huffman * * Fisier : huffman.c * Scop : Compresia/decompresia fisierelor folosind algoritmul Huffman ***************************************************************************/ /*************************************************************************** * FISIERE INCLUSE ***************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <string.h> #include "alegopt.h" -58-

/*************************************************************************** * TIPURI DEFINITE ***************************************************************************/ /* tipurile sistemului */ typedef unsigned char octet_t; /* 8 biti fara semn */ typedef unsigned short cod_t; /* 16 biti fara semn pentru caracterele codurilor */ typedef unsigned int frecventa_t; /* 32 biti fara semn pentru caracterele cautate */ /* foloseste preprocesorul pentru a verifica lungimea tipurilor*/ #if (UCHAR_MAX != 255) #error Acest program depaseste tipul char fara semn de 1 octet #endif #if (USHRT_MAX != 65535) #error Acest program depaseste tipul short fara semn de 2 octeti #endif /* frecventa_t n tablou al octet_t */ typedef union frecventa_octet_t { frecventa_t frecventa; octet_t octet[sizeof(frecventa_t)]; } frecventa_octet_t; typedef struct huffman_nod_t { int valoare; /* valoarea caracterului introdus */ frecventa_t frecventa; /* numarul de evenimente al valorii (probabilitati) */ char ignorat; /* ADEVARAT -> deja intalnit sau nu are nevoie sa fie luat */ int nivel; /* nivelul n arbore (radacina are nivelul 0) */ /*********************************************************************** * pointer spre fiu i tatal. ***********************************************************************/ struct huffman_nod_t *stanga, *dreapta, *tatal; } huffman_nod_t; typedef struct cod_lista_t { octet_t codLungime; cod_t cod; } cod_lista_t; typedef enum { CONSTRUIRE_ARBORE, COMPRESIE, DECOMPRESIE } MODURI; /*************************************************************************** * CONSTANTE ***************************************************************************/ #define FALS 0 #define ADEVARAT 1 #define NIMIC -1 #define CARACTERE_T_MAX UINT_MAX #define NOD_COMBINAT -1 #define NUM_CARACTERE 256 /* bazat pe tipul definit frecventa_t */ /* nod reprezentand caractere multiple */ /* 256 simboluri posibile pe 1 octet */

/* numarul de biti folositi n cod */ /* cod folosit pentru simbol */

-59-

#define MAX(a, b) ((a)>(b)?(a):(b)) /*************************************************************************** * VARIABILE GLOBALE ***************************************************************************/ huffman_nod_t *huffmanTablou[NUM_CARACTERE]; /* tablou pentru toate caracterele*/ frecventa_t totalCaractere = 0; /* numarul total de caractere */ /*************************************************************************** * PROTOTIPURI ***************************************************************************/ /* alocare/dezalocare rutine */ huffman_nod_t * AlocareNodHuffman (int valoare); huffman_nod_t *AlocareNodCombinatHuffman(huffman_nod_t *stanga, huffman_nod_t *dreapta); void EliberareArboreHuffman(huffman_nod_t *ah); /* constructia i aratarea arborelui */ huffman_nod_t *ArboreGeneratDinFisier(char *inFisier); huffman_nod_t *ConstruireArboreHuffman(huffman_nod_t **ah, int elemente); void TiparireCod(huffman_nod_t *ah, char *outFisier); void CodareFisier(huffman_nod_t *ah, char *inFisier, char *outFisier); void DecodareFisier(huffman_nod_t **ah, char *inFisier, char *outFisier); void CreareCodLista(huffman_nod_t *ah, cod_lista_t *cl); /* citire-scriere arbore n fisier*/ void ScriereHeader(huffman_nod_t *ah, FILE *pf); void CitireHeader(huffman_nod_t **ah, FILE *pf); /*************************************************************************** * FUNCTII ***************************************************************************/ /**************************************************************************** * Functia : Main * Descriere : Aceasta este functia main pentru acest program,ea valideaza liniile de * comanda de intrare si, daca sunt valide, ea va construi un arbore huffman pentru fisierul de intrare, un fisier huffman de codare , sau un fisier de * decodare huffman. * Parametri : argc - numarul de parametri * argv - lista de parametri * Efect : Construieste un arbore huffman pentru fisierul specificat i la iesire * rezulta cod spre stdout. * Returneaza : EXIT_SUCCES pentru succes, altfel returneaza EXIT_FAILURE. ****************************************************************************/ int main (int argc, char *argv[]) { huffman_nod_t *arboreHuffman; /* radacina arborelui huffman */ int opt; char *inFisier, *outFisier; MODURI mod; /* initializare variabile */ inFisier = NULL; outFisier = NULL; mod = CONSTRUIRE_ARBORE; -60-

/* analizarea liniilor de comanda */ while ((opt = alegopt(argc, argv, "cdli:o:h?")) != -1) { switch(opt) { case 'c': /* compresie mod */ mod = COMPRESIE; break; case 'd': /* decompresie mod */ mod = DECOMPRESIE; break; case 'l': /* doar constructie i afisare arbore*/ mod = CONSTRUIRE_ARBORE; break; case 'i': /* nume fisier de intrare */ if (inFisier != NULL) { fprintf(stderr, "Multiple fisiere de intrare nu sunt permise.\n"); free(inFisier); if (outFisier != NULL) { free(outFisier); } exit(EXIT_FAILURE); } else if ((inFisier = (char *)malloc(strlen(optarg) + 1)) == NULL) { perror("Alocarea memoriei"); if (outFisier != NULL) { free(outFisier); } exit(EXIT_FAILURE); } strcpy(inFisier, optarg); break; case 'o': /* nume fisier de iesire */ if (outFisier != NULL) { fprintf(stderr, " Multiple fisiere de iesire nu sunt permise.\n"); free(outFisier); if (inFisier != NULL) { free(inFisier); } exit(EXIT_FAILURE); } -61-

else if ((outFisier = (char *)malloc(strlen(optarg) + 1)) == NULL) { perror("Alocarea memoriei"); if (inFisier != NULL) { free(inFisier); } exit(EXIT_FAILURE); } strcpy(outFisier, optarg); break; case 'h': case '?': printf("Utilizare: huffman <optiuni>\n\n"); printf("optiuni:\n"); printf(" -c : Fisier intrare codat n fisier de iesire.\n"); printf(" -d : Fisier intrare decodat n fisier de iesire.\n"); printf(" -t : Generare lista coduri fisier intrare n fisier de iesire.\n"); printf(" -i <nume_fisier> : Nume fisier de intrare.\n"); printf(" -o <nume_fisier> : Nume fisier de iesire.\n"); printf(" -h|? : Tiparire optiuni linia de comanda.\n\n"); return(EXIT_SUCCESS); } } /* validare linie de comanda */ if (inFisier == NULL) { fprintf(stderr, "Fisierul de intrare trebuie furnizat\n"); fprintf(stderr, "Intrare \"huffman -?\" pentru ajutor.\n"); exit (EXIT_FAILURE); } if ((mod == COMPRESIE) || (mod == CONSTRUIRE_ARBORE)) { arboreHuffman = ArboreGeneratDinFisier(inFisier); /* determinam ce facem cu arborele */ if (mod == COMPRESIE) { /* scriem fisier de codare */ CodareFisier(arboreHuffman, inFisier, outFisier); } else { /* doar cod de iesire */ TiparireCod(arboreHuffman, outFisier); } EliberareArboreHuffman(arboreHuffman); /* eliberarea memoriei alocate*/ } else if (mod == DECOMPRESIE) { DecodareFisier(huffmanTablou, inFisier, outFisier); } -62-

/* stergere*/ free(inFisier); if (outFisier != NULL) { free(outFisier); } return(EXIT_SUCCESS); } /*************************************************************************** * Functie : ArboreGeneratDinFisier * Descriere : Aceasta functie creeaza un arbore huffman optim pentru codarea fisieruuil luat * ca parametru. * Parametri : inFisier - numele fisierului pentru creare arbore * Efecte : Arborele Huffman este construit pentru fisier. * Returneza : Pointer spre arborele rezultat. ***************************************************************************/ huffman_nod_t *ArboreGeneratDinFisier(char *inFisier) { huffman_nod_t *arboreHuffman; /* radacina arborelui huffman */ int c; FILE *pf; /* deschide fisierul ca binar */ if ((pf = fopen(inFisier, "rb")) == NULL) { perror(inFisier); exit(EXIT_FAILURE); } /* alocare tablou pentru toate caracterele posibile */ for (c = 0; c < NUM_CARACTERE; c++) { huffmanTablou[c] = AlocareNodHuffman(c); } /* frecventa probabilitati fiecare caracter */ while ((c = fgetc(pf)) != EOF) { if (totalCaractere < CARACTERE_T_MAX) { totalCaractere++; /* incrementare frecventa pentru caracter i il includem n arbore */ huffmanTablou[c]->frecventa++; /* cauta pentru depasire */ huffmanTablou[c]->ignorat = FALS; } else { fprintf(stderr, "Numarul de caractere n fisier este prea mare pt. a calcula frecventa. \n"); exit(EXIT_FAILURE); } } fclose(pf); /* pune tablou de evenimente intr-un arbore huffman */ arboreHuffman = ConstruireArboreHuffman(huffmanTablou, NUM_CARACTERE); -63-

return(arboreHuffman); } /*************************************************************************** * Functie : AlocareNodHuffman * Descriere : Aceasta rutina aloca i initializeaza memoria pentru un nod (intrare arbore * pentru un singur caracter) n arborele Huffman. * Parametri : valoare - caracterul valoare reprezentat de acest nod * Efect : Memoria pentru huffman_nod_t este alocata din memoria heap * Returneaza : Pointer pentru alocare nod. NULL pentru insuccesul alocarii. ***************************************************************************/ huffman_nod_t *AlocareNodHuffman(int valoare) { huffman_nod_t *ah; ah = (huffman_nod_t *)(malloc(sizeof(huffman_nod_t))); if (ah != NULL) { ah->valoare = valoare; ah->ignorat = ADEVARAT;

/* va fi FALS daca se gaseste unul */

/* la acest punct, nodul nu face parte din arbore */ ah->frecventa = 0; ah->nivel = 0; ah->stanga = NULL; ah->dreapta = NULL; ah->tatal = NULL; } else { perror("Alocare Nod"); exit(EXIT_FAILURE); } return ah; } /**************************************************************************** * Functie : AlocareNodCombinatHuffman * Descriere: Aceasta rutina aloca i initializeaza memoria pentru un nod combinat (intrare * arbore pentru multiple caractere) intr-un arbore Huffman .Numarul de * evenimente pentru un nod combinat este suma evenimentelor (probabilitatilor) * fiilor sai. * Parametri : stanga - fiu stanga n arbore * dreapta - fiu dreapta n arbore * Efecte : Memoria pentru un huffman_nod_t este alocata din memoria heap * Returneaza : Pointer spre nodul alocat ****************************************************************************/ huffman_nod_t *AlocareNodCombinatHuffman(huffman_nod_t *stanga, huffman_nod_t *dreapta) { huffman_nod_t *ah; ah = (huffman_nod_t *)(malloc(sizeof(huffman_nod_t))); if (ah != NULL) { -64-

ah->valoare = NOD_COMBINAT; /* reprezinta multiple caractere */ ah->ignorat = FALS; ah->frecventa = stanga->frecventa + dreapta->frecventa; /* suma fiilor */ ah->nivel = max(stanga->nivel, dreapta->nivel) + 1; /* ataseaza fiu */ ah->stanga = stanga; ah->stanga->tatal = ah; ah->dreapta = dreapta; ah->dreapta->tatal = ah; ah->tatal = NULL; } else { perror("Alocare Nod Combinat"); exit(EXIT_FAILURE); } return ah; } /**************************************************************************** * Functie : EliberareArboreHuffman * Descriere : Aceasta este o rutina recursiva pentru eliberarea memoriei alocate pentru un * nod i a tuturor descendentilor sai. * Parametri : ah - structura de stergere impreuna cu fii sai. * Efecte : Memoria pentru un huffman_nod_t i fii sai este returnata memoriei heap. * Returneaza : Nimic ****************************************************************************/ void EliberareArboreHuffman(huffman_nod_t *ah) { if (ah->stanga != NULL) { EliberareArboreHuffman(ah->stanga); } if (ah->dreapta != NULL) { EliberareArboreHuffman(ah->dreapta); } free(ah); } /**************************************************************************** * Functie : FrecventaMinimuluiGasit * Descriere: Aceasta functie cauta n tablou huffman_struct pentru a gasi elementul activ * (ignorat == FALS) cu cea mai mica frecventa cautata. n ordinea cautarii * arborelui care-l vad, daca doua noduri au aceeasi frecventa, nodul cu nivelul mai * mic este selectat. * Parametri : ah - pointer spre tablou structurii de frecventa * elemente - numar de elemente din tablou * Efect : Nimic * Returneaza : Indicele elementului activ cu cea mai mica frecventa. * Este returnat NIMIC daca nu sunt gasite minime. ****************************************************************************/ int FrecventaMinimuluiGasit(huffman_nod_t **ah, int elemente) { int i; /* indicele tabloului */ -65-

int IndiceleCurent = NIMIC;

/* indicele cu cea mai mica frecventa vazut pana n prezent */ int FrecventaCurenta = INT_MAX; /* frecventa cea mai mica pana n prezent */ int NivelulCurent = INT_MAX; /* niveull cautarii celei mai mici pana n prezent */ /* tablou de frecventa secvential */ for (i = 0; i < elemente; i++) { /* cauta pentru cea mai mica frecventa (sau egala ca cea mai mica) */ if ((ah[i] != NULL) && (!ah[i]->ignorat) && (ah[i]->frecventa < FrecventaCurenta || (ah[i]->frecventa == FrecventaCurenta && ah[i]->nivel < NivelulCurent))) { IndiceleCurent = i; FrecventaCurenta = ah[i]->frecventa; NivelulCurent = ah[i]->nivel; } } return IndiceleCurent; } /**************************************************************************** * Functie : ConstruireArboreHuffman * Descriere: Aceasta functie construieste un arbore huffman din tablou huffmann_struct. * Parametri : ah - pointer spre tablou de structuri ce vor fi cautate * elemente - numar de elemente din tablou * Efecte : Tablou huffman_nod_t este construit intr-un abore huffman. * Returneaza : Pointer spre radacina lui Huffman Arbore ****************************************************************************/ huffman_nod_t *ConstruireArboreHuffman(huffman_nod_t **ah, int elemente) { int min1, min2; /* doua noduri cu cea mai mica frecventa */ /* sa cautam pana ce nu mai sunt noduri gasite */ for (;;) { /*gaseste nodul cu cea mai mica frecventa */ min1 = FrecventaMinimuluiGasit(ah, elemente); if (min1 == NIMIC) { /* nu mai sunt noduri de combinat */ break; } ah[min1]->ignorat = ADEVARAT; /* gaseste nodul ce cea de a doua frecventa dintre cele mici */ min2 = FrecventaMinimuluiGasit(ah, elemente); if (min2 == NIMIC) { /* nu mai sunt noduri de combinat */ break; } ah[min2]->ignorat = ADEVARAT; /* combina nodurile intr-un arbore */ -66-

ah[min1] = AlocareNodCombinatHuffman(ah[min1], ah[min2]); ah[min2] = NULL; } return ah[min1]; } /**************************************************************************** * Functie : TiparireCod * Descriere: Aceasta functie da arborele huffman tiparind la iesire codul nodului fiecarui * caracter intalnit.. * Parametri : ah - pointer spre radacina arborelui * outFisier - unde sunt rezultatele de iesire (NULL -> stdout) * Efect : Codul pentru caracterele continute n arbore sunt tiparite n outFisier. * Returneaza : Nimic ****************************************************************************/ void TiparireCod(huffman_nod_t *ah, char *outFisier) { char cod[50]; int adancime = 0; FILE *pf; if (outFisier == NULL) { pf = stdout; } else { if ((pf = fopen(outFisier, "w")) == NULL) { perror(outFisier); EliberareArboreHuffman(ah); exit(EXIT_FAILURE); } } /* tiparire */ fprintf(pf, "Caracter Frecventa Codat\n"); fprintf(pf, "-------- --------- ------------\n"); for(;;) { /* urmeaza toata calea din stanga */ while (ah->stanga != NULL) { cod[adancime] = '0'; ah = ah->stanga; adancime++; } if (ah->valoare != NOD_COMBINAT) { /* cand avem nodul unui caracter, i-i tiparim codul */ cod[adancime] = '\0'; /* n cazul de EOF */ fprintf(pf, "0x%02X } %5d %s\n", ah->valoare, ah->frecventa, cod);

-67-

while (ah->tatal != NULL) { if (ah != ah->tatal->dreapta) { /* incercam tatal n dreapta */ cod[adancime - 1] = '1'; ah = ah->tatal->dreapta; break; } else { /* tatal din dreapta am incercat, mergem mai sus un nivel */ adancime--; ah = ah->tatal; cod[adancime] = '\0'; } } if (ah->tatal == NULL) { /* suntem n varf i nu avem unde sa mai mergem */ break; } } if (outFisier != NULL) { fclose(pf); } } /**************************************************************************** * Functie : CodareFisier * Descriere: Aceasta functie foloseste arborele Huffman furnizat pentru codarea fisierului * luat ca parametru. * Parametri : ah - pointer spre radacina arbore huffman * inFisier - fisier de intrare pentru codare * outFisier - unde se pun rezultatele de iesire (NULL -> stdout) * Efect : inFisier este codat i codul plus rezultatele sunt scrise n fisierul outFisier. * Returneaza : Nimic ****************************************************************************/ void CodareFisier(huffman_nod_t *ah, char *inFisier, char *outFisier) { cod_lista_t codLista[NUM_CARACTERE]; /* tabel pentru codare */ FILE *pfIn, *pfOut; int c, i, bitFrecventa; cod_t cod, masca; char bitBufer; /* deschide intrarea binara i fisierul de iesire */ if ((pfIn = fopen(inFisier, "rb")) == NULL) { perror(inFisier); exit(EXIT_FAILURE); } if (outFisier == NULL) { pfOut = stdout; -68-

} else { if ((pfOut = fopen(outFisier, "wb")) == NULL) { perror(outFisier); EliberareArboreHuffman(ah); exit(EXIT_FAILURE); } } ScriereHeader(ah, pfOut); CreareCodLista(ah, codLista); /* scrie fisierul codat pe 1 octet */ bitBufer = 0; bitFrecventa = 0; masca = 1 << ((sizeof(cod_t) * 8) - 1); while((c = fgetc(pfIn)) != EOF) { cod = codLista[c].cod; /* deplasare biti */ for(i = 0; i < codLista[c].codLungime; i++) { bitFrecventa++; bitBufer = (bitBufer << 1) | ((cod & masca) != 0); cod <<= 1; if (bitFrecventa == 8) { /* avem un octet n bufer */ fputc(bitBufer, pfOut); bitFrecventa = 0; } } } if (bitFrecventa != 0) { bitBufer <<= 8 - bitFrecventa; fputc(bitBufer, pfOut); } fclose(pfIn); fclose(pfOut); } /**************************************************************************** * Functie : CreareCodLista * Descriere: Aceasta functie foloseste un arbore huffman pentru a construi o lista de coduri * i lungimile lor pentru fiecare simbol codat. Aceasta simplifica procesul de * codare. n locul tranversarii arborelui pentru frecventa codului pentru orice * simbol,codul poate fi gasit accesand cl[simbol].cod. * Parametri : ah - pointer spre radacina arborelui huffman * cl - cod lista . * Efect : Valoarile codurilor se afla toate n lista de coduri. * Returneaza : Nimic -69/* scriere header pentru refacerea arborelui */ /* converteste cod pentru a folosi usor lista */

****************************************************************************/ void CreareCodLista(huffman_nod_t *ah, cod_lista_t *cl) { cod_t cod = 0; octet_t adancime = 0; for(;;) { /* urmeaza toata calea din stanga */ while (ah->stanga != NULL) { cod = cod << 1; ah = ah->stanga; adancime++; } if (ah->valoare != NOD_COMBINAT) { /* introduse rezultatele n lista */ cl[ah->valoare].codLungime = adancime; cl[ah->valoare].cod = cod; /* */ cl[ah->valoare].cod <<= ((sizeof(cod_t) * 8) - adancime); } while (ah->tatal != NULL) { if (ah != ah->tatal->dreapta) { /* incearca tatal este dreapta */ cod |= 1; ah = ah->tatal->dreapta; break; } else { /* tatal n dreapta a fost incercat, mergem un nivel mai sus */ adancime--; cod = cod >> 1; ah = ah->tatal; } } if (ah->tatal == NULL) { /*suntem n varf, nu avem unde sa mai mergem */ break; } } } /*************************************************************************** * Functie : ScriereHeader * Descriere: Aceasta functie scrie fiecare simbol continut n arbore precum i numarul de evenimente n fisierul original n fisierul de iesire specificat. Daca acelasi algoritm care a produs arborele original este folosit cu aceste cautari, o copie exacta a arborelui va fi produsa. * Parametri : ah - pointer spre radacina arborelui huffman * pf - pointer de a deschide fisierul binar n care se scrie. -70-

* Efect : Valoarile simbolurilor i cautarile simbolurilor sunt scrise. * Returneaza : Nimic ***************************************************************************/ void ScriereHeader(huffman_nod_t *ah, FILE *pf) { frecventa_octet_t octetUniune; int i; for(;;) { /* urmeaza calea din stanga */ while (ah->stanga != NULL) { ah = ah->stanga; } if (ah->valoare != NOD_COMBINAT) { /* scrie simbolul i cauta dupa header */ fputc(ah->valoare, pf); octetUniune.frecventa = ah->frecventa; for(i = 0; i < sizeof(frecventa_t); i++) { fputc(octetUniune.octet[i], pf); } } while (ah->tatal != NULL) { if (ah != ah->tatal->dreapta) { ah = ah->tatal->dreapta; break; } else { /* tatal din dreapta este incercat,mergem mai sus un nivel */ ah = ah->tatal; } } if (ah->tatal == NULL) { /*suntem n varf, nu avem unde sa mergem */ break; } } /* acum scriem sfarsitul tabelei caracterr 0 */ fputc(0, pf); for(i = 0; i < sizeof(frecventa_t); i++) { fputc(0, pf); } } /*************************************************************************** * Functie : DecodareFisier -71-

* Descriere: Aceasta functie decodeaza un fisier codat huffman, scriind rezultatele intr-un * fisier de iesire specificat. * Parametri : ah - pointer spre tablou al poiterilor spre nod arbore * inFisier - fisier de decodare * outFisier - unde sunt rezultatele de iesire (NULL -> stdout) * Efect : inFisier este decodat i rezultatele sunt scrise n outFisier. * Returneaza : Nimic ****************************************************************************/ void DecodareFisier(huffman_nod_t **ah, char *inFisier, char *outFisier) { huffman_nod_t *arboreHuffman, *NodulCurent; int i, c; FILE *pfIn, *pfOut; if ((pfIn = fopen(inFisier, "rb")) == NULL) { perror(inFisier); exit(EXIT_FAILURE); return; } if (outFisier == NULL) { pfOut = stdout; } else { if ((pfOut = fopen(outFisier, "wb")) == NULL) { perror(outFisier); exit(EXIT_FAILURE); } } /* aloca tablou pentru toate posibilele caractere */ for (i = 0; i < NUM_CARACTERE; i++) { ah[i] = AlocareNodHuffman(i); } CitireHeader(ah, pfIn); /* pune tabloul intr-un arbore huffman */ arboreHuffman = ConstruireArboreHuffman(ah, NUM_CARACTERE); /* acum noi avem un arbore Huffman folositt pentru codare */ NodulCurent = arboreHuffman; while ((c = fgetc(pfIn)) != EOF) { /* traversare arbore potrivit gasit pentru caracterele noastre */ for(i = 0; i < 8; i++) { if (c & 0x80) { NodulCurent = NodulCurent->dreapta; } else { NodulCurent = NodulCurent->stanga; -72-

} if (NodulCurent->valoare != NOD_COMBINAT) { /* am gasit un caracter */ fputc(NodulCurent->valoare, pfOut); NodulCurent = arboreHuffman; totalCaractere--; if (totalCaractere == 0) { /* deja am scris ultimul caracter */ break; } } c <<= 1; } } /* inchide toate fisierele */ fclose(pfIn); fclose(pfOut); EliberareArboreHuffman(arboreHuffman); } /*************************************************************************** * Functie : CitireHeader * Descriere: Aceasta functie citeste informatiile din ScriereHeader. * Parametri : ah - pointer spre tablou de pointeri spre locurile arborelui * pf pointer spre tipul FILE * Efect : Informatia frecventa este citita n nodul lui ah * Returneaza : Nimic ***************************************************************************/ void CitireHeader(huffman_nod_t **ah, FILE *pf) { frecventa_octet_t octetUniune; int c; int i; while ((c = fgetc(pf)) != EOF) { for (i = 0; i < sizeof(frecventa_t); i++) { octetUniune.octet[i] = (octet_t)fgetc(pf); } if ((octetUniune.frecventa == 0) && (c == 0)) { /* tocmai am citit sfarsitul tabelei marcate */ break; } ah[c]->frecventa = octetUniune.frecventa; ah[c]->ignorat = FALS; totalCaractere += octetUniune.frecventa; } }

/* memoria alocata este eliberata */

-73-

ANEXA 2 - Program C++ -

#include "huffman.h" #include <map> #include <iostream> #include <ostream> #include <algorithm> #include <iterator> #include <string> using namespace std; int ii; std::ostream& operator<<(std::ostream& os, std::vector<bool> vec) { std::copy(vec.begin(), vec.end(), std::ostream_iterator<bool>(os, "")); return os; } int main() { std::map<char, double> frecventa; frecventa['e'] = 0.124167; frecventa['a'] = 0.0820011; frecventa['t'] = 0.0969225; frecventa['i'] = 0.0768052; frecventa['n'] = 0.0764055; frecventa['o'] = 0.0714095; frecventa['s'] = 0.0706768; frecventa['r'] = 0.0668132; frecventa['l'] = 0.0448308; frecventa['d'] = 0.0363709; frecventa['h'] = 0.0350386; frecventa['c'] = 0.0344391; frecventa['u'] = 0.028777; frecventa['m'] = 0.0281775; frecventa['f'] = 0.0235145; frecventa['p'] = 0.0203171; frecventa['y'] = 0.0189182; frecventa['g'] = 0.0181188; frecventa['w'] = 0.0135225; frecventa['v'] = 0.0124567; frecventa['b'] = 0.0106581; frecventa['k'] = 0.00393019; frecventa['x'] = 0.00219824; frecventa['j'] = 0.0019984; frecventa['q'] = 0.0009325; frecventa['z'] = 0.000599; frecventa[' '] = 0.081599; Hufftree<char, double> hufftree(frecventa.begin(), frecventa.end()); for (char ch = 'a'; ch <= 'z'; ++ch) { std::cout << ch << ": " << hufftree.encode(ch) << "\n"; -74-

} std::string source = "sursa"; cout<<"Introduceti un text:"; cin>>source; cout<<"S-a codat:"; std::vector<bool> encoded = hufftree.encode(source.begin(), source.end()); std::cout << encoded << "\n"; cout<<"S-a decodat:"; std::string destination; hufftree.decode(encoded, std::back_inserter(destination)); std::cout << destination << "\n"; cin>>ii; } #ifndef HUFFMAN_H_INC #define HUFFMAN_H_INC #include <vector> #include <map> #include <queue> template<typename DataType, typename Frequency> class Hufftree { public: template<typename InputIterator> Hufftree(InputIterator begin, InputIterator end); ~Hufftree() { delete tree; } template<typename InputIterator> std::vector<bool> encode(InputIterator begin, InputIterator end); std::vector<bool> encode(DataType const& value) { return encoding[value]; } template<typename OutputIterator> void decode(std::vector<bool> const& v, OutputIterator iter); private: class Node; Node* tree; typedef std::map<DataType, std::vector<bool> > encodemap; encodemap encoding; class NodeOrder; }; template<typename DataType, typename Frequency> struct Hufftree<DataType, Frequency>::Node { Frequency frequency; Node* leftChild; union { Node* rightChild; // daca leftChild != 0 DataType* data; // daca leftChild == 0 }; -75-

Node(Frequency f, DataType d): frequency(f), leftChild(0), data(new DataType(d)) { } Node(Node* left, Node* right): frequency(left->frequency + right->frequency), leftChild(left), rightChild(right) { } ~Node() { if (leftChild) { delete leftChild; delete rightChild; } else { delete data; } } void fill(std::map<DataType, std::vector<bool> >& encoding, std::vector<bool>& prefix) { if (leftChild) { prefix.push_back(0); leftChild->fill(encoding, prefix); prefix.back() = 1; rightChild->fill(encoding, prefix); prefix.pop_back(); } else encoding[*data] = prefix; } }; template<typename DataType, typename Frequency> template<typename InputIterator> Hufftree<DataType, Frequency>::Hufftree(InputIterator begin, InputIterator end): tree(0) { std::priority_queue<Node*, std::vector<Node*>, NodeOrder> pqueue; while (begin != end) { Node* dataNode = new Node(begin->second, begin->first); pqueue.push(dataNode); ++begin; } while (!pqueue.empty()) { Node* top = pqueue.top(); pqueue.pop(); if (pqueue.empty()) { -76-

tree = top; } else { Node* top2 = pqueue.top(); pqueue.pop(); pqueue.push(new Node(top, top2)); } } std::vector<bool> bitvec; tree->fill(encoding, bitvec); } template<typename DataType, typename Frequency> struct Hufftree<DataType, Frequency>::NodeOrder { bool operator()(Node* a, Node* b) { if (b->frequency < a->frequency) return true; if (a->frequency < b->frequency) return false; if (!a->leftChild && b->leftChild) return true; if (a->leftChild && !b->leftChild) return false; if (a->leftChild && b->leftChild) { if ((*this)(a->leftChild, b->leftChild)) return true; if ((*this)(b->leftChild, a->leftChild)) return false; return (*this)(a->rightChild, b->rightChild); } return *(a->data) < *(b->data); }}; template<typename DataType, typename Frequency> template<typename InputIterator> std::vector<bool> Hufftree<DataType, Frequency>::encode(InputIterator begin, InputIterator end) { std::vector<bool> result; while (begin != end) { typename encodemap::iterator i = encoding.find(*begin); result.insert(result.end(), i->second.begin(), i->second.end()); ++begin; } return result; } template<typename DataType, typename Frequency> template<typename OutputIterator> void Hufftree<DataType, Frequency>::decode(std::vector<bool> const& v, OutputIterator iter) { Node* node = tree; for (std::vector<bool>::const_iterator i = v.begin(); i != v.end(); ++i) { node = *i? node->rightChild : node->leftChild; if (!node -> leftChild) { *iter++ = *(node->data); -77-

node = tree; } }} #endif

BIBLIOGRAFIE

1. Appel K. i Haken W., Algoritmi greedy, 1976 2. Cisma C. S. , MPEG,- standardul revoluiei audio-video digitale 3. Craw I., Huffman Coding, 2001-04-27 4. Frncu C., Caraiman A., PCREPORT,2002 5. Huber A. W., Hoffman Codes, Spring 2002 6. Munteanu V.,Teoria transmisiunii informaiei, Institutul Politehnic Gh.Asachi, Iai ,1979 7.Murgan A.T., Principiile teoriei informaiei n ingineria informaiei i a comunicaiilor, Editura Academiei Romne, Bucureti, 1998
-78-

8. Schildt H., C manual complet, Editura Teora, 2000 7. Spataru A., Teoria Transmisiunii Informaiei, Editura Didactic i Pedagogic, Bucureti,1983. 9.Stoian R., Rdescu R., Coduri utilizate n prelucrarea numeric a semnalelor, Editura U.P.B., 1994 10. Soroiu Claudiu, Compresia datelor, serial Gazeta de informatic nr.13/1, ClujNapoca, ianuarie 2003. 11. Soroiu Claudiu, Compresia datelor, serial Gazeta de informatic nr.13/2, ClujNapoca, februarie 2003.

-79-