Häviöttömät tiedonpakkausalgoritmit Jukka Pollari Tiivistelmä. Tässä tutkielmassa käsitellään häviöttömiä pakkausalgoritmeja, tarkemmin määriteltynä sellaisia, joilla voidaan pakata kaikenlaista dataa. Työssä tutkitaan sitä, mihin algoritmien pakkaustehot perustuvat ja selvitetään pakkausalgoritmeista niiden historia sekä algoritmien toiminta. Tutkielman tarkoituksena on antaa selkeä kuva algoritmien rakenteesta ja siitä miksi ne pakkaavat dataa sekä antaa kuva siitä, millaisten rajojen sisällä yleensä ottaen tietoa voidaan pakata. Avainsanat ja sanonnat: Tiedonpakkaus, algoritmit, Huffmanin koodaus, aritmeettinen koodaus, sanakirjat, informaatioteoria. CR luokat: E.4 1. Johdanto Tiedonpakkaus on muodostumassa yhä tärkeämmäksi osaksi yhteiskuntaa bittimuotoisen tiedon määrän kasvaessa huimaa vauhtia. Tietoa pakataan monin eri tavoin arkipäivässä, sekä tietokoneiden maailmassa että kodin sähkölaitteissa. Vaikka tiedonpakkaus ei ole kovin näkyvä osa maailmaa, on se ollut suuressa roolissa monissa teknisissä innovaatioissa, esimerkiksi internetin tiedonsiirtomekanismien suunnittelussa. Ilman tiedonpakkausta tiedonsiirtokapasiteettien täytyisi olla huomattavasti suurempia nykyisiin verrattuna. Tämän teoksen tutkimusongelmana on häviöttömien pakkausalgoritmien historian selvittäminen sekä yksittäisten algoritmien toiminta. Tutkimuksessa selvitetään mitä algoritmeja käytetään laajalti ja pohditaan syitä algoritmien yleistymiseen. Tutkimusalueen rajaamiseksi teoksessa keskitytään esittelemään vain sellaisia häviöttömiä algoritmeja, joilla voidaan pakata kaikenlaista dataa. Tutkimuksessa ei siis käsitellä sellaisia häviöttömiä pakkausalgoritmeja, joilla voidaan pakata vain esimerkiksi ääntä, kuvaa tai videokuvaa. Tutkimuksen aluetta rajataan myös niin, että siinä ei perehdytä matemaattiseen teoriaan kovin laajasti, mutta käydään tiiviisti läpi informaatioteoria ensimmäisessä kappaleessa. Lähdin ratkaisemaan tutkimusongelmaa kirjallisuuskartoituksen avulla. Halusin mahdollisimman tuoreen näkemyksen algoritmeihin, joten päätin etsiä eri tietokannoista mahdollisimman tuoreita aihetta käsitteleviä teoksia. Yksittäisiä algoritmeja ja niiden soveltamista erilaisissa tilanteissa löytyi
2 tietokannasta runsaasti, mutta pohjatietoa käsitteleviä teoksia oli niukalti. Tietokannasta löytyi kuitenkin kuitenkin Sayoodin tuore kirja Introduction to Data Compression (Third Edition) vuodelta 2006. Tämä teos oli tärkein lähde tutkimuksessa, koska siinä käsiteltiin tuoreimpia algoritmeja. Toinen tutkimuksessa käyttämäni teos on Nelson ja Gaillyn The Data Compression Book 2nd edition vuodelta 1995. Nelson ja Gaillyn kirja ei käsittele algoritmeja niin matemaattisesti kuin Sayoodin kirja, joten se on ideaalinen omaan tarkoitukseeni. Kolmas käyttämäni pääteos oli Lelewer ja Hirschbergin yleisteos Data compression vuodelta 1987. Pääteosten lisäksi tietokannoista sekä www:stä löytyi algoritmien kehittäjien omia teoksia, joissa he itse käyvät läpi algoritmien toimintaa. Tutkimuksen ensimmäisessä kappaleessa esitellään informaatioteoria ja seuraavissa esitellään siihen perustuvista algoritmeista Huffmanin staattinen ja mukautuva algoritmi sekä aritmeettinen koodaus ja sitä kautta siirrytään Lempelin ja Zivin alkujaan kehittämiin sanakirja algoritmeihin. 2. Informaatioteoria Nykyaikaisen tiedonpakkauksen juuret ovat 1940 luvun lopulla, jolloin Bell Labsin Claude Shannon kehitti informaatioteorian. Informaatioteoriassa tutkitaan redundanssia, entropiaa ja tilastollista informaatiosisältöä. Redundanssilla eli toistolla viitataan siihen tietoon, joka on käytännössä turhaa ja jonka poistamalla voidaan tietoa pakata tiiviimpään tilaan. Entropia taas on se mitta, jolla lasketaan kuinka paljon informaatiota viesti sisältää [Sayood, 2006; Nelson ja Gailly, 1995; Lelewer ja Hirschberg, 1987]. Näitä informaatioteorian matemaattisia teorioita voidaan soveltaa myös binäärimuotoiseen tietoon. Shannon kehitti teoriassaan suureen, jota hän kutsui itseinformaatioksi (self information). Määritellään A tapahtumaksi, joka on jonkin satunnaisen tapahtuman tulos. Tällöin itseinformaatiolla I(A) tarkoitetaan sitä entropista informaation määrää, minkä luku A sisältää [Sayood, 2006]. Shannon määritteli, että jos P(A) on todennäköisyys sille, että tapahtuma A tapahtuu, niin A:n sisältämä itseinformaatio I(A) voidaan laskea kaavasta 1 I( A) = logb = loga P( A). (1) P( A) Kaavassa (1) kantaluku a kertoo informaation yksikön. Se voi olla mikä tahansa luku a > 1. Tyypillisiä esimerkkejä on 2 (bittiä), e (nattia) tai 10 (dittiä). 2
3 Jos siis halutaan laskea informaation sisältöä bitteinä, sijoitetaan kantaluvuksi 2. Itseinformaatio bittityyppiselle yksikölle voidaan laskea kaavasta ln P( A) I ( A) = log2 P( A) = (2) ln 2 Määritellään esimerkiksi, että merkki a esiintyy todennäköisyydellä P(A) 1 = 4. Tällöin entropisten bittien määrä voidaan laskea kaavasta (2) seuraavasti: 1 ln 4 I(A)= ln 2 = 2 bittiä. Kirjaimen a informaatiosisältö on siis tässä tilanteessa 2 bittiä. Jokaisen merkin vaatima tila yleisellä Ascii koodauksella on 8 bittiä, joten pakkauksella olisi mahdollista säästää tässä tilanteessa 6 bittiä. Pakkausalgoritmeissa informaation sisällön entropiaa on kuitenkin käytännössä mahdotonta mitata, koska pakkausalgoritmin käyttämä malli (model) määrää merkkien todennäköisyydet. Tämä johtuu siitä, että mallin suuruusluokka (order) määrää sen montako edellistä merkkiä malli lukee [Nelson ja Gailly, 1995]. Esimerkiksi suurusluokan 0 malli lukee vain nykyisen merkin, suurusluokan 1 lukee yhden ja niin edelleen. Jos suuruusluokan 0 malli lukee esimerkiksi vain nykyisen kirjaimen, joka on a ja saa todennäköisyydeksi 1/70, suuruusluokan 1 malli voi lukea myös edellisen kirjaimen saaden näin tulokseksi esimerkiksi at jonka todennäköisyys voi olla 2/3. Todennäköisyydet vaihtuvat siis aina mallin muuttuessa. Jotta pakkaus olisi mahdollisimman tehokasta, on valittava malli joka osaa ennustaa ne symbolit, joita esiintyy usein ja tämän jälkeen pakattava nämä symbolit mahdollisimman pienillä bittimäärillä. 3. Todennäköisyyksiin perustuva staattinen mallinnus Informaatioteorian kehittämisen jälkeen tuli tarve etsiä tapoja, joilla saataisiin tietoa pakattua informaatioteorian antamien mahdollisuuksien mukaisesti pienempään tilaan. Ensimmäiset pakkausalgoritmit perustuivat staattisiin todennäköisyystaulukoihin, joita päivitettiin vain kerran algoritmin ajon aikana. Tällaisen mallin etu on, että se säästää tietokonetehoja [Nelson ja Gailly, 1995]. Yksinkertaisin tapa tällaisessa algoritmissa olisi esimerkiksi luoda taulukko, joka sisältää symbolit ja niiden todennäköisyyden. Pakkausohjelma pääsee käsiksi kyseiseen taulukkoon, jonka avulla se pakkaa ja purkaa tiedoston. Aineistoon perustuvan taulukon sijaan voidaan käyttää myös universaalia yleistä taulukkoa, jota ei ole räätälöity aineiston mukaan. On kuitenkin selvä, että tällaisen mallin ongelmana voi olla pakkaussuhde, koska 3
4 sisään tuleva tietovirta ei välttämättä vastaa taulukon symbolien esiintymistiheyksiä. Huonoimmassa tapauksessa universaalin taulukon tapauksessa pakattu tiedosto voi olla suurempi kuin alkuperäinen. Shannon Fanon koodaus oli ensimmäinen suosittu ja tehokas staattista mallia käyttävä pakkausalgoritmi. Algoritmi sai nimensä Bell Lapsin Claude Shannonin ja MIT:n R.M. Fanon mukaan, jotka kehittivät algoritmin lähes yhtäaikaisesti vuonna 1949. Algoritmia käytettiin tietokoneiden lisäksi aluksi myös muissa laitteissa, esimerkiksi kaukokirjoittimissa [Nelson ja Gailly, 1995; Lelewer ja Hirschberg, 1987]. Shannon Fanon algoritmi muodostaa järjestetyn frekvenssilistan avulla binääripuun, jossa jokaisella symbolilla on eri bittikoodi. Frekvenssilista jaetaan kahteen mahdollisimman yhtä suureen ryhmään. Tätä tehdään rekursiivisesti, kunnes kaikki frekvenssilistan osat olivat lehtisolmuja. Shannon Fano oli suuri harppaus eteenpäin pakkaustekniikoissa, mutta lyhytikäinen, sillä Huffmanin koodaus korvasi Shannon Fano pakkauksen tehokkaampana algoritmina [Nelson ja Gailly, 1995; Lelewer ja Hirschberg, 1987]. David Huffman julkaisi tutkimusraporttinsa A Method for the Construction of Minimum Redundancy Code vuonna 1952. Huffmanin algoritmi on myöhemmin kehittynyt yhdeksi maailman yleisimmistä pakkausmenetelmistä. Algoritmia käytetään useissa pakkausohjelmissa, kuten ZIP ja GZIP sekä kuvankäsittelymuodoista suositussa JPEG kuvanpakkauksessa. Sitä käytetään myös esimerkiksi monissa elektronisissa laitteissa kuten FAX laitteissa. Huffman ja Shannon Fano koodaukset ovat teholtaan melko samaa tasoa, mutta Huffman koodaus on aina vähintään yhtä tehokas kuin Shannon Fano [Nelson ja Gailly, 1995]. Tästä syystä Huffmanin koodausta käytetään Shannon Fanoa useammin. Yksi Shannon Fanon algoritmin ja Huffman algoritmin eroista on, että Huffman aloittaa datan läpikäymisen lopusta ja päätyy alkuun, kun taas Shannon Fano kulkee alusta loppuun. 3.1. Shannon Fano algoritmi Ennen Shannon Fano algoritmia luodaan kaikista symboleista lista, joka sisältää symbolin sekä sen luvun, montako kertaa symboli esiintyy aineistossa. Lista jaetaan joka kierroksella kahteen osaan niin, että molempien summa on mahdollisimman samanlainen: 1. Järjestetään frekvenssilista suuruusjärjestykseen niin, että ne symbolit joilla on suurin todennäköisyys, ovat alkupäässä ja ne joilla on pienin, ovat loppupäässä. 4
5 2. Jaetaan lista kahteen ryhmään niin, että vasemman ja oikean puoliskon yhteenlasketut todennäköisyydet ovat mahdollisimman lähellä toisiaan. 3. Lisätään kaikille vasemman puolen symboleille koodiluvuksi 0 ja oikean puolen symboleille 1. 4. Käydään rekursiivisesti läpi kaikki jaetut ryhmät jakaen ne kohdan 2 ja 3 mukaisesti ja lisäten niille koodiluvut. Tehdään tätä niin kauan, että jokainen ryhmä sisältää vain yhden symbolin. Algoritmi 1. Shannon-Fano algoritmi [Lelewer ja Hirschberg, 1987]. Kun lista on jaettu kahteen osaan, toisesta puoliskosta tulee binääripuun oikea lapsi ja toisesta vasen lapsi. Lopulta koko aineisto on jaettu yhdeksi binääripuuksi. Tämä binääripuu lisätään ensin pakattavan tiedoston alkuun purkamisen mahdollistamiseksi, jonka jälkeen pakataan aineisto bittimuotoisesti kyseisellä binääripuulla. 3.2. Huffman algoritmi Ennen Huffmanin algoritmia on luettava aineisto ja luotava frekvenssilista, joka sisältää jokaisen symbolin ja sen frekvenssin eli määrän, montako kertaa symboli esiintyy aineistossa. Frekvenssilistan pienimmät alkiot yhdistetään solmuksi, jonka juurena on summafrekvenssi. Tätä tehdään niin kauan, että lista on yksittäinen binääripuu: 1. Luodaan uusi binääripuusolmu. 2. Poistetaan kaksi listan pienintä alkiota frekvenssilistasta ja lisätään ne uuden binääripuusolmun oikeaksi ja vasemmaksi lapseksi. Vasemman lapsen bittikoodi on 0 ja oikean 1. 3. Asetetaan solmun juureksi vasemman ja oikean lapsen frekvenssien summa. 4. Lisätään binääripuusolmu listaan. 5. Tehdään niin kauan kohdasta 1 lähtien, kunnes lista sisältää vain yhden binääripuun. Algoritmi 2. Huffmanin algoritmi [Cormen, Leiserson, Rivest ja Stein, 2001; Nelson ja Gailly, 1995]. Kuten Shannon Fano algoritmissakin (1), Huffmanin algoritmin (2) suorittamisen jälkeen pakatun tiedoston alkuun lisätään ensiksi pakattu 5
6 Huffman puu ja sen jälkeen aineisto koodataan bittimuotoisesti binääripuun avulla. 3.3. Staattisten mallien analysointia Huffman koodauksen avulla saavutetaan minimimäärä redundanssia koodeilla, joiden bittikoko vaihtelee symbolien esiintymismäärän mukaan. Huffmanin koodaus ei ole optimaalinen, mutta se tarjoaa hyvän likiarvollisen tuloksen. Se miksi Huffman ei ole optimaalinen johtuu siitä, että sekä Huffman, että Shannon Fano koodauksissa symbolin esitykseen käytetty bittimäärä esitetään kokonaislukuna. Esimerkiksi Huffmanin koodauksessa bittimäärä pyöristetään lähimpään kokonaislukuun. Kummassakaan ei voida siis päästä tarkalleen informaatioteorian mukaiseen tulokseen. Huffmanin koodauksesta tuli kuitenkin suosittu, koska se oli helppo ottaa käyttöön ja sen pakkaussuhteet olivat hyviä [Nelson ja Gailly, 1995]. Sekä Huffmanin koodaus, että Shannon Fano koodaus käyttävät molemmat suuruusluokan 0 mallia, jossa keskitytään yksittäiseen symboliin eikä oteta huomioon sitä, millaisessa kokonaisuudessa symboli esiintyy. Tällaisessa tapauksessa saadaan aikaan pakkausta, jos kaikkia symboleita ei ole aineistossa tarkalleen samaa määrää. Suuruusluokan 0 Huffman kooditaulukko sisältää 256 bittiä, mutta suuremmilla suuruusluokilla todennäköisyystaulukon vaatima tila kasvaa kovaa vauhtia. Suuruusluokassa 1 se on jo 65 536 bittiä. Tästä seuraa, että vaikka korkeampi suuruusluokka parantaa pakkauksen tehokkuutta, se kasvattaa todennäköisyystaulukon koon niin suureksi, että pakkauksen tuoma etu katoaa [Nelson ja Gailly, 1995]. Suuruusluokkaongelmaan on kuitenkin olemassa ratkaisu, jota käsitellään seuraavassa kappaleessa. 4. Mukautuva Huffman koodaus Huffman koodauksessa todennäköisyystaulukon täytyy olla aina pakatun aineiston mukana, joka muodostuu ongelmaksi, jos malli on suurempaa suuruusluokkaa kuin 0. Suuruusluokassa 0 tällä ei ole suurempaa merkitystä, koska taulukko vie enimmillään n. 250 tavua [Nelson ja Gailly, 1995]. Kuitenkin jo kun siirrytään suuruusluokan 1 malliin, annetaan yhden todennäköisyystaulukon sijaan 257 taulukkoa, joka kasvattaa pakatun aineiston kokoa merkittävästi. Jos pakattavat tiedostot eivät ole kovin suuria, tarkempien pakkaussuhteiden edut katoavatkin suuren todennäköisyystaulukon vuoksi. Tämä ongelma voidaan ratkaista niin, että ei käytetä staattista binääripuuta vaan päivitetään puuta sitä mukaa, kun pakattavaa aineistoa luetaan. Tällaisessa tapauksessa binääripuun luomiseen täytyy ottaa uudenlainen 6
7 lähestymistapa, koska puun luomiseen ei voida käyttää perinteistä Huffmanin algoritmia. Faller (1973) ja Gallagher (1978) loivat ensimmäiset mukautuvat Huffman algoritmit, joita myöhemmin päivittivät Knuth (1985) ja Vitter (1987) [Sayood, 2006]. Heidän algoritminsa perustuvat siihen, että Huffman puussa kaikilla solmuilla lukuun ottamatta juurisolmua on sisarsolmu. Pitämällä yllä tätä ominaisuutta voidaan varmistaa, että binääripuu pysyy Huffman puuna. Symbolit, joita ei vielä ole esitetty koodissa muodostavat ongelman mukautuvassa koodauksessa. Ratkaisu on käyttää EVV (ei vielä välitetty) solmua, joka sisältää käyttämättömien symbolien määrän. EVV solmu on aina, myös tyhjässä puussa, sisällytettynä puuhun painoarvolla 0. Mukautuvan Huffmanin algoritmin periaate on seuraavanlainen: 1. Jos nykyinen symboli on EVV, luodaan EVV solmulle kaksi lasta. Ensimmäiseksi lapseksi asetetaan uusi EVV - solmu ja toiseksi solmu, joka sisältää puuhun lisättävän symbolin. Siirrytään entiseen EVV - solmuun kohtaan ja siirrytään algoritmissa kohtaan 6. 2. Muussa tapauksessa siirrytään solmuun, joka sisältää kyseisen symbolin. 3. Tarkistetaan onko solmun painoarvo kyseisessä lohkossa suurin. 4. Jos solmun painoarvo ei ole suurin, vaihdetaan solmun ja lohkon suurimman paikkaa. 5. lisätään solmun painoarvoa yhdellä. 6. Tutkitaan, onko kyseessä juurisolmu. Jos ei, niin siirrytään kohtaan 3. Tehdään tätä niin kauan, että kyseessä on juurisolmu. Algoritmi 3. Algoritmi mukautuvan Huffman puun luomiselle ja päivitykselle [Sayood, 2006]. Algoritmin 3 ensimmäisessä vaiheessa tarkastetaan onko kyseessä EVV symboli ja jos on, luodaan EVV solmulle kaksi lasta ja asetetaan toiseksi lapseksi uusi EVV ja toiseksi kyseinen symboli. Päivitysoperaation tarkoitus on ylläpitää sisarsolmuperiaatetta, joten binääripuu on päivitettävä aina lisäyksen jälkeen. Tämän takia solmun luomisen jälkeen vaihdellaan binääripuun solmujen paikkaa, kunnes puu täyttää Huffman puun ehdot. Jos kyseessä on symboli, joka löytyy jo puusta, lisätään solmun painoarvoa, eli sitä määrää, montako kertaa symboli aineistossa esiintyy. Tämän jälkeen vaihdetaan tarvittaessa solmujen paikkaa kuten EVV solmun tilanteessa. 7
8 Kun mukautuva Huffman puu on luotu, on vuorossa aineiston pakkaus, jonka algoritmissa käydään läpi kaikki symbolit ja kutsutaan päivitysalgoritmia (3) jokaisen symbolin jälkeen: 1. Luetaan symboli. 2. Jos tämä on ensimmäinen kerta kun symboli esiintyy, pakataan EVV listan osoitin sekä väylä, jota kautta EVV - solmuun päästiin. 3. Muussa tapauksessa pakataan koodi, joka on väylä, joka kuljetaan juuresta kyseiseen solmuun. 4. Kutsutaan päivitysalgoritmia. 5. Siirrytään kohtaan 1 niin kauan, kunnes kyseessä on viimeinen symboli. Algoritmi 4. Algoritmi mukautuvan Huffman puun pakkaukselle [Sayood, 2006]. Algoritmin 4 kohdan 2 erikoistapauksessa ilmoitetaan pakatussa datassa, että kyseessä on symboli, jota ei ole vielä listassa. Muussa tapauksessa koodataan väylä, jota kautta päästän solmuun juuresta lähtien. Väylä muodostuu niin, että aina, kun puussa kuljetaan vasemmalle, on bittikoodi 0 ja kun kuljetaan oikealle, on koodi 1. Esimerkiksi jos symboliin päästään juuren kulkiessa ensin vasemmalle, sitten oikealle ja sitten vasemmalle, on bittikoodi tällöin 010. Mukautuvan Huffman puun purkaminen tapahtuu yksittäisten bittien avulla niin, että liikutaan puussa juuresta lähtien niin kauan, että vastaan tulee ulkosolmu tai EVV: 1. Aloitetaan Huffman puun juuresta. 2. Luetaan seuraava bitti pakatusta aineistosta. 3. Siirrytään joko vasempaan tai oikeaan lapseen bitin mukaisesti. Jos Huffman puussa kyseessä on sisäsolmu, siirrytään takaisin algoritmin kohtaan 2. Tehdään tätä niin kauan, kunnes tullaan ulkosolmuun tai EVV:hen. 4. Jos kyseessä on ulkosolmu ja se on EVV, puretaan aineistosta symboli sen mukaan, mitä indeksiä se vastaa EVV:ssä. 5. Jos kyseessä on ulkosolmu, tulostetaan symboli, joka sijaitsee kyseisessä solmussa. 8
6. Pienennetään symbolin painoarvoa ja kutsutaan päivitysalgoritmia. 7. Jatketaan kohdasta 1, kunnes kyseessä on viimeinen bitti. 9 Algoritmi 5. Algoritmi mukautuvan Huffman puun purkamiselle [Sayood, 2006]. Aina, kun yksi symboli on luettu, vähennetään sen arvoa binääripuussa. Näin siis Algoritmissa 5 ylläpidetään puuta myös purkamisen aikana. 5. Aritmeettinen koodaus Kappaleissa 3 ja 4 käsiteltiin todennäköisyyksiin perustuvia algoritmeja. Tärkeimpänä algoritmina esiteltiin Huffmanin koodaus, joka hallitsi pitkän aikaa tutkimusta pakkausteknologioiden alalla. Tässä kappaleessa esitellään aritmeettinen koodaus, joka on nousemassa yhä suositummaksi todennäköisyyksiin perustuvaksi koodaustavaksi Huffmanin koodauksen ohella. Huffman koodauksen yksi suurimmista ongelmista on se, että se pystyy pakkaamaan tietoa vain todennäköisyyksillä, jotka ovat kokonaislukumääräisiä. Monesti kuitenkin symbolien optimaalinen bittimäärä sisältää desimaaleja. Jos tietosisältö on esimerkiksi 2,6 bittiä, on Huffman puussa se koodattava joko 2 tai 3 bitillä. Tämä ongelma kasvaa suurimmaksi, kun jonkin yksittäisen symbolin todennäköisyys on erittäin suuri. Jos esimerkiksi symbolin todennäköisyys on 9/10, sen sisältämä informaatiosisältö on 0,15. Tässäkin tapauksessa symboli on pakattava yhdellä bitillä, joten ero alkuperäiseen tietosisältöön on suuri. Tämä ongelma muodostuu suureksi varsinkin binäärimuotoisessa datassa, kuten FAX laitteiden kaksivärisissä kuvissa, joissa todennäköisyys on välillä [0,1) [Sayood, 2006; Nelson ja Gailly, 1995]. Aritmeettinen koodaus tekee parannuksen tähän desimaaliluvulliseen todennäköisyysongelmaan. Aritmeettinen koodaus on siis käyttökelpoinen varsinkin binäärimuotoisissa kuvissa sekä aineistoissa, joissa erilaisia symboleita on vähän sekä sellaisissa aineistoissa, joissa todennäköisyydet ovat vinoutuneet (skewed), eli niiden jakauma ei ole normaalijakauman muotoinen [Sayood, 2006]. Aritmeettinen koodaus ei luo yksittäistä koodia jokaiselle symbolille, vaan koodiluku luodaan koko aineistolle. Tällä tavalla symbolin bittimäärä voidaan määritellä tarkemmin ja informaatiosisältö saadaan hyödynnettyä Huffman koodausta paremmin. Aritmeettinen koodaus on pakkausnopeudeltaan Huffman koodaukseen verrattuna hitaampi, mutta pakkausteho on yleensä parempi [Sayood, 2006; Nelson ja Gailly, 1995]. 9
10 Aritmeettisessa koodauksessa jokaisella symbolilla on oma paikkansa todennäköisyysvälillä [0, 1). Kaikkien todennäköisyyksien summa on aina 1. Koodauksessa jokainen merkki omistaa rivissä sen alueen, johon se kuuluu. Symbolit järjestetään ensin johonkin järjestykseen, jonka jälkeen jokaiselle symbolille annetaan väli jolle se kuuluu. Symbolien järjestyksellä ei ole väliä, kunhan purkuoperaatiossa käytetään samaa järjestystä kuin pakkauksessa. Esimerkiksi symbolijono binääripuu voitaisiin järjestää aakkosjärjestykseen. Tässä tapauksessa kirjain b omistaisi ensimmäisenä lukuna välin [0, 0,1). Seuraavan välin voisi omistaa symboli i, jota löytyy kaksi kappaletta, jolloin i omistaisi välin [0,1, 0,3) ja niin edelleen. 5.1. Aritmeettinen pakkausalgoritmi Aritmeettisessa pakkausalgoritmissa symbolimäärän ja aineiston suuretessa jokainen uusi symboli rajaa symbolin aluetta.: 1. Pienin luku <- 0,0 ja suurin luku <- 1,0 2. Luetaan merkkijonosta seuraava luku 3. nykyisen symbolin ala <- suurin luku-pienin luku 4. suurin luku <- pienin luku + nykyisen symbolin ala * nykyisen symbolin yläraja 5. pienin luku <- pienin luku + nykyisen symbolin ala * nykyisen symbolin alaraja 6. Palataan kohtaan 2, kunnes on luettu aineiston jokainen symboli. 7. Pakattu aineisto on pienin luku. Algoritmi 6. Aritmeettinen pakkausalgoritmi [Sayood, 2006]. Algoritmissa 6 Koodaus alkaa ensimmäisestä merkistä, jonka todennäköisyyden sisällä toimitaan symbolijonon loppuaika. Tätä tehdään rekursiivisesti, eli aina kun uusi merkki esiintyy, siirrytään rekursiossa sen todennäköisyyteen aikaisempien todennäköisyyksien sisällä. Esimerkiksi symbolijonossa binääripuu siirrytään aluksi ensimmäinen merkin todennäköisyyden sisälle, b = [0, 0,1). Tämän jälkeen esimerkissä siirrytään merkkiin i ja sijaitsee välillä [0,1, 0,3). Tällöin siirrytään ensimmäisen symbolin sisälle ja todennäköisyys muuttuu luvuksi [0,01, 0,03). Kolmas merkki toistaa tätä. Symboli n, jonka todennäköisyys rivillä on [0,3, 0,4) muuttaa koko merkkijonon todennäköisyydeksi [0,016, 0,018). Kun kaikki symbolit on 10
11 käyty läpi, saadaan alue, jonka alaraja on algoritmista saatu pakkaustulos. binääripuu lauseen esimerkissä tulos on 0.0179615616. 5.2. Aritmeettinen purkualgoritmi Aritmeettisessa purkualgoritmissa lähdetään pakatun datan alusta ja käydään jokainen desimaali läpi. Jokaisen puretun symbolin jälkeen poistetaan sitä vastaava desimaali: 1. Luku <- Pakkauksen tuottama koodi. 2. Nykyinen symboli <- Symbolitaulukosta luvun osoittama väli 3. Lisätään symboli purettuun dataan. 4. Ala <- nykyisen symbolin yläraja - nykyisen symbolin alaraja 5. Numero <- numero - nykyisen symbolin alaraja 6. Numero <- numero / ala 7. Palataan kohtaan 2, kunnes on luettu jokainen desimaali. Algoritmi 7. Aritmeettinen purkualgoritmi [Nelson ja Gailly, 1995]. Purkualgoritmin (7) havainnollistamiseksi puretaan pakkausalgoritmissa käytetty esimerkki binääripuu, jonka pakattu data oli 0.0179615616. Ensimmäinen desimaali on 0 eli voidaan olettaa että kyseessä on symboli b, joka lisätään ensimmäisenä merkkinä purettuun dataan. Symbolien todennäköisyysjakauma toimitetaan pakkausalgoritmille, joten tiedämme että symbolin b :n alue on [0, 0,1). Tämä alue poistetaan pakatusta datasta, jonka jälkeen pakattu data esimerkissä on 0.179615616. Poistoa tehdään niin kauan, kunnes pakattu data on kokonaan purettu. 5.3. Aritmeettisen algoritmin vertailua Huffmanin algoritmiin Tutkimme merkkijonoa aaaaaaa ja määrittelemme, että merkin a todennäköisyys koko aineistossa on 0,9. Tässä tapauksessa ensimmäinen a koodataan välillä [0,0, 0,9), toinen [0,0, 0,81) ja niin edelleen. Koko merkkijonon todennäköisyys muodostuu lopulta lopetussymbolin kanssa seuraavaksi: [0,43046721, 0,4782969). aaaaaaa merkkijono voidaan tällöin merkitä esimerkiksi luvulla 0,45. Tämä desimaaliluku vie alle seitsemän bittiä. Kyseisen merkkijonon pituus Huffman koodauksessa veisi vähimmillään yhdeksän bittiä. Kuten aikaisemmin todettiin, aritmeettinen koodaus tuo parannusta Huffmanin koodaukseen esimerkiksi binäärimuotoisissa kuvissa sekä aineistoissa, joissa aakkosto on pieni [Sayood, 2006; Nelson ja Gailly, 1995]. 11
12 6. Sanakirjat tiedonpakkauksen uusi aika Aikaisemmissa kappaleissa kerrottiin pakkausteknologioista, jotka keskittyivät tutkimaan tiedon pakkausta informaatioteorian avulla. Tärkeitä käsitteitä olivat muun muassa entropia, redundanssi informaatio sekä tilastolliset todennäköisyydet. Esittelimme Huffmanin koodauksen sekä aritmeettisen koodauksen, jotka olivat tärkeimpiä teorioita koodauksen tutkimisessa. 1970 luvun lopulla alkoi muutos kohti sanakirjoja, kun Jacob Ziv ja Abraham Lempel julkaisivat vuonna 1977 teoksen A Universal Algorithm for Sequential Data Compression. Tämä teos ei kuitenkaan välittömästi tuonut suosiota. Suuren yleisön tietoon sanakirjat tulivat vasta vuonna 1984 Terry Welchin julkaistua oman teoksensa A Technique for High Performance Data Compression [Welch, 1984]. Informaatioteoriaan perustuvissa koodauksissa yksittäiselle symbolille lasketaan todennäköisyys ja mallin pitää sekä arvioida, että ennustaa keskiarvosta eroavia todennäköisyyksiä. Sanakirjoissa koodaus perustuu yksittäisten symbolien sijaan symboliryhmiin, jotka muodostavat sanakirjan. Pakatussa aineistossa käytetään symboliryhmien sijaan osoittimia (pointer) kyseiseen sanakirjan kohtaan. 6.1. Sanakirjoihin siirtymisen vaiheet Sanakirjoihin siirryttiin kohtalaisen hitaasti. Vuonna 1977 julkaistu algoritmi otettiin tietokonemaailman käyttöön vasta 1984, kun Terry Welchin LZW:ksi nimeämän algoritmin myötä. Terry Welch käytti algoritminsa pohjana vuonna 1978 Lempelin ja Zivin julkaisemaa LZ78 algoritmia. LZW algoritmi oli alun perin suunniteltu kasettiasemien ja levykeasemien käyttöön, mutta siitä sai helposti muunneltua yleisesti toimivan pakkausohjelman. Lähes välittömästi artikkelin julkaisun jälkeen alettiin kehitellä LZW:llä toimivaa Unix pakkausohjelmaa. Ohjelma valmistui noin vuosi artikkelin julkaisun jälkeen ja sen nimi oli Compress [Nelson ja Gailly, 1995]. Vuonna 1985 julkaistiin LZ78 algoritmilla toimiva ARC pakkausohjelma, joka nousi välittömästi MS DOS käyttöjärjestelmien keskuudessa suureen suosioon. ARC:n avulla pystyttiin ensimmäistä kertaa MS DOS käyttöjärjestelmässä siirtämään useita tiedostoja samaan aikaan, joka oli ollut jo mahdollista Unix koneissa tar pakkausten avulla. PKARC nimisen kilpailijan markkinoille tulo lisäsi LZ78:n suosiota, mutta 1990 luvulla PKZIP, LHarc ja ARJ ohjelmistojen ilmestyminen muutti suuntausta LZ78 algoritmista LZ77 algoritmiin. Myös Internetissä suosittu PNG kuvanpakkausformaatti käyttää LZ77:aa, johtuen Unisys yhtiön LZW algoritmia käyttävän GIF pakkausformaatin patenttiongelmista [Nelson ja 12
13 Gailly, 1995]. Paneudumme yksittäisten sanakirjojen historiaan lähemmin seuraavassa kappaleessa. 6.2. Staattiset ja mukautuvat sanakirjat Staattinen sanakirja on pysyvä sanakirja, joka luodaan ennen pakkausta, eikä sitä muuteta pakkauksen aikana. Yksi staattisen sanakirjan hyvistä puolista on se, että sanakirja voidaan optimoida tarkasti pakattavan aineiston mukaan. Joissain tilanteissa tämä on tarpeen, esimerkiksi tietokonepeliin ohjelmoitava pysyvä tietokanta voisi käyttää tällaista ratkaisua. Staattisella sanakirjalla on kuitenkin sama ongelma kuin muissakin staattisissa malleissa, eli sanakirja täytyy välittää pakatun aineiston mukana ja tämä lisää tiedoston kokoa [Sayood, 2006; Nelson ja Gailly, 1995]. Yleisimmät tunnetut sanakirja algoritmit, kuten LZ77, LZ78 ja LZW edustavat mukautuvia sanakirjoja. Mukautuvat sanakirjat toimivat staattisen sanakirjan sijaan niin, että sanakirja on aluksi tyhjä ja siihen lisätään symbolijonoja sitä mukaa, kun pakkaus etenee. Tällaisessa pakkausalgoritmissa jaetaan aineisto ensin osiin, verrataan niitä sanakirjaan ja sen jälkeen lisätään ne sanakirjaan. Lopulta osoittimet ja aineisto pakataan yhdeksi tiedostoksi. Purkualgoritmissa puretaan tietovirta symboleiksi tai sanakirjaviittauksiksi ja lisätään sanat sanakirjaan, minkä jälkeen muutetaan sanakirja osiksi ja muutetaan osat symboleiksi. Purku, ja pakkausalgoritmit eivät vaadi paljoa prosessoritehoa, minkä takia mukautuvat sanakirjat ovat staattisia sanakirjoja suositumpia [Sayood, 2006; Nelson ja Gailly, 1995]. 7. Ikkunoihin perustuvat sanakirja algoritmit 7.1. LZ77 Ensimmäinen Lempelin ja Zivin esittelemä algoritmi oli vuonna 1977 ilmestynyt LZ77. Algoritmi sisältää ennalta määrätyn kokoisen ikkunan, joka on jaettu kahteen osaan. Ensimmäinen osa sisältää tietyn osan aikaisemmin koodatuista symboleista, alkaen edellisestä symbolista taaksepäin. Toinen osa sisältää etukäteispuskurin (look ahead buffer), joka lukee aineistosta tietyn kokoisen osan tulevia merkkejä. Edelliset symbolit sisältävä osa on etukäteispuskuria huomattavasti suurikokoisempi. Algoritmin toimintatapa perustuu näiden kahden osan vertaamiseen. Jos ikkunasta ei löydy symbolia ennestään, lisätään se sanakirjaan. Lopulta usein esiintyvät symbolijonot ryhmitellään suuremmiksi merkkiryhmiksi ja harvemmin esiintyvät pienemmiksi. Pakkausteho riippuu siitä kuinka pitkiä sanakirjan symbolijonot ovat, kuinka suuri ikkuna pakkauksessa on käytössä ja kuinka suuri entropia alkuperäisessä aineistossa on. 13
14 7.2. LZ77 pakkausalgoritmi sanakirjan luomiseen LZ77:ssa sanakirja muodostetaan samalla, kun aineistoa luetaan sisään. Etukäteispuskurista luetaan dataa ja verrataan sitä ikkunan sisältöön. Jos vastaavuus löytyy, tallennetaan osoitin tähän symboliin sekä vastaavuuden jälkeinen symboli: 1. Lähdetään lukemaan aineistoa alusta. 2. Käydään ikkunan aikaisemmin koodatut symbolit läpi ja etsitään se kohta, jolla on pisin vastaavuus etukäteispuskurin kanssa. 4. Jos vastaavuus löytyy, tallennetaan suurimmasta vastaavuuden kohdasta osoitin, joka sisältää tiedot: {[Osoittimen kohta, josta pisin vastaavuus alkoi], [Vastaavuuden pituus], [Koko vastaavuutta seuraava symboli]} 5. Muussa tapauksessa tallennetaan osoitin, joka sisältää etukäteispuskurin ensimmäisen symbolin arvoilla {0, 0, [Ensimmäinen symboli etukäteispuskurissa]}. 6. Jos etukäteispuskuri ei ole tyhjä, siirrytään aineistossa yksi symboli eteenpäin sekä algoritmissa kohtaan 2. Algoritmi 8. LZ77 pakkausalgoritmi [Sayood, 2006; Christina Zeeh, 2003]. Esimerkiksi jos algoritmissa 8 vertailtaisiin etukäteispuskurissa binääripuu symbolijonoa ja ikkunasta löytyisi jo symbolijono binää, tallennettaisiin sanakirjaan osoitin, joka viittaisi tähän kohtaan, sekä seuraava merkki eli r. Seuraavan kerran, kun sana binääripuu tulee vastaan, lisätään osoitin tähän kyseiseen sanakirjan esiintymään, joka sisältää nyt siis binäär ja seuraava merkki eli i. Kun etukäteispuskuri on tyhjä, on pakkausalgoritmi käyty läpi ja luotu sanakirja, jolla aineiston voi pakata. LZ77 sisältää suorituskyvyllisiä sekä koodaustehollisia ongelmia. Ensimmäinen ongelma on suorituskyvyllinen. Algoritmissa tehdään vertailu aikaisemmin koodattujen merkkien ja etukäteispuskurin aloitusmerkin kanssa, mikä on raskas operaatio. Ongelma pahenee entisestään, jos yritetään parantaa pakkaussuhdetta suurentamalla ikkunan kokoa. Toinen ongelma voi tulla pakkaustehon kanssa. Algoritmi olettaa symbolijonojen esiintyvän lähellä toisiaan, joka johtaa siihen, että merkkijonoa ei koodata, jos se toistuu etukäteispuskuria suuremmalla merkkijonovälillä. Pahimmassa tapauksessa koodattu aineisto on suurempi kuin alkuperäinen. Kolmas ongelma tulee vastaan, jos vastaantulevia symboleita ei löydy sanakirjasta. Tässäkin tapauksessa sen sijaan, että kerrottaisiin ainoastaan symboli, algoritmi käyttää 14
15 osoitinta, joka kertoo kohdan, pituuden ja seuraavan symbolin. Tämä tapa vie enemmän tilaa kuin vain alkuperäisen symbolin esittäminen, joten tässäkin tilanteessa pahimmassa tapauksessa koodaus vain suurentaa alkuperäisen tiedoston kokoa [Sayood, 2006; Nelson ja Gailly, 1995; Zeeh, 2003]. 7.3. LZSS LZ77 algoritmiin on tehty useita parannuksia, joista suosituin on vuonna 1982 julkaistu Storerin ja Szymanskin LZSS. LZSS käyttää LZ77:n tavoin ikkunaa ja korjaa tehokkuusongelmia, joita LZ77:ssa oli. Ensimmäinen parannus on siinä, kuinka ikkunaa hallitaan. LZSS parantaa koodausvaiheen koodauksen lisäämällä teksti ikkunan koodatut symbolit binäärihakupuuhun. Tällä tavoin saadaan LZ77:n O(n 2 ) aikavaatimuksellisesta algoritmista O(n log n) tyyppinen. Tämän uudistuksen ansiosta on mahdollista suurentaa tekstiikkunaa huomattavasti ilman suurempia aikavaatimusongelmia [Nelson ja Gailly, 1995]. Toinen parannus LZSS:ssä on symbolijonojen koodaamisessa. LZ77:ssa koko aineisto koodattiin osoittimella, joka kertoi aloituskohdan, pituuden ja symbolijonoa seuraavan symbolin. Myös sellaiset symbolit, joita ei löytynyt ikkunasta, muutettiin osoittimiksi. LZSS algoritmi voi sen sijaan tuottaa sekä osoittimia, että symboleja. Pelkkinä symboleina esitetään siis sellaiset symbolit, joita ei ikkunasta löydy ennestään [Nelson ja Gailly, 1995]. 8. LZ78 ja perilliset 8.1. LZ78 LZ77:n suurimpana ongelmana pidetään sitä, että jos toistoa tapahtuu alkuperäisessä aineistossa liian harvoin, saattaa toistuva symbolijono jäädä ikkunan ja pakkauksen ulkopuolelle. Tilannetta voidaan yrittää parantaa suurentamalla etukäteispuskuria, mutta tämä johtaa vielä huonompiin tuloksiin, koska osoittimen kohdan ilmoittaminen sekä symbolijonon koodaus vievät enemmän tilaa. Tämän lisäksi prosessoriajan kulutus kasvaa nopeasti, varsinkin puskurin kokoa nostaessa [Nelson ja Gailly, 1995]. LZ77:n ongelmien ratkaisemiseksi Lempel ja Ziv julkaisivat vuonna 1978 Compression of Individual Sequences via Variable Rate Coding nimisessä artikkelissa LZ78 algoritmin, jossa he paransivat LZ77:n ongelmia. LZ78 algoritmi ratkaisee LZ77:n suorituskykyongelman ja koodausteho ongelman niin, että se poistaa pakkausalgoritmista ikkunan jolloin koko sanakirjaa verrataan sisään tulevaan tietovirtaan [Sayood, 2006; Nelson ja Gailly, 1995]. 15
16 8.2. LZ78 pakkausalgoritmi LZ78 pakkausalgoritmissa koko sanakirjaa verrataan sisään tulevaan tietovirtaan toisin kuin LZ77:ssa, jossa luotiin ikkuna vertailun avuksi: 1. Asetetaan väliaikainen i muuttuja tyhjäksi. 2. Luetaan seuraava symboli s. 3. Jos i + s sijaitsee sanakirjassa, asetetaan i:n arvoksi i + s ja siirrytään kohtaan 6. 4. Muussa tapauksessa lisätään koodattuun dataan {[viittaus siihen indeksiin jossa sijaitsee i], [s]}. Jos sanakirjassa ei ole viittausta i:hin, asetetaan viitteeksi 0. 5. Lisätään i + s sanakirjaan ja tyhjennetään i. 6. jatketaan kohdasta 2, kunnes koko aineisto on luettu. Algoritmi 9. LZ78 pakkausalgoritmi [Christina Zeeh, 2003]. Esimerkkinä pakkauksesta koodataan algoritmissa 9 sana abab. Algoritmissa luetaan ensin symboli a, joka lisätään koodattuun dataan arvolla {0, a }. Tämän jälkeen sanakirjaan lisätään symboli a indeksillä 1 ja jatketaan seuraavasta merkistä. Seuraavaa merkkiä b :kään ei näytä löytyvän sanakirjasta, joten lisätään se koodattuun dataan arvolla {0, b }, lisätään b sanakirjaan indeksiin 2 ja Jatketaan seuraavan symboliin. Seuraava symboli on a ja näyttää olevan jo sanakirjassa, joten lisätään se väliaikaiseen muuttujaan ja aloitetaan alusta. Seuraava merkki on b, joten merkkijono on nyt i + s, eli ab. Tätä ei näytä olevan sanakirjassa, joten lisätään koodattuun dataan a :n indeksi ja symboli, eli {1, b }. Tämän jälkeen lisätään sanakirjaan ab indeksiin 3. LZ78 algoritmi sisältää ongelmallisia kohtia, joita on yritetty korjata erilaisilla tavoilla. Suurin ongelma algoritmissa on sanakirja, joka voi kasvaa rajattoman suureksi. Sanakirjan kasvua voidaan kuitenkin yrittää rajoittaa muokkaamalla algoritmia. Esimerkiksi UNIXin compress pakkausohjelmassa tarkkaillaan tiedoston pakkaussuhdetta. Jos pakkaussuhde alkaa heiketä, sanakirja poistetaan ja ohjelma aloittaa pakkauksen alusta. Vaihtoehtoisesti ohjelmassa voidaan käyttää nykyistä sanakirjaa, mutta ei lisätä siihen enempää sanakirjamerkintöjä tulevasta tietovirrasta [Nelson ja Gailly, 1995; Zeeh, 2003]. 16
17 8.3. LZW Terry Welch julkaisi vuonna 1984 A Technique for High Performance Data Compression artikkelin. Kuten 6.1 kohdassa todettiin, tämä artikkeli aloitti sanakirjojen nopean leviämisen. LZW on LZ78 algoritmin jälkeläinen. Algoritmissa luetaan sanakirjaan kaikki yksittäiset symbolit ennen koodauksen aloittamista, joten algoritmissa ei tarvitse ottaa huomioon symboleita ja koko aineisto voidaan tallentaa ainoastaan osoittimina. Tämä yksinkertaistaa algoritmia LZ78:aan verrattuna jonkin verran. Welch otti artikkelissaan kantaa myös jo LZ78 algoritmissa mainittuun sanakirjan rajattomaan kasvuun. Hän ehdotti, että osoittimien koko olisi 12 bittiä, jolloin sanakirjaan voitaisiin lisätä enimmillään 4096 sanakirjamerkintää. Tämän jälkeen sanakirja muuttuisi staattiseksi [Sayood, 2006; Nelson ja Gailly, 1995; Zeeh, 2003]. 8.4. LZW algoritmi LZW algoritmin toimintatapa on seuraavanlainen: 1. Asetetaan väliaikainen i muuttuja tyhjäksi. 2. Luetaan seuraava symboli s. 3. Jos i + s sijaitsee sanakirjassa, asetetaan i:n arvoksi i + s ja siirrytään kohtaan 6. 4. Muussa tapauksessa lisätään koodattuun dataan {[viittaus siihen indeksiin jossa sijaitsee i]}. 5. Lisätään i + s sanakirjaan ja asetetaan i:ksi s. 6. jatketaan kohdasta 2, kunnes koko aineisto on luettu. Algoritmi 10. LZ78 pakkausalgoritmi [Christina Zeeh, 2003]. LZW sanakirja algoritmi (10) vastaa LZ78 algoritmia ainoastaan sillä erolla, että LZW:ssä luetaan kaikki käytettävät symbolit sanakirjaan ennen koodauksen aloittamista. Tämä yksinkertaistaa algoritmia niin, ettei sanakirjaan tarvitse lisätä kuin osoittimia. 9. Yhteenveto Nykyaikainen tiedonpakkausteorioiden kehittely sai alkunsa Bell Labsin Claude Shannonin 1940 luvulla kehittelemästä informaatioteoriasta. Informaatioteoria antoi hyvän kuvan siitä, missä rajoissa tietoa pystyttiin binäärimuodossa pakkaamaan poistamalla redundassi informaatio. David A. Huffman (1925 1999) kehitti tämän teorian pohjalta vuonna 1952 17
18 algoritmiteorian, joka muodostui vuosikymmenten ajaksi tärkeimmäksi pakkaustekniikoiden tutkimuskohteeksi. Ajan mittaan informaatioteorian perusteella kehitettiin myös Huffmanin algoritmista eroavia pakkausalgoritmeja. Huffman koodauksen perilliseksi muodostui tietokoneiden kehittyessä aritmeettinen koodaus, joka oli Huffman koodausta tehokkaampi, mutta tietokoneteholtaan vaativampi. Todennäköisyyksiin perustuva pakkaus oli 1970 luvun loppuun asti ainoa varteenotettava tekniikka harrastaa tiedonpakkausta, kunnes Jacob Ziv ja Abraham Lempel julkaisivat LZ77 ja LZ78 algoritmit. Sanakirjat olivat aivan uudenlainen lähtökohta tiedonpakkauksen tutkimisessa ja ne erosivat aikaisemmista algoritmeista, koska ne eivät perustuneet informaatioteoriaan. Nämä algoritmit eivät yleistyneet välittömästi. Vasta Terry Welchin julkaistessa LZ78:aan perustuvan LZW algoritmin vuonna 1984 tiedonpakkauksen tutkimus siirtyi kohti sanakirjoja. 18
19 Viiteluettelo [Nelson ja Gailly, 1995] Mark Nelson ja Jean loup Gailly, The Data Compression Book 2nd edition, M&T Books, New York, NY, 1995. [Sayood, 2006] Khalid Sayood, Introduction to Data Compression (Third Edition), Elsevier Inc, 2006. [Lelewer ja Hirschberg, 1987] Debra A. Lelewer ja Daniel S. Hirschberg, Data compression, ACM Computing Surveys 19 (Sep. 1987), 261 296. [Stix, 1991] Gary Stix, Profile: David A. Huffman, Scientific American, 1991. [Cormen, Leiserson, Rivest ja Stein, 2001] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein, Introduction to Algorithms, Second Edition, MIT Press and McGraw Hill, 2001. [Zeeh, 2003] Christina Zeeh, Seminar Famous Algorithms, January 16, 2003. [Huffman, 1952] David A. Huffman, A Method for the construction of minimumredundancy codes, Proc. IRE 40, 1098 (1952). [Welch, 1984] Terry A. Welch, A technique for high performance data compression, Computer 17 (Jun. 1984), 8 19. [Ziv ja Lempel, 1977] Jacob Ziv and Abraham Lempel, A Universal Algorithm for Sequential Data Compression, IEEE Transactions on Information Theory, 23(3), (May 1977), 337 343. [Ziv ja Lempel, 1978] Jacob Ziv and Abraham Lempel, Compression of Individual Sequences via Variable Rate Coding, IEEE Transactions on Information Theory, 24, (1978), 530 536. 19