miskolci egyetem informatikai intézet Általános informatikai tanszé k pance miklós

24
Miskolci Egyetem Informatikai Intézet Általános Informatikai Tanszék Pance Miklós Adatstruktúrák, algoritmusok előadásvázlat Miskolc, 2004 Technikai közreműködő: Imre Mihály, műszaki informatikus hallgató

Upload: cairo-woods

Post on 01-Jan-2016

22 views

Category:

Documents


0 download

DESCRIPTION

Miskolci Egyetem Informatikai Intézet Általános Informatikai Tanszé k Pance Miklós Adatstruktúrák, algoritmusok előadásvázlat Miskolc, 2004 Technikai közreműködő: Imre Mihály, műszaki informatikus hallgató. Tömörítés: LZ 77. LZ 77 Sliding Window compression : - PowerPoint PPT Presentation

TRANSCRIPT

Miskolci EgyetemInformatikai Intézet

Általános Informatikai Tanszék

Pance Miklós

Adatstruktúrák, algoritmusok

előadásvázlat

Miskolc, 2004

Technikai közreműködő: Imre Mihály, műszaki informatikus hallgató

Tömörítés: LZ 77

LZ 77 Sliding Window compression:

Alapja: Jacob Ziv, Abraham Lampel: „A Universal Algorithm for Sequental Data Compression” IEEE Transactions on Information Theory.

Az LZ 77 tömörítő szótárként az előzőleg látott szöveget használja. Az input szöveg kifejezéseit a szótárra mutató pointerekkel helyettesíti. A tömörítés foka függ a szótár kifejezések hosszától, az előzőleg látott szövegre nyíló ablak nagyságától, és a forrás szövegnek a modellre vonatkozó entrópiájától.

A szöveg ablak két részre osztott. Az első a jelenleg dekódolt szöveg nagy blokkja, a második általában sokkal kisebb előrenéző buffer.

Az előrenéző bufferben az inputszövegáramból olvasott karakterek vannak, amit még nem kódoltunk be.

2

Tömörítés: LZ 77

A szöveg ablak szokásos mérete általában néhány ezer karakter. Az előrenéző buffer általában sokkal kisebb, tíz – száz karakter.

3

for(i = 0; i <MAX–1; i++) \r for(j =i+1;j <MAX ;j++) \r

szöveg ablak előrenéző puffer

A szöveg ablak 64 karakter, ebből 16-ot használ az előrenéző puffer. Az LZ 77 eredetileg token sorozatot ad ki, melyek mindegyike három adatot tartalmaz az aktuális előrenéző puffer, változó hosszúságú kifejezésére:

•mutató egy szöveg ablakbeli kifejezésre,•a kifejezés hossza,•a kifejezést követő első karakter az előrenéző pufferben.

A példában az előrenéző puffer tartalma: ’< MAX ; j++)\r’ . A puffert kutatva, megtaláljuk ’<MAX’ kifejezést a szöveg ablak 14. pozícióján és 4 karakter egyezik meg. Az előrenéző puffer első nem található karaktere a <space> . Így a token: 14, 4, <space>

Tömörítés: LZ 77

Ezután a tömörítő program a szöveg ablakot 5 karakterrel eltolja, ami az éppen elkódolt (encode) kifejezés szélessége. Ezután 5 új jelet olvas az előrenéző pufferbe és az eljárás ismétlődik.

4

Ezután a ’;j+’ kifejezést kódolja be : 40, 2, ’+’

Ha nincs megfelelés, akkor 0 hosszúságú kifejezést ad ki, pl.: 0, 0, ’*’ . Ez nem hatékony, de így bármilyen szöveg bekódolható.

Egy durva implementáció (brute force): megkeresi a leghosszabb egyezést, bekódol, eltol.

= 0; i <MAX –1; i++) \r for(j =i+1;j<MAX ;j++) \r a[i]

szöveg ablak előrenéző puffer

Tömörítés: LZ 77

Betömörítés:

int window_cmp(char *w, int i, int j, int length){int count = 0;while(length--){

if(w[i++] == w[j++])count++;

elsereturn(count);

}return(count);

}

5

Tömörítés: LZ 77

match_poz = 0;match_len = 0;for(i = 0; i < ( Winsize - elonezsize ); i++){

len = window_cmp( win, i, elonez, elonezsize);if(len > match_len){match_poz = i;match_len = len;

}}encode(match_poz, match_len, win[ elonez + match_len]);memmove(win, win + match_len +1, Winsize - match_len);for(i = 0; i < match_len + 1; i++)

win [ Winsize - match_len + i] = getc(input);

6

Tömörítés: LZ 77

Kitömörítés (decompression):

Nincs összehasonlítás. Beolvassa a tokent, kiírja a kifejezést, kiírja a követő karaktert, eltol, ismétel végig.

decode(&match_poz, &match_len; &charac);

fwrite(win+ match_poz, 1, match_len, output);

putc(charac, output);

