Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Bits&Bytes... (3)<br />
serial<br />
<strong>COMPRESIA</strong> <strong>datelor</strong><br />
Claudiu Soroiu<br />
În cadrul acestui episod al serialului dedicat compresiilor de date vã vom<br />
prezenta metoda de compresie cu dicþionare Lempel Ziv, cea mai rapidã<br />
metodã de compresie, ºi variante ale acesteia.<br />
<strong>GInfo</strong> nr. 13/3 - martie 2003<br />
42<br />
Metoda de compresie LZ (Lempel-Ziv) a fost implementatã<br />
pentru prima datã de cãtre Abraham Lempel ºi Jacob<br />
Ziv în anul 1977 ºi aceastã implementare este denumitã<br />
LZ77. Ulterior metoda a suferit unele modificãri ºi au apãrut<br />
variantele LZ78, LZW (Lempel-Ziv-Welch - 1984),<br />
LZSS (Lempel-Ziv-Storer-Szymanski - 1986).<br />
Metoda LZ77<br />
Metoda de compresie LZ77 constã în pãstrarea ultimelor n<br />
simboluri generate de o sursã de informaþie S ºi gãsirea<br />
celei mai lungi secvenþe de lungime maximã L s<br />
(L s<br />
< n) de<br />
simboluri generate de sursa S în cadrul secvenþei de n<br />
simboluri stocate.<br />
Algoritmul de compresie<br />
Algoritmul de compresie pentru varianta LZ77 este foarte<br />
simplu. La început avem un ºir T care conþine n simboluri.<br />
Fiecare dintre cele n simboluri se iniþializeazã cu primul<br />
simbol al alfabetului unei surse de informaþie S. În continuare,<br />
se pãstreazã primele L S<br />
simboluri generate de sursa<br />
de informaþie S pe ultimele L S<br />
poziþii din ºirul T. Cele L S<br />
simboluri se transmit la ieºirea E.<br />
La pasul urmãtor avem un ºir w iniþial de lungime 0.<br />
Atâta timp cât ºirul w se regãseºte în ºirul T acesta este concatenat<br />
cu urmãtorul simbol generat de sursa de informaþie<br />
S. Vom nota prin w i<br />
ºirul obþinut din primele i simboluri ale<br />
ºirului w. Fie i lungimea lui w în momentul în care acesta<br />
nu se mai regãseºte în ºirul T; aceasta înseamnã cã ºirul w i-1<br />
este cel mai lung ºir generat de sursa S care se aflã în ºirul<br />
T începând de la poziþia p. La ieºirea E se transmite poziþia<br />
p, numãrul (i - 1) ºi simbolul c de pe ultima poziþie a ºirului<br />
w, ceea ce se explicã prin faptul cã urmãtoarele i elemente<br />
ale ºirului iniþial se obþin din i - 1 simboluri consecutive din<br />
ºirul T începând de la poziþia p ºi simbolul c. ªirul T se deplaseazã<br />
la stânga cu i poziþii, iar ultimele i poziþii vor fi<br />
umplute cu simbolurile ºirului w. Acest pas se executã pânã<br />
când sursa de informaþie S nu mai genereazã simboluri.<br />
Pentru a descrie în pseudocod algoritmii din cadrul<br />
acestui articol, vom nota cu '+' operaþia de concatenare<br />
dintre douã ºiruri de simboluri sau dintre un ºir de simboluri<br />
ºi un caracter, vom considera cã prima poziþie a unui<br />
ºir de simboluri este 0 ºi vom avea nevoie de urmãtorii<br />
subalgoritmi:<br />
• Citeºteªir(S, I) - citeºte de la sursa de informaþie S<br />
un ºir de maxim I simboluri pe care îl returneazã ca rezultat;<br />
• CiteºteSim(S) - citeºte de la sursa de informaþie S un<br />
singur simbol pe care îl returneazã ca rezultat;<br />
• CiteºteNum(S) - citeºte de la sursa de informaþie S un<br />
ºir de simboluri pe care îl converteºte la un numãr care<br />
este returnat ca rezultat;<br />
• CiteºteBit(S) - citeºte de la sursa de informaþie S un<br />
ºir de simboluri pe care îl converteºte la o valoare logicã<br />
(0 sau 1) care constituie rezultatul subalgoritmului;<br />
• Genereazã(S) - returneazã ca rezultat valoarea logicã<br />
adevãrat dacã sursa S mai genereazã simboluri ºi valoarea<br />
fals în caz contrar;<br />
• Transmite(E, I 1<br />
, ..., I n<br />
) - transmite la ieºirea E conþinutul<br />
variabilelor I 1<br />
, ..., I n<br />
;<br />
• Lungime(w) - returneazã ca rezultat lungimea ºirului de<br />
simboluri w;<br />
• Primele(s, i) - returneazã ca rezultat ºirul de simboluri<br />
obþinut din primele i simboluri ale ºirului s;<br />
• Ultimele(s, i) - returneazã ca rezultat ºirul de simboluri<br />
obþinut din ultimele i simboluri ale ºirului s;<br />
• Gãsit(s, s 1<br />
) - returneazã ca rezultat valoarea logicã<br />
adevãrat dacã ºirul s 1<br />
este subºir al ºirului s ºi valoarea<br />
fals în caz contrar;<br />
• Poziþie(s, s 1<br />
) - returneazã ca rezultat poziþia de început<br />
a primei apariþii a ºirului s 1<br />
în ºirul s dacã ºirul s 1<br />
este<br />
subºir al ºirului s ºi valoarea -1 în caz contrar.<br />
În continuare vom prezenta algoritmul de compresie<br />
în pseudocod:
se iniþializeazã n cu o constantã<br />
se iniþializeazã L S<br />
cu o constantã<br />
se iniþializeazã T<br />
w ← Citeºteªir(S, L S<br />
)<br />
Transmite(E, w)<br />
T ← Primele(T, n - L S<br />
)+w<br />
dacã nu Genereazã(S) atunci<br />
ieºire<br />
sfârºit dacã<br />
cât timp Genereazã(S) executã<br />
w ← ""<br />
cât timp Genereazã(S) ºi Gãsit(T, w) executã<br />
c ← CiteºteSim(S)<br />
w ← w+c<br />
sfârºit cât timp<br />
i ← Lungime(w)<br />
p ← Poziþie(T, Primele(w, i - 1))<br />
Transmite(E, p, i - 1, c)<br />
T ← Ultimele(T, n - i) + w<br />
sfârºit cât timp<br />
În figura 1 este ilustrat modul în care se realizeazã<br />
compresia unui ºir de simboluri folosind metoda LZ77. Se<br />
poate observa foarte uºor felul în care este actualizat ºirul<br />
de simboluri T ºi cum variazã ºirul w la fiecare pas. Pasul 0<br />
reprezintã pasul de dinaintea instrucþiunii repetitive cât<br />
timp.<br />
Figura 1: Exemplu de compresie cu LZ77<br />
Algoritmul de decompresie<br />
Algoritmul de decompresie pentru metoda LZ77 constã în<br />
interpretarea ºirului comprimat de simboluri generate de o<br />
sursã de informaþie S.<br />
Iniþial se citesc L S<br />
simboluri care se transmit ºi sunt<br />
stocate ºi pe ultimele L S<br />
poziþii ale ºirului T de lungime n.<br />
În continuare, la fiecare pas se citeºte un triplet (p, i, c),<br />
se transmit i simboluri din T începând cu poziþia p, se<br />
transmite simbolul c, T se deplasezã la stânga cu i + 1 poziþii<br />
ºi ultimele i + 1 poziþii din T se completeazã cu ºirul<br />
obþinut din cele i + 1 simboluri transmise.<br />
În pseudocod acest algoritm este:<br />
se iniþializeazã n cu o constantã<br />
se iniþializeazã L S<br />
cu o constantã<br />
se iniþializeazã T<br />
w ← Citeºteªir(S, L S<br />
)<br />
Transmite(E, w)<br />
T ← Primele(T, n - L S<br />
)+w<br />
dacã nu Genereazã(S) atunci<br />
ieºire<br />
sfârºit dacã<br />
cât timp Genereazã(S) executã<br />
p ← CiteºteNum(S)<br />
i ← CiteºteNum(S)<br />
c ← CiteºteSim(S)<br />
w ← Primele(Ultimele(T, n - p), i) + c<br />
Transmite(E, w)<br />
T ← Ultimele(T, n - i - 1) + w<br />
sfârºit cât timp<br />
Observaþii<br />
Complexitatea algoritmului de compresie este O(n · nr),<br />
unde nr este numãrul de simboluri generate de sursa de informaþie<br />
S, deoarece funcþia de cãutare a unui subºir întrun<br />
ºir are complexitatea O(n) ºi modificarea ºirului T se<br />
realizeazã în maxim O(n) paºi, în funcþie de algoritmul<br />
utilizat (dacã se utilizeazã un ºir circular, modificarea poate<br />
avea loc în O(1) paºi).<br />
Complexitatea algoritmului de decompresie este O(nr)<br />
în cazul în care modificarea ºirului T se face în O(1) paºi ºi<br />
construirea lui w se face la fiecare pas folosind O(i) instrucþiuni.<br />
În general ºirul T de simboluri poartã numele de fereastrã<br />
mobilã, acest algoritm fiind cel mai simplu algoritm<br />
de compresie din clasa algoritmilor de compresie cu<br />
ferestre mobile. Dicþionarul în cazul algoritmului LZ77<br />
este constituit chiar de fereastra mobilã T.<br />
Din cauzã cã ºirul T este format numai din ultimele n<br />
simboluri generate de sursa de informaþie S algoritmul<br />
LZ77 nu este optim, deoarece nu mai apar referinþe la codificãri<br />
anterioare deci, dacã mai apare un ºir de simboluri<br />
care trebuie codificate identic cu un ºir codificat anterior,<br />
se întâmplã destul de des sã nu fie codificat în aceeaºi manierã<br />
(folosind un singur triplet (p, i, c)) fapt care duce la<br />
creºterea lungimii codificãrii întregului ºir de simboluri<br />
generate de sursa S. Din acest motiv, cercetãtorii Abraham<br />
Lempel ºi Jacob Ziv au renunþat la fereastra mobilã ºi în<br />
anul 1978 au realizat o nouã implementare în care dicþionarul<br />
de compresie era constituit de ºirurile de simboluri<br />
codificate anterior.<br />
LZ77 este un algoritm fundamental de compresie a<br />
<strong>datelor</strong> ºi stã la baza clasei de algoritmi de compresie cu<br />
fereastrã mobilã, în timp ce LZ78 este baza unei noi clase<br />
de algoritmi de compresie.<br />
Metoda LZSS<br />
În cazul metodei LZ77, se observã foarte uºor cã dacã ultimul<br />
ºir de simboluri w i-1<br />
care se regãseºte în ºirul T este<br />
43<br />
serial<br />
<strong>GInfo</strong> nr. 13/3 - martie 2003
serial<br />
<strong>GInfo</strong> nr. 13/3 - martie 2003<br />
44<br />
chiar ºirul vid atunci, pentru a reprezenta ºirul w care se<br />
codificã se transmit numerele p ºi (i - 1) care au valoarea 0<br />
ºi simbolul din ºirul w. Aceasta înseamnã cã pentru a codifica<br />
un singur simbol trebuie douã numere care sunt inutile.<br />
Metoda LZSS constituie o îmbunãtãþire asupra metodei<br />
LZ77. Îmbunãtãþirea este aceea cã în cazul enunþat<br />
anterior, dacã avem de codificat un singur simbol, se transmite<br />
bitul 0 urmat de simbol, iar dacã avem de codificat un<br />
ºir mai lung de simboluri, se transmite bitul 1 urmat de<br />
perechea (p, i). În acest ultim caz, la iteraþia urmãtoare<br />
ºirul w va fi iniþializat cu ºirul format din ultimul simbol<br />
generat de sursa S.<br />
În concluzie, dacã lungimea codificãrii unui ºir este<br />
mai mare decât ºirul necodificat, atunci se va transmite la<br />
ieºire necodificat, precedat de un bit setat pe 0. Se observã<br />
cã în cazul acestui algoritm, dacã este nevoie sã se codifice<br />
un ºir, în fereastra mobilã se va introduce doar cel mai<br />
lung subºir care se regãseºte în aceasta ºi nu primul subºir<br />
care nu se regãseºte (cum se întâmplã în cazul algoritmului<br />
de compresie LZ77).<br />
Algoritmul de compresie LZSS este:<br />
se iniþializeazã n cu o constantã<br />
se iniþializeazã L S<br />
cu o constantã<br />
se iniþializeazã T<br />
w ← Citeºteªir(S, L S<br />
)<br />
Transmite(E, w)<br />
T ← Primele(T, n - L S<br />
)+w<br />
dacã nu Genereazã(S) atunci<br />
ieºire<br />
sfârºit dacã<br />
w ← ""<br />
cât timp Genereazã(S) executã<br />
cât timp Genereazã(S) ºi Gãsit(T, w) executã<br />
c ← CiteºteSim(S)<br />
w ← w+c<br />
sfârºit cât timp<br />
i ← Lungime(w)<br />
dacã i=1atunci<br />
Transmite(E, 0, c)<br />
w' ← ""<br />
altfel<br />
p ← Poziþie(T, Primele(w, i))<br />
dacã p=-1atunci<br />
i ← i-1<br />
p ← Poziþie(T, Primele(w, i))<br />
w' ← c<br />
dacã i=1atunci<br />
Transmite(E, 0, Primele(w, 1))<br />
altfel<br />
Transmite(E, 1, p, i )<br />
sfârºit dacã<br />
altfel<br />
w' ← ""<br />
Transmite(E, 1, p, i )<br />
sfârºit dacã<br />
sfârºit dacã<br />
T ← Ultimele(T, n - i) + Primele(w, i)<br />
w ← w'<br />
sfârºit cât timp<br />
În figura urmãtoare se poate observa modul de compresie<br />
a unui ºir de simboluri generate de o sursã S, folosind<br />
algoritmul LZSS:<br />
Figura 2: Exemplu de compresie cu LZSS<br />
Decompresia unui ºir de simboluri comprimat cu metoda<br />
LZSS se realizeazã la fel ca în cazul algoritmului<br />
LZ77 cu precizarea cã în momentul decodificãrii unei perechi<br />
se verificã bitul care o precede, ºi anume, dacã bitul<br />
este 0, atunci, la ieºire se va transmite un simbol c, iar dacã<br />
este 1, atunci, la ieºire se va transmite subºirul din fereastra<br />
mobilã care începe de la o poziþie p ºi are lungimea i. Acþiunea<br />
de decodificare va fi urmatã de actualizarea ferestrei<br />
mobile.<br />
În continuare prezentãm varianta pseudocod a algoritmului<br />
de decompresie LZSS:<br />
se iniþializeazã n cu o constantã<br />
se iniþializeazã L S<br />
cu o constantã<br />
se iniþializeazã T<br />
w ← Citeºteªir(S, L S<br />
)<br />
Transmite(E, w)<br />
T ← Primele(T, n - L S<br />
)+w<br />
dacã nu Genereazã(S) atunci<br />
ieºire<br />
sfârºit dacã<br />
cât timp Genereazã(S) executã<br />
b ← CiteºteBit(S)<br />
dacã b=0atunci<br />
c ← CiteºteSim(S)<br />
w ← c<br />
i ← 1<br />
altfel
p ← CiteºteNum(S)<br />
i ← CiteºteNum(S)<br />
w ← Primele(Ultimele(T, n - p), i)<br />
sfârºit dacã<br />
Transmite(E, w)<br />
T ← Ultimele(T, n - i) + w<br />
sfârºit cât timp<br />
memorie) nu se mai adaugã elemente în aceasta, acest fapt<br />
ducând la ineficienþa algorimtului pentru date foarte multe<br />
ºi pentru multe apariþii ale unor ºiruri de simboluri care nu<br />
se aflã printre elementele listei.<br />
În figura 3 se poate observa modul în care se realizeazã<br />
compresia <strong>datelor</strong> folosind algorimtul prezentat anterior.<br />
Ordinele de complexitate ale algoritmilor de compresie<br />
ºi decompresie pentru metoda LZSS sunt aceleaºi cu<br />
cele ale algoritmilor de compresie, respectiv decompresie,<br />
pentru algoritmul LZ77, în schimb, un ºir de simboluri generat<br />
de o sursã de informaþie S este comprimat mai bine<br />
dacã se foloseºte algoritmul LZSS.<br />
Metoda LZ78<br />
Spre deosebire de cele douã metode de compresie a <strong>datelor</strong><br />
prezentate anterior, metoda LZ78 nu foloºte o fereastrã<br />
mobilã, ci reþine toate ºirurile de simboluri codificate anterior<br />
într-o listã.<br />
La începutul compresiei, mãrimea listei de elemente T<br />
codificate anterior este 1 ºi conþine pe poziþia 0 ºirul vid.<br />
La un pas, se considerã lista elementelor obþinutã la pasul<br />
anterior ºi se citeºte de la o sursã de informaþie S cel mai<br />
scurt ºir de simboluri w care nu se aflã în lista elementelor<br />
T. În aceastã listã se va adãuga ºirul w, iar la ieºirea E se va<br />
transmite o pereche formatã din numãrul de ordine al primei<br />
apariþii a ºirului obþinut din ºirul w prin eliminarea<br />
ultimului simbol din acesta.<br />
Pentru a putea prezenta algoritmul de compresie LZ78<br />
avem nevoie de subalgoritmul Cautã(s, w), care primeºte<br />
ca parametri o listã de ºiruri de simboluri s ºi un ºir de<br />
simboluri w, returnând ca rezultat numãrul de ordine al primei<br />
apariþii a ºirului w în lista s. Dacã w nu apare printre elementele<br />
listei s, atunci rezultatul subalgoritmului va fi -1.<br />
Vom considera cã numãrul de ordine al primului ºir care<br />
apare în lista s este 0.<br />
T[0] ← w //iniþial, lista T conþine doar ºirul vid<br />
k ← 1<br />
//k reprezintã mãrimea listei T<br />
cât timp Genereazã(S) executã<br />
w ← ""<br />
cât timp Genereazã(S) ºi Cautã(T, w) ≥ 0 executã<br />
c ← CiteºteSim(S)<br />
w ← w+c<br />
sfârºit cât timp<br />
T[k] ← w<br />
k ← k+1<br />
i ← Lungime(w)<br />
w ← Primele(w, i - 1)<br />
i ← Cautã(T, w)<br />
Transmite(E, i, c)<br />
sfârºit cât timp<br />
În practicã, atunci când dimensiunea listei T ajunge la<br />
o valoare prestabilitã (datoritã insuficienþei resurselor de<br />
Figura 3: Exemplu de compresie cu LZ78<br />
Algoritmul de decompresie devine acum foarte simplu<br />
deoarece, la fiecare pas, se interpreteazã o pereche (i, T)<br />
care se citeºte de la sursa de informaþii ºi se transmite ºirul<br />
care se aflã pe poziþia i în lista T urmat de caracterul din<br />
pereche.<br />
Iniþial, ca ºi în cazul compresiei, lista T conþine doar<br />
ºirul vid. La fiecare pas se adaugã în lista T ºirul format din<br />
elementul de pe poziþia i din T ºi din caracterul care a fost<br />
citit.<br />
În pseudocod, algoritmul de decompresie LZ78 este:<br />
T[0] ← w //iniþial, lista T conþine doar ºirul vid<br />
k ← 1<br />
//k reprezintã mãrimea listei T<br />
cât timp Genereazã(S) executã<br />
i ← CiteºteNum(S)<br />
c ← CiteºteSim(S)<br />
Transmite(E, T[i], c)<br />
T[k] ← T[i] + c<br />
k ← k+1<br />
sfârºit cât timp<br />
Complexitatea algoritmului de compresie are ordinul<br />
O(m · n), unde m reprezintã media lungimii ºirurilor de<br />
simboluri care se aflã în lista T, în cazul în care se foloseºte<br />
un algoritm banal de cãutare, iar n reprezintã numãrul<br />
total de simboluri generate de o sursã de informaþie S. În<br />
cazul în care se foloºte un algoritm performant de cãutare<br />
ºi lista T este reprezentatã folosind arbori de sufixe, atunci<br />
aceastã complexitate scade foarte mult.<br />
45<br />
serial<br />
<strong>GInfo</strong> nr. 13/3 - martie 2003
Algoritmul de decompresie are ordinul de complexitate<br />
O(n), unde n reprezintã numãrul de simboluri care trebuie<br />
decodificate.<br />
algoritmul LZ78 care, pentru a comprima acelaºi ºir de<br />
simboluri, transmite 11 coduri ºi 11 simboluri.<br />
serial<br />
<strong>GInfo</strong> nr. 13/3 - martie 2003<br />
Metoda LZW<br />
Welch a observat o deficienþã a algoritmului LZ78, ajungând<br />
la concluzia cã pentru a decodifica un ºir de simboluri<br />
nu este nevoie de o pereche formatã dintr-un indice ºi<br />
un simbol.<br />
Prin urmare, acesta a modificat algoritmii de compresie<br />
ºi decompresie astfel:<br />
• lista T era iniþializatã cu ºiruri de simboluri, fiecare ºir de<br />
simboluri fiind format din câte un simbol al alfabetului<br />
sursei de informaþie S. În lista T sunt atâtea ºiruri câte<br />
simboluri are alfabetul;<br />
• la fiecare pas din iteraþie:<br />
♦ ºirul w este iniþializat cu ultimul simbol al ºirul w obþinut<br />
la pasul anterior;<br />
♦ în lista T se adaugã cel mai scurt ºir w, obþinut prin<br />
concatenãri succesive cu simboluri generate de sursa<br />
S, care nu se alfã în T;<br />
♦ la ieºire se transmite un singur cod care reprezintã numãrul<br />
de ordine al apariþiei lui w din care se extrage<br />
ultimul simbol.<br />
Acest algoritm este cunoscut sub denumirea LZW.<br />
În continuare prezentãm în pseudocod algoritmul care<br />
realizeazã compresia unui ºir de simboluri generate de o<br />
sursã de informaþie S pentru metoda LZW. În figura 4 se<br />
poate observa modul în care se realizeazã compresia unui<br />
ºir de simboluri.<br />
se iniþializeazã T cu cele m simboluri ale alfabetului sursei S<br />
k ← m<br />
//k reprezintã mãrimea listei, T<br />
w ← ""<br />
cât timp Genereazã(S) executã<br />
cât timp Genereazã(S) ºi Cautã(T, w) ≥ 0 executã<br />
c ← CiteºteSim(S)<br />
w ← w+c<br />
sfârºit cât timp<br />
i ← Cautã(T, w)<br />
dacã i ≥ 0 atunci<br />
Transmite(E, i)<br />
altfel<br />
T[k] ← w<br />
k ← k+1<br />
i ← Lungime(w)<br />
w ← Primele(w, i - 1)<br />
i ← Cautã(T, w)<br />
Transmite(E, i)<br />
w ← c<br />
sfârºit dacã<br />
sfârºit cât timp<br />
//sursa S nu mai genereazã simboluri,<br />
sfârºitul compresiei<br />
Figura 4: Exemplu de compresie cu LZW<br />
Algoritmul de decompresie este asemãnãtor cu cel de<br />
la metoda LZ78 cu deosebirea cã în lista T se adaugã ºirul<br />
format din ºirul decodificat la pasul anterior ºi primul<br />
simbol al ºirului decodificat la pasul curent.<br />
În pseudocod, acest algoritm este:<br />
se iniþializeazã T cu cele m simboluri ale alfabetului sursei S<br />
k ← m<br />
//k reprezintã mãrimea listei, T<br />
i ← CiteºteNum(S)<br />
w ← T[i]<br />
Transmite(E, w)<br />
cât timp Genereazã(S) executã<br />
i ← CiteºteNum(S)<br />
T[k] ← w + Primele(T[i], 1)<br />
w = T[i]<br />
Transmite(E, T[i])<br />
k ← k+1<br />
sfârºit cât timp<br />
Ordinele de complexitate ale algoritmilor de compresie<br />
ºi decompresie ale metodei LZW sunt aceleaºi ca ºi în<br />
cazul metodei LZ78.<br />
Algoritmii LZ78 ºi LZW, datoritã rapiditãþii, sunt utilizaþi<br />
des în programele comerciale de compresie singurul<br />
inconvenient fiind acela cã mãrimea listei T trebuie limitatã<br />
datoritã cantitãþii mici de resurse disponibile pe sistemele<br />
de calcul.<br />
46<br />
Pe exemplul din figura 4 se poate observa cã pentru a<br />
comprima ºirul se transmit 14 coduri, spre deosebire de<br />
Claudiu Soroiu este redactor al <strong>GInfo</strong> ºi poate fi contactat prin e-mail la<br />
adresa csoroiu@yahoo.com.