6.

Arbori de compresie Huffman
În analiza algoritmilor pe care i-am studiat până acum prioritară a fost complexitatea timp. În acest capitol ne punem problema elaborării unor algoritmi care să micşoreze spaţiul necesar memorării unui fişier. Tehnicile de compresie sunt utile pentru fişiere text, în care anumite caractere apar cu o frecvenţă mai mare decât altele, pentru fişiere ce codifică imagini sau sunt reprezentări digitale ale sunetelor ori ale unor semnale analogice, ce pot conţine numeroase motive care se repetă. Chiar dacă astăzi capacitatea dispozitivelor de memorare a crescut foarte mult, algoritmii de compresie a fişierelor rămân foarte importanţi, datorită volumului tot mai mare de informaţii ce trebuie stocate. În plus, compresia este deosebit de utilă în comunicaţii, transmiterea informaţiilor fiind mult mai costisitoare decât prelucrarea lor. Una dintre cele mai răspândite tehnici de compresie a fişierelor text, care, în funcţie de caracteristicile fişierului ce urmează a fi comprimat, conduce la reducerea spaţiului 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 biţi (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 fişier de 100.000 de caractere din alfabetul {a,b,c,d,e,f}, pe care dorim să-l memorăm cât mai compact. Dacă am folosi un cod de lungime fixă, pentru cele 6 caractere, ar fi necesari câte 3 biţi. 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 biţi. Să presupunem acum că frecvenţele cu care apar în text cele 6 caractere sunt : a b c d e f
218

6.000 biţi (deci o reducere a spaţiului de memorie cu aproximativ 25%).. numãrul lor de apariţii.cn} mulţimea caracterelor dintr-un text. când citim în fişierul comprimat secvenţa 1001 nu putem decide dacă este vorba de caracterul a sau de o parte a codului caracterului b. Pentru a evita ambiguităţile este necesar ca nici un cod de caracter să nu fie prefix al unui cod asociat al unui caracter (un astfel de cod se numeşte cod prefix )...fn.. numit cod Huffman. Codul Huffman D. calculate în funcţie de limbă sau de specificul textului. dacă am fi codificat a cu 1001 şi b cu 100101. Ideea de a folosi separatori între codurile caracterelor pentru a nu crea ambiguităţi ar conduce la mărirea dimensiunii codificării. fără ambiguităţi. Există situaţii în care putem utiliza frecvenţele standard de apariţie a caracterelor... De exemplu. Fie C={c1. construim un arbore binar complet în manieră bottom-up 219 .frecvenþã 45 13 12 16 9 5 Considerând următorul cod de lungime variabilă : a b c d e f cod 0 101 100 111 110 110 variabil 1 0 ar fi necesari doar 224... astfel încât să fie posibilă decodificarea fişierului comprimat. respectiv.1. Pentru aceasta. Prima etapă în construcţia codului Huffman este calcularea numărului de apariţii ale fiecărui caracter în text. în funcţie de frecvenţă. iar f1. Dacă li ar fi lungimea şirului ce codifică simbolul ci. Huffman a elaborat un algoritm Greedy care construieşte un cod prefix optimal.f2.c2. atunci lungimea totală a reprezentării ar fi : L = ∑ li ⋅ fi i =1 n Scopul nostru este de a construi un cod prefix care să minimizeze această expresie. Problema se reduce deci la a asocia fiecărui caracter un cod binar.

4.f2}. La fiecare pas unificăm 2 arbori ale căror rădăcini au frecvenţele cele mai mici. {cn. al cărui subarbore stâng este A 1.c.f} cu frecvenţele {45. 220 . 1.12.9.b. iar frecvenţa rădăcinii lui A este suma frecvenţelor rădăcinilor celor doi arbori. 2.16.d.e. cu rădăcina ce are frecvenţa 14: Fig. -Pentru a obţine arborele final..{c2. reprezentată printr-o pădure de arbori formaţi dintr-un singur nod. Pas 1: Unific arborii corespunzători deoarece au frecvenţele cele mai mici: lui e şi f.astfel : -Iniţial. se execută n-1 operaţii de unificare... Unificarea a doi arbori A1 şi A2 constă în obţinerea unui arbore A.fn}}. Pas 3: Unific arborele corespunzător lui d şi arborele obţinut la primul pas.5} se construieşte pornind de la cele cinci noduri din figura 1: Fig. 3. considerăm o partiţie a mulţimii C={ {c1. Unific arborii corespunzători lui b şi c: Fig.f1}.13. arborele Huffman asociat caracterelor {a. subarbore drept A2. . Pas 2: Fig. De exemplu.

6. Pas 5: Unificând ultimii doi arbori. Caracterele fiind frunze în arborele obţinut. nodurile interioare conţin suma frecvenţelor caracterelor corespunzătoare nodurilor terminale din subarbori. Fig. se va asocia pentru fiecare deplasare la stânga pe drumul de la rădăcină la nodul terminal corespunzător caracterului un 0. Arborele Huffman obţinut va permite asocierea unei codificări binare fiecărui caracter. 5. Fig.Pas 4: Unific arborii ce au frecvenţele 25 şi 30. Obţinem codurile : a b c d e f cod 0 100 101 110 111 111 0 1 Observaţii 221 . obţin arborele Huffman asociat acestui set de caractere cu frecvenţele specificate iniţial. iar pentru fiecare deplasare la dreapta un 1. Nodurile terminale vor conţine un caracter şi frecvenţa corespunzătoare caracterului.

care au timpul de execuţie de O(log n). Pentru extragerea eficientă a minimului vom folosi un minheap. Iniţializare : .dr := Y . Singurul nod rămas în heap este rădăcina arborelui Huffman. Pas 3.la fiecare pas am selectat cele mai mici două frecvenţe. Algoritm de construcţie a arborelui Huffman * Pas 1. Astfel timpul de execuţie pentru operaţiile de extragere minim. 222 .extrage succesiv X şi Y.unifică arborii X şi Y : .caracterele care apar cel mai frecvent sunt mai aproape de rădăcină şi astfel lungimea codificării va avea un număr mai mic de biţi.Z^.frecv := X^.crează Z un nou nod ce va fi rădăcina arborelui . Analiza complexităţii Iniţializarea heapului este liniară. Corectitudinea algoritmului Trebuie să demonstrăm că algoritmul lui Huffman produce un cod prefix optimal. parcurgând arborele Huffman.. . . în funcţie de frecvenţele de apariţie.fiecare caracter reprezintă un arbore format dintr-un singur nod.Z^. Pas 2.inserează Z în heap. * Programul Huffman realizeazã compactarea ºi decompactarea unui fiºier text utilizând arbori Huffman. Se generează codurile caracterelor.st := X . Se repetă de n-1 ori : .Z^. două elemente din heap . inserare şi ştergere va fi. Pasul 2 se repetă de n-1 ori şi presupune două operaţii de extragere a minimului dintr-un heap şi de inserare a unui element în heap. pentru a unifica arborii corespunzători.organizăm caracterele ca un min-heap.frecv .frecv+Y^. în cazul cel mai defavorabil. de O(log n). Deci complexitatea algoritmului de construcţie a arborelui Huffman este de O(n log n).

Notăm 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) ⇔ nivT '( x)= nivT (a) C (T ) − C (T ') = ( f (a) − f ( x)) ⋅ (nivT (a) − nivT ( x)) ≥ 0. care sunt fiii aceluiaşi nod. Obţinem: Deci C(T) ≥ C(T''). Observaţie Din lemă deducem că pentru construcţia arborelui optimal putem începe prin a selecta după o strategie Greedy caracterele cu cele mai mici frecvenţe. b două noduri de pe nivelul maxim. dar cum T era optimal deducem că T″este arbore optimal şi în plus x şi y sunt noduri terminale situate pe nivelul maxim. schimbând pe a cu x : Fig. pentru că nivT '(a)= nivT ( x) c∈C { C (T ') − C (T '') = ( f (b) − f ( y)) ⋅ (nivT (b) − nivT ( y)) ≥ 0. Atunci există un cod prefix optimal în care x şi y au codificări de aceeaşi lungime şi care diferă doar prin ultimul bit. Q.Lema 1. Cum x şi y au cele mai mici frecvenţe rezultă că f[x] ≤ f[a] şi f[y] ≤ f[b]. 7. Demonstraţie: Fie T arborele binar asociat unui cod prefix optimal oarecare şi fie a. fii ai aceluiaşi nod.E. construim arborele T. două caractere care au frecvenţa cea mai mică. y ∈ C. Dacă x şi y sunt 223 . În mod analog. Fie T un arbore binar complet corespunzator unui cod prefix optimal pentru alfabetul C. Lema 2. Transformăm arborele T în T'. schimbând pe x cu y.D. Putem presupune fără a restrânge generalitatea că f[a] ≤ f[b] şi f[x] ≤ f[y]. Fie x.

E. atunci arborele T' obţinut prin eliminarea nodurilor x şi y este arborele binar complet asociat unui cod prefix optimal pentru alfabetul C' = (C-{x. este necesară memorarea arborelui Huffman utilizat pentru comprimare. Demonstraþie: ∀c∈C −o x. T’ este un arbore optimal pentru C’. Arborele Huffman generat este static.două noduri. Deci. a doua oară pentru a comprima efectiv fişierul. Deci. aşa cum este şi firesc.y}) ∪ { z}. Cum z este nod terminal în T ″. 3. atârnând pe x [i y ca fii ai lui z. Observaţie Metoda de compresie bazată pe arbori Huffman statici are o serie de dezavantaje: 1. ceea ce conduce la mărirea dimensiunii codului generat. putem construi un arbore T1 pentru alfabetul C. 2. Q. yr . ceea ce contrazice ipoteza că T este arbore optimal pentru C. ale cărui frunze sunt caractere din C’. Pentru o dezarhivare ulterioară a fişierului. Deci metoda nu poate fi folosită pentru transmisii de date pentru care nu este posibilă reluarea. Corectitudinea algoritmului lui Huffman este o consecinþă imediată a celor două leme.D. Dacă presupunem prin reducere la absurd că arborele T’ nu este optimal pentru alfabetul C’ = (C{x. C(T1) = C(T″)+f(x)+f(y) < C(T). astfel încât C(T″) < C(T). Fişierul de compactat trebuie parcurs de două ori: o dată pentru a calcula numărul de apariţii ale caracterelor în text. f[z] fiind f[x]+f[y]. metoda nefiind 224 C (T ) = ∑ f (c) ⋅ nivT (c) = C (T ') + ( f ( x) + f ( y)) c∈ C .y}) ∪ {z} ⇒ ∃ T″. fii ai aceluiaşi nod z în T.nivT (c)= nivT '(c) 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)).

c.d. 2 Care este un cod Huffman pentru alfabetul. Exerciþii 1.c. comparativ cu necesarul de memorie pentru un cod de lungime fixă. C={a. arborele Huffman să se reorganizeze astfel încât caracterul respectiv să aibă eventual un cod mai scurt. Construiþi arborele Huffman corespunzător alfabetului C={a.h} şi frecvenþelor: a b c d e f g h frecve 5 1 7 5 8 1 4 1 nþa 0 5 0 0 Construiþi codul Huffman şi evaluaþi necesarul de memorie pentru un fişier cu 100000 de caractere.h. ideea fiind ca la fiecare nouă codificare.g. 225 .f.g. Demonstraþi că nici o schemă de compresie nu poate garanta reducerea spaþiului de memorie necesar unui fişier de caractere aleatoare pe 8 biþi nici măcar cu un singur bit.i} şi frecvenþele a b c d e f g h i frecve 1 1 2 3 5 8 1 2 3 nþa 3 1 4 Puteþi generaliza răspunsul pentru cazul în care frecvenþele sunt primele n numere din şirul Fibonacci? 3. Dacă presupunem că oricare două caractere din alfabet au frecvenþe distincte.e.d.b. 6.capabilă să se adapteze la variaţii locale ale frecvenţelor caracterelor.f.b. Demonstraþi că un arbore binar care nu este strict nu poate corespunde unui cod prefix optimal. 4.2. Aceste dezavantaje au fost în mare parte eliminate în metodele care folosesc arbori Huffman dinamici.e. atunci arborele Huffman este unic? 5.

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

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

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

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

Sign up to vote on this title
UsefulNot useful