Sunteți pe pagina 1din 13

6.

Arbori de compresie Huffman


n analiza algoritmilor pe care i-am studiat pn acum prioritar a fost complexitatea timp. n acest capitol ne punem problema elaborrii unor algoritmi 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 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 b c d e f cod fix 000 001 010 011 100 101 ar fi necesari n total 300.000 bii. S presupunem acum c frecvenele cu care apar n text cele 6 caractere sunt : a b c d e f frecven 45 13 12 16 9 5 Considernd urmtorul cod de lungime variabil : a b c d e f cod variabil 0 101 100 111 1101 1100 ar fi necesari doar 224.000 bii (deci o reducere a spaiului de memorie cu aproximativ 25%). Problema se reduce deci la a asocia fiecrui caracter un cod
218

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). 6.1. Codul Huffman D. Huffman a elaborat un algoritm Greedy 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. 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 f1,f2,...,fn, respectiv, numrul lor de apariii. Dac li ar fi lungimea irului ce codific simbolul ci, atunci lungimea total a reprezentrii ar fi : L = li fi
i =1 n

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. 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
219

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

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

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

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

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

220

Fig. 6. 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 b c d e f cod 0 100 101 110 1110 1111 Observaii - 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 min-heap. Astfel timpul de execuie pentru operaiile de extragere minim, inserare i tergere va fi, n cazul cel mai defavorabil, de O(log n). Algoritm de construcie a arborelui Huffman* 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;
Programul Huffman realizeaz compactarea i decompactarea unui fiier text utiliznd arbori Huffman.
*

221

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 - Z^.st := X - Z^.dr := Y - Z^.frecv := X^.frecv+Y^.frecv - insereaz Z n heap; Pas 3. Singurul nod rmas n heap este rdcina arborelui Huffman. Se genereaz codurile caracterelor, parcurgnd arborele Huffman. Analiza complexitii 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). Corectitudinea algoritmului Trebuie s demonstrm c algoritmul lui Huffman produce un cod prefix optimal. Lema 1. Fie x, y 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 frecvene rezult c f[x] f[a] i f[y] f[b]. Transformm arborele T n T', schimbnd pe a cu x :

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


C (T ) = f (c) nivT (c) C (T ) C (T ') = f ( x) nivT ( x) + f (a) nivT (a) f ( x) nivT ' ( x) f (a) nivT ' (a) 222
cC

nivT '( x)= nivT (a) C (T ) C (T ') = ( f (a) f ( x)) (nivT (a) nivT ( x)) 0, pentru c nivT '(a)= nivT ( x)

C (T ') C (T '') = ( f (b) f ( y)) (nivT (b) nivT ( y)) 0.

n mod analog, construim arborele T, schimbnd pe x cu y. Obinem:

Deci C(T) C(T''), dar cum T era optimal deducem c Teste arbore optimal i n plus x i y sunt noduri terminale situate pe nivelul maxim, fii ai aceluiai nod. Q.E.D. 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}) { z}, f[z] fiind f[x]+f[y]. Demonstraie:
cC o , yr nivT (c)= nivT '(c) x , nivT ( x)= nivT ( y)= nivT ( z)+1 f ( x) nivT ( x) + f ( y) nivT ( y) =

U V W

= ( f ( x) + f ( y)) (nivT ( z) + 1) = f ( z) nivT ( z) + f ( x) + f ( y) = = f ( z) nivT ' ( z) + ( f ( x) + f ( y)).