for(i = 0; i < match_len; i++)

win[elonez + i] = win[match_poz + i];

win[elonez + i] = charac;

memmove(win, win + match_len + 1, Winsize - match_len);

7

Tömörítés: LZ 77

Ennek a kitömörítő eljárásnak egy érdekes mellékhatása, hogy használhat olyan kifejezést is egy létező kifejezés bekódolására, amit még nem enkódolt. Pl. egy fájl, ami 100 A betűt tartalmaz egymásután:

Az első A enkódja: (0,0,’A’)

8

Ezután a következő 9 A betű kódolható így is: (38,9,’A’) . Bár mi láthatjuk a kifejezést az előrenéző pufferben (az A karaktereket), de a dekóder erre nem képes. Amikor a dekóder megkapja a (38,9,’A’) tokent, akkor a puffere:

- - - - - - - - - - - - - - - - - - - A A A A A A A A A A A

- - - - - - - - - - - - - - - - - - - A

match_poz elonez_puff

Tömörítés: LZ 77

De a decompress algoritmus ezt meg tudja oldani: a ciklusban a match_poz –ból másol az elonez pufferbe:

9

A A

match_poz +i elonez_puff + i

végül

Ez az LZ 77 tömörítés gyors alkalmazkodását bizonyítja. Bekódolt 10 karakteres sorozatot, amikor a szótárában még csak egyetlen karakter volt belőle.

A A A A A A A A A A A

match_poz +i elonez_puff + i

Tömörítés: LZ 77

Problémák az LZ 77-tel

A fenti implementáció az algoritmusnak egy laza interpretációja. Nyilvánvaló a teljesítmény szűk keresztmetszete (bottleneck), a string összehasonlítás: a szöveg ablak minden pozícióján összevet az előrenéző pufferrel. Ez még csak romlik, ha a teljesítmény (tömörítés foka) fokozására növeljük az ablak méretét, azaz a szótár méretét. A dekódolást ez nem befolyásolja.

A másik probléma a csúszó ablak kezelésének módja, kényelmességből itt a csúszó ablakot úgy kezeltük mintha ez valóban végig csúszna a szövegen. Helyette a kezdő és vég pointereket csúsztatjuk a puffer (a teljes szöveg) mentén.

10

Tömörítés: LZ 77

De ekkor a modulo indexet kell használnunk:int window_cmp(char* w, int i, int j, int len){

count = 0;while(len--){

if (w[i] == w[j])count++;

elsereturn(count);i = ++i % winsize;j = ++j % winsize;

}return(count);

}

11

Tömörítés: LZ 77

Egy enkód probléma

Ha nem talál egyező kifejezést, akkor az egyetlen karakter bekódolására is a három komponensű tokent használja. Pl. használjunk egy 4096 bájtos ablakot, 16 bájtos előrenéző pufferrel. Ehhez 20 bit az ablak pozíció, 4 bit a kifejezés hossz = 24, egyetlen 8 bites jel bekódolására.

12

Tömörítés: LZ SS

1. változtatás: a kifejezés tárolása

Az LZ 77-ben a kifejezések folytonos szövegblokként tárolódnak, minden szervezettség nélkül. Az LZ SS bináris kereső fa szerkezetet használ a kifejezések tárolására. Így a leghosszabb megegyező kifejezés megtalálása a korábbi winsize* kifejezésméret helyett annak logaritmusával arányos. Ez bátoríthat a nagyobb ablakokkal való kísérletezésre. Pl. az ablak megduplázása az összehasonlítási időt csak 1 egységgel növeli, míg az LZ 77-nél ez duplája.

2. változtatás: a token kialakítása

LZ 77 : a token 3 részből áll

LZ SS megengedi a pointerek és karakterek szabad keveredését. A beindulásnál csupa ismeretlen kifejezés jön ...

Az LZ SS a tokenek elé egybites jelzőt tesz az offset/hossz páros illetve az egyetlen karakter jelzésére az outputban.

Ennél kisebb gondot okoz, hogy a követő karaktert is kiírja. 13

Tömörítés: LZ

Az alkalmazott adatszerkezetek:

1. unsigned char win[winsize];nem az ablak csúszik, hanem a pointerek, ekkor az (i+1) mod winsize művelet hatékonyabban végezhető, ha a winsize 2 hatványa

2. a kifejezések tárolására bináris kereső fát használunk:

struct{

int parent;

int smaller_child;

int larger_child;

} tree[winsize + 1];

14

Tömörítés: LZ

A tree[Winsize] elem a fa gyökerét jelöli ki, ehhez nem tartozik kifejezés, nincs kisebb, nagyobb gyereke, a nagyobb gyerek indexe magára a fa gyökerére mutat. Ez csökkenti a feldolgozási időt és egyszerűsíti a kódot.

Pl. törlésnél ilyen kódrészlet:

tree[tree[i].parent].child = tree[i].child

mivel a gyökérre mutató pointert ugyanabban a fában tároljuk, ezért nem kell külön ellenőrzés, arra, hogy az a gyökér-e. Mégha i a gyökér csomópont, akkor is a tree[i].parent még érvényes csomópontra mutat a fában.

Egy további szokatlan jellemző, hogy az LZ SS egy speciális kódot használ a tömörített adat vége elérésének jelzésére. Ebben az esetben a zérus ablak index az adatáram végét jelzi. Így ez nem használható érvényes kifejezésként.

15

Tömörítés: LZ

A 0 kifejezést nem használjuk, így a 0 csomópontot speciális UNUSED indexként használva kódot takaríthatunk meg. Pl. a törlés kódrészleténél: 

if (tree[i].smaller_child != UNUSED)

tree[tree[i].smaller_child].parent = tree[i].parent;

if (tree[i].larger_child != UNUSED)

tree[tree[i].larger_child].parent = tree[i].parent;

De ha az UNUSED index egy megengedett tárolóterületre mutat, akkor az érvényességi vizsgálat elhagyható.

tree[tree[i].smaller_child].parent = tree[i].parent;

tree[tree[i].larger_child].parent = tree[i].parent;

Mivel a tree[0] értéket sohasem használjuk navigálásra, hibát nem okoz, és jelentős CPU időt takarít meg.

16

Tömörítés: LZ

Kiegyensúlyozás

A kereső fa könnyen láncolt listává alakulhat, mivel a fájlokban gyakran előfordulnak csökkenő vagy növekvő kifejezések. Ezek gyakran megesnek, de a csúszó ablak természete folytán gyorsan ki is mennek a fából. Ezért fa kiegyensúlyozást általában nem építenek be.

„Greedy” vagy a „lehető legjobb”

Az LZ 77 és az LZ SS is greedy algoritmus, mivel nem néznek előre az input áramba, hogy azt analizálják az indexek és karakterek legjobb kombinációja érdekében. A gyakorlatban néhány % megtakarítás mutatkozik, a feldolgozási idő pedig jelentősen nő. Néhány jó heurisztikát szoktak mindössze használni és ez a greedy algoritmus határozottan jó.

17

Tömörítés: LZ

Javítások:

• Előre feltölteni az ablakot Winsize-elonezsize karakterrel és utána adjuk a megfelelő stringeket a bináris fához. De mivel töltsük fel előre?

• Lehet kísérletezni az index és a kódhossz bitjeinek növelésével.

• „ghost buffer” a szöveg ablak végére, ami az ablak első 17 karakterét tartalmazza (16 a mérete az elorenez ablaknak) így a modulo aritmetika kihagyható, de ezt karban kell tartani.

• Blokkolt I/O.

• String duplikátumok kezelése:a fába nem tesszük, de az ablakban benne van.

18

Tömörítés: LZ 78 • jelsorozat, szótárat használ,

• ez a szótár a tömörítés végéig él (nem kerülnek ki belőle elemek), fokozatosan bővül (tanul) az új jelsorozatokkal,

• ha megtelik nem vesz fel újat,

• induláshoz a szótárnak 1 eleme van: üres string.

A szótár felépítése: jelsorozat, kód.

19

Tömörítés: LZ 78

A tömörítés elve:

• adott pozíción vagyunk

• megkeressük a szövegben azt a leghosszabb jelsorozatot, ami már benne van a szótárban (kezdetben csak rövidebbek, később hosszabbak),

• a tömörített állományba a kódot írjuk ki,

• megtalált rész + az őt követő karakter együtt mint egy új jelsorozat bekerül a szótárba új kódértékkel,

• a tömörített fájlba kiírja a követő karaktert is, így nem kell a szótárat is hozzáírni, hanem az felépíthető a dekódolás során.

20

Tömörítés: LZ 78

Példa:

WAWATOSOWA

Tömörítve: 0W | 0A | 1A | 0T | 0O | 0S | 5W | 2-

Vissza:

WAWATOSOWA

21

kód 0 1 2 3 4 5 6 7

jelsor " W A WA T O S OW

kód 0 1 2 3 4 5 6 7

jelsor " W A WA T O S OW

Tömörítés: LZW

LZW (1984 Terry Welsh)

 Az LZ 78 javított változata, induláskor az összes jellel feltöltjük a szótárt.

A kódolás menete:

• megkeressük a szótárban is meglévő leghosszabb részt és kiírjuk a kódját,

• ez a rész is a követő karakter új jelsorozatként kerül a szótárba,

• a követő karakteren indulva indulva folytatjuk a vizsgálatot.

22

Tömörítés: LZW

WAWATOSO

W | A | 256 | T | O | S | O

WAWATOSO

WWWWWW

W | 256 | 257

23

0 255 256 257 258 259 260 261

"   WA AW WAT TO OS SO

    256        

    WA        

        256 257

        WW WWW

Tömörítés

Forrás:

Mark Nelson: The Data Compression Book M&T, 1991. ISBN 1-55851-216-0

24