miskolci egyetem informatikai intézet Általános informatikai tanszé k pance miklós
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 PresentationTRANSCRIPT
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