Deci, Dac presupunem prin reducere la absurd c arborele T nu este optimal pentru alfabetul C = (C-{x,y}) {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 T1 pentru alfabetul C, atrnnd pe x [i y ca fii ai lui z. C(T1) = 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. Q.E.D. 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:
223 C (T ) = f (c) nivT (c) = C (T ') + ( f ( x) + f ( y)) c C

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. 6.2. Exerciii 1. Construii arborele Huffman corespunztor alfabetului C={a,b,c,d,e,f,g,h} i frecvenelor: a b c d e f g h frecven 5 1 7 5 8 1 4 10 a 0 5 0 Construii codul Huffman i evaluai necesarul de memorie pentru un fiier cu 100000 de caractere, comparativ cu necesarul de memorie pentru un cod de lungime fix. 2 Care este un cod Huffman pentru alfabetul, C={a,b,c,d,e,f,g,h,i} i frecvenele a b c d e f g h i frecven 1 1 2 3 5 8 1 2 34 a 3 1 Putei generaliza rspunsul pentru cazul n care frecvenele sunt primele n numere din irul Fibonacci? 3. Demonstrai c un arbore binar care nu este strict nu poate corespunde unui cod prefix optimal. 4. Dac presupunem c oricare dou caractere din alfabet au frecvene distincte, atunci arborele Huffman este unic? 5. Demonstrai c nici o schem de compresie nu poate garanta reducerea spaiului de memorie necesar unui fiier de caractere aleatoare pe 8 bii nici mcar cu un singur bit.

224

Anex
program Huffman;
uses crt; const NrMaxNoduri = 256; type Nod = 1..NrMaxNoduri; Arbore = ^NodArbore; NodArbore = record inf: char; f: word; st, dr, t: Arbore; end; Heap = array[Nod] of Arbore; var l, nrbiti: byte; n, i: Nod; h: heap; fisier, farh, fdezarh: text; A: Arbore; x, y, z: Arbore; t: array[char] of Arbore; {t[c]=pointer spre nodul terminal ce contine caracterul c} fr: array[char] of word; {frecventele caracterelor in textul de comprimat} procedure calcul_frecvente; var c: char; begin assign(fisier, 't.in'); reset(fisier); while not eof(fisier) do begin read(fisier, c); inc(fr[c]) end; close(fisier); end; procedure CombHeap(i, n: Nod); {combina heap-ul cu radacina 2i cu heap-ul cu radacina 2i+1 si cu nodul i} var parinte, fiu: Nod; v: Arbore; begin v := h[i]; parinte := i; fiu := 2*i; while fiu <= n do begin if fiu < n then if h[fiu]^.f > h[fiu+1]^.f then fiu := fiu+1; if v^.f > h[fiu]^.f then begin h[parinte] := h[fiu]; parinte := fiu; fiu := fiu*2; end else

225

fiu := n+1; end; h[parinte] := v; end; procedure initializare; var p: Arbore; l: byte; i: Nod; begin n := 1;{numarul efectiv de caractere din text} for l := 1 to 255 do if fr[chr(l)] <> 0 then{acest caracter apare in text} begin new(p); p^.inf := chr(l); p^.f := fr[chr(l)]; p^.st:=nil; p^.dr := nil; p^.t := nil; h[n] := p; t[chr(l)] := p; inc(n) end; dec(n); {constructie heap} for i := n div 2 downto 1 do CombHeap(i, n); write(' Heap-ul este: '); for i := 1 to n do write(h[i]^.f, ' '); writeln; end; function extrage_min:Arbore; {functia extrage din heap cel mai mic element} begin extrage_min := h[1]; h[1] := h[n]; dec(n); CombHeap(1, n); end; procedure insert(x: Arbore);{insereaza in heap arborele x} var fiu, parinte: Nod; begin inc(n); h[n] := x; fiu := n; parinte := n div 2; while (parinte > 0) and (h[parinte]^.f > x^.f) do begin h[fiu] := h[parinte]; fiu := parinte; parinte := parinte div 2 end; h[fiu] := x end; procedure Codificare; {preia fiecare caracter din fisierul sursa si il codifica; fiecare octet obtinut este plasat in fisierul arhiva} var v: byte; c: char;

226

procedure codifica(c: char); {codifica caracterul c} var p: Arbore; d: array[Nod] of 0..1; i, j: Nod; begin i := 1; p := t[c]; {nodul terminal corespunzator lui c} while p^.t <> nil do begin {obtin in d codul lui c in ordine inversa} if p = p^.t^.st then {p este fiu stang} d[i] := 0 else {p este fiu drept} d[i] := 1; inc(i); p := p^.t end; for j := i-1 downto 1 do begin v := v shl 1 +d[j]; inc(nrbiti); if nrbiti = 8 then begin{cand completam un octet scriem caracterul corespunzator in arhiva} write(farh, chr(v)); nrbiti := 0; v := 0; end; end; end; begin {procedura de codificare a fisierului sursa} assign(farh, 'testArh.in'); rewrite(farh); reset(fisier); nrbiti := 0;{numarul de biti din octetul curent} v := 0;{valoarea octetului curent} while not eof(fisier) do begin read(fisier, c); codifica(c); end; if nrbiti <> 0 then begin {ultimul octet, incomplet, trebuie trecut in arhiva} v := v shl (8-nrbiti); write(farh, chr(v));{ nrbiti va indica numarul de biti ce trebuie extrasi din ultimul octet} end; close(fisier); close(farh); end; procedure Decodificare; {preia din fisierul fiecare octet si extrage fiecare bit, deplasandu-se corespunzator in arborele Huffman} var c: char; p: Arbore;

227

procedure decodifica(c: char; sf: boolean); {extrage bitii din octetul corespunzator caracterului c} var i, nr: byte; begin if sf then nr := nrbiti{ultimul octet avea doar nrbiti} else nr := 8; for i := 1 to nr do begin {extragem pe rand cei 8 biti din caracterul c} if (ord(c) shr (8-i)) and 1 = 0 {bit-ul i} then p := p^.st else p := p^.dr; if p^.st = nil then begin {p terminal, am decodificat un caracter} write(fdezarh, p^.inf); p := A; end; end; end; begin {procedura de decodificare a fisierului arhiva} p := A; {indica pozitia curenta in arborele Huffman} reset(farh); assign(fdezarh, 'h.out'); rewrite(fdezarh); while not eof(farh) do begin read(farh, c); decodifica(c, eof(farh)); end; close(farh); close(fdezarh); end; begin{program principal} clrscr; calcul_frecvente; { constructia arborelui Huffman} initializare; for i := 1 to n-1 do begin x := extrage_min; y := extrage_min; {unifica arborii x si y} new(z); z^.f : =x^.f+y^.f; z^.t := nil; z^.st := x; x^.t := z; z^.dr := y; y^.t := z; insert(z); end; A := h[1]; Codificare; Decodificare; end.

228

229

230