- kod = sljedeći bit iz toka, dužina = 1
- Ćao kod< base
kod = kod<< 1
kod = kod + sljedeći bit iz toka
dužina = dužina + 1 - simbol = simbol + kod - baza]
Drugim riječima, gurati ćemo bit po bit iz ulaznog toka u varijablu koda slijeva do koda< base. При этом на каждой итерации будем увеличивать переменную length на 1 (т.е. продвигаться вниз по дереву). Цикл в (2) остановится когда мы определим длину кода (уровень в дереве, на котором находится искомый символ). Остается лишь определить какой именно символ на этом уровне нам нужен.
Pretpostavimo da se petlja u (2), nakon nekoliko iteracija, zaustavlja. U ovom slučaju, izraz (kod - baza) je serijski broj željenog čvora (simbola) na nivou dužine. Prvi čvor (simbol) koji se nalazi na nivou dužine u stablu nalazi se u nizu simbola na indeksnim isključenjima. Ali nas ne zanima prvi znak, već numerirani znak (kod - baza). Stoga je indeks traženog simbola u nizu simbola jednak (offs + (code - base)). Drugim riječima, željeni simbol simbol=symb + kod - baza].
Navedimo konkretan primjer. Koristeći opisani algoritam, dekodiramo poruku Z / .
Z / ="0001 1 00001 00000 1 010 011 1 011 1 010 011 0001 1 0010 010 011 011 1 1 1 010 1 1 1 0010 01 01 01 01 01 01 10 1 1"
- kod = 0, dužina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 1 = 1
dužina = 3 + 1 = 4
kod = 1 = baza = 1 - simbol = symb = 2 + kod = 1 - baza = 1] = symb = A
- kod = 1, dužina = 1
- kod = 1 = baza = 1
- simbol = symb = 7 + kod = 1 - baza = 1] = symb = H
- kod = 0, dužina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 3 + 1 = 4
kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 1 = 1
dužina = 4 + 1 = 5
kod = 1 > baza = 0 - simbol = symb = 0 + kod = 1 - baza = 0] = symb = F
Dakle, dekodirali smo prva 3 znaka: A, H, F. Jasno je da ćemo slijedeći ovaj algoritam dobiti upravo poruku S.
Proračun dužine koda
Da bismo kodirali poruku, moramo znati kodove znakova i njihove dužine. Kao što je navedeno u prethodnom odeljku, kanonski kodovi su u potpunosti određeni svojim dužinama. Dakle, naš glavni zadatak je izračunavanje dužine kodova.
Ispostavilo se da ovaj zadatak, u velikoj većini slučajeva, ne zahtijeva eksplicitnu konstrukciju Huffmanovog stabla. Štaviše, algoritmi koji koriste internu (ne eksplicitnu) reprezentaciju Hafmanovog stabla su mnogo efikasniji u smislu brzine i troškova memorije.
Danas postoji mnogo efikasnih algoritama za izračunavanje dužine koda (,). Ograničićemo se na razmatranje samo jednog od njih. Ovaj algoritam je prilično jednostavan, ali je unatoč tome vrlo popularan. Koristi se u programima kao što su zip, gzip, pkzip, bzip2 i mnogi drugi.
Vratimo se algoritmu za konstruisanje Hafmanovog stabla. Na svakoj iteraciji, izvršili smo linearnu pretragu za dva čvora s najmanjom težinom. Jasno je da je prioritetni red kao što je piramidalni (minimalni) pogodniji za ovu svrhu. Čvor sa najmanjom težinom imaće najveći prioritet i biće na vrhu piramide. Hajde da predstavimo ovaj algoritam.
Uključimo sve kodirane simbole u piramidu.
Iz piramide ćemo uzastopno izdvojiti 2 čvora (to će biti dva čvora s najmanjom težinom).
Hajde da se formiramo novi čvor i pričvrstite na njega, kao djeca, dva čvora uzeta iz piramide. U ovom slučaju postavljamo težinu formiranog čvora jednaku zbroju težina podređenih čvorova.
Uključimo formirani čvor u piramidu.
Ako postoji više od jednog čvora u piramidi, ponovite 2-5.
Pretpostavićemo da je za svaki čvor pohranjen pokazivač na njegov roditelj. U korijenu stabla postavljamo ovaj pokazivač jednak NULL. Odaberimo sada čvor (simbol) lista i, prateći sačuvane pokazivače, penjaćemo se po stablu sve dok sledeći pokazivač ne postane NULL. Poslednji uslov znači da smo stigli do korena drveta. Jasno je da je broj prelazaka sa nivoa na nivo jednak dubini lisnog čvora (simbola), a samim tim i dužini njegovog koda. Prolaskom kroz sve čvorove (simbole) na ovaj način dobijamo dužine njihovih kodova.
Maksimalna dužina koda
U pravilu se kod kodiranja koristi tzv šifrarnik (šifarnik), jednostavna struktura podataka, u suštini dva niza: jedan sa dužinama, drugi sa kodovima. Drugim riječima, kod (kao niz bitova) je pohranjen na memorijskoj lokaciji ili registru fiksne veličine (obično 16, 32 ili 64). Kako bismo spriječili prelijevanje, moramo biti sigurni da će kod stati u registar.
Ispostavilo se da na abecedi N znakova maksimalna veličina koda može biti dužine do (N-1) bita. Drugim riječima, sa N=256 (uobičajena opcija) možemo dobiti kod dug 255 bita (iako za ovo datoteka mora biti veoma velika: 2.292654130570773*10^53~=2^177.259)! Jasno je da se takav kod neće uklopiti u registar i sa njim treba nešto učiniti.
Prvo, hajde da saznamo pod kojim uslovima dolazi do prelivanja. Neka frekvencija i-tog simbola bude jednaka i-tom Fibonačijevom broju. Na primjer: A-1, B-1, C-2, D-3, E-5, F-8, G-13, H-21. Konstruirajmo odgovarajuće Huffmanovo stablo.
ROOT /\ / \ / \ /\ H / \ / \ /\ G / \ / \ /\ F / \ / \ /\ E / \ / \ /\ D / \ / \ /\ C / \ / \ A B
Takvo drvo se zove degenerisati. Da bi ga primili, frekvencije simbola moraju rasti barem onoliko koliko su Fibonačijevi brojevi ili čak i brže. Iako je u praksi, koristeći stvarne podatke, gotovo nemoguće dobiti takvo stablo, vrlo ga je lako generirati umjetno. U svakom slučaju, ova opasnost se mora uzeti u obzir.
Ovaj problem se može riješiti na dva prihvatljiva načina. Prvi od njih se oslanja na jedno od svojstava kanonskih kodova. Činjenica je da u kanonskom kodu (bit string) nijedan bitniji bit ne može biti različit od nule. Drugim riječima, svi ostali bitovi možda uopće neće biti sačuvani, jer uvijek su nula. U slučaju N=256, potrebno je samo pohraniti najmanje značajnih 8 bitova iz svakog koda, pod pretpostavkom da su svi ostali bitovi jednaki nuli. Ovo rješava problem, ali samo djelimično. Ovo će značajno zakomplicirati i usporiti i kodiranje i dekodiranje. Stoga se ova metoda rijetko koristi u praksi.
Drugi metod je da se veštački ograniči dužina kodova (bilo tokom izgradnje ili posle). Ova metoda je općenito prihvaćena, pa ćemo se na njoj detaljnije zadržati.
Postoje dvije vrste algoritama za ograničavanje dužine koda. Heuristički (približan) i optimalan. Algoritmi drugog tipa su prilično složeni za implementaciju i po pravilu zahtijevaju više vremena i memorije od prvog. Efikasnost heuristički ograničenog koda određena je njegovim odstupanjem od optimalno ograničenog koda. Što je ova razlika manja, to bolje. Vrijedi napomenuti da je za neke heurističke algoritme ova razlika vrlo mala ( , , ), i oni vrlo često generiraju optimalan kod (iako ne garantuju da će to uvijek biti slučaj). Štaviše, jer u praksi se prelivanje događa izuzetno rijetko (osim ako ne postoji vrlo striktno ograničenje na maksimalnu dužinu koda); s malom veličinom alfabeta preporučljivije je koristiti jednostavne i brze heurističke metode.
Pogledat ćemo jedan prilično jednostavan i vrlo popularan heuristički algoritam. Našao je svoju primjenu u programima kao što su zip, gzip, pkzip, bzip2 i mnogim drugim.
Problem ograničavanja maksimalne dužine koda je ekvivalentan problemu ograničavanja visine Hafmanovog stabla. Imajte na umu da po konstrukciji svaki nelisni čvor u Huffmanovu stablu ima tačno dva djeteta. Pri svakoj iteraciji našeg algoritma smanjit ćemo visinu stabla za 1. Dakle, neka je L maksimalna dužina koda (visina stabla) i želimo je ograničiti na L / < L. Neka je dalje RN i krajnji desni listni čvor na nivou i, a LN i je krajnji lijevi.
Počnimo sa radom od nivoa L. Pomerimo čvor RN L na mesto njegovog roditelja. Jer čvorovi idu u parovima; moramo pronaći mjesto za čvor pored RN L. Da bismo to uradili, nalazimo nivo j najbliži L, koji sadrži čvorove lista, tako da je j < (L-1). Umjesto LN j, formiraćemo nelisni čvor i priložiti mu kao podređeni čvor LN j i preostali neupareni čvor sa nivoa L. Primijeniti istu operaciju na sve preostale parove čvorova na nivou L. Jasno je da smo preraspodjelom čvorova na ovaj način smanjili visinu našeg stabla za 1. Sada je jednako (L-1). Ako sada L / < (L-1), onda ćemo isto učiniti sa nivoom (L-1) itd. dok se ne dostigne traženi limit.
Vratimo se na naš primjer, gdje je L=5. Ograničimo maksimalnu dužinu koda na L / =4.
ROOT /\ / \ / \ /\ H C E / \ / \ / \ / \ /\ A D G / \ / \ B F
Može se vidjeti da je u našem slučaju RN L = F, j=3, LN j = C. Prvo pomjerimo čvor RN L = F na mestu svog roditelja.
ROOT /\ / \ / \ /\ H / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ C E / \ / \ / \ / \ F A D G B(nespareni čvor)
Sada na mjestu LN j = C Formiramo čvor bez lista.
ROOT /\ / \ / \ /\ H E / \ / \ / \ / \ / \ / \ F A D G ? ? B(nespareni čvor) C(nespareni čvor)
Pričvrstimo dva neuparena na formirani čvor: B I C.
ROOT /\ / \ / \ /\ H / \ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ /\ E / \ / \ / \ / \ / \ / \ F A D G B C
Stoga smo maksimalnu dužinu koda ograničili na 4. Jasno je da smo promjenom dužine koda malo izgubili u efikasnosti. Dakle, poruka S, kodirana takvim kodom, imat će veličinu od 92 bita, tj. 3 bita više u poređenju sa minimalno redundantnim kodom.
Jasno je da što više ograničimo maksimalnu dužinu koda, to će kod biti manje efikasan. Hajde da saznamo koliko možemo ograničiti maksimalnu dužinu koda. Očigledno ne kraće.
Računanje kanonskih kodova
Kao što smo više puta napomenuli, dužine kodova su dovoljne za generisanje samih kodova. Hajde da pokažemo kako se to može uraditi. Pretpostavimo da smo već izračunali dužine kodova i izbrojali koliko kodova svake dužine imamo. Neka je L maksimalna dužina koda, a T i broj kodova dužine i.
Izračunajmo S i - početna vrijednost kod dužine i, za sve i od
S L = 0 (uvijek)
S L-1 = (S L + T L) >> 1
S L-2 = (S L-1 + T L-1) >> 1
...
S 1 = 1 (uvijek)
Za naš primjer, L = 5, T 1 .. 5 = (1, 0, 2 , 3, 2).
S 5 = 00000 bin = 0 dec
S 4 = (S 5 =0 + T 5 =2) >> 1 = (00010 bin >> 1) = 0001 bin = 1 dec
S 3 = (S 4 =1 + T 4 =3) >> 1 = (0100 bin >> 1) = 010 bin = 2 dec
S 2 = (S 3 =2 + T 3 =2) >> 1 = (100 bin >> 1) = 10 bin = 2 dec
S 1 = (S 2 =2 + T 2 =0) >> 1 = (10 bin >> 1) = 1 bin = 1 dec
Može se vidjeti da su S 5, S 4, S 3, S 1 upravo znakovni kodovi B, A, C, H. Zajedničko ovim simbolima je da su svi prvi, svaki na svom nivou. Drugim riječima, pronašli smo početnu vrijednost koda za svaku dužinu (ili nivo).
Sada dodijelimo kodove preostalim simbolima. Šifra prvog znaka na nivou i je S i , drugog je S i + 1, trećeg je S i + 2, itd.
Zapišimo preostale kodove za naš primjer:
B= S 5 = 00000 bin | A= S 4 = 0001 bin | C= S 3 = 010 bin | H= S 1 = 1 bin |
F= S 5 + 1 = 00001 bin | D= S 4 + 1 = 0010 bin | E= S 3 + 1 = 011 bin | |
G= S 4 + 2 = 0011 bin |
Može se vidjeti da smo dobili potpuno iste kodove kao da smo eksplicitno izgradili kanonsko Huffmanovo stablo.
Transfer stabla koda
Da bi kodirana poruka bila dekodirana, dekoder mora imati isto stablo koda (u ovom ili onom obliku) koje je korišteno tokom kodiranja. Stoga smo, zajedno sa kodiranim podacima, primorani da sačuvamo odgovarajuće stablo koda. Jasno je da što je kompaktniji, to bolje.
Postoji nekoliko načina za rješavanje ovog problema. Najviše očigledno rešenje- sačuvati stablo u eksplicitnom obliku (tj. kao uređeni skup čvorova i pokazivača jednog ili drugog tipa). Ovo je možda najrasipniji i najneefikasniji način. U praksi se ne koristi.
Možete pohraniti listu frekvencija simbola (tj. frekvencijski rečnik). Uz njegovu pomoć, dekoder može lako rekonstruirati kodno stablo. Iako je ova metoda manje rasipnička od prethodne, nije najbolja.
Konačno, možete koristiti jedno od svojstava kanonskih kodova. Kao što je ranije navedeno, kanonski kodovi su u potpunosti određeni svojim dužinama. Drugim riječima, sve što je dekoderu potrebno je lista dužina znakovnog koda. Uzimajući u obzir da se u prosjeku dužina jednog koda za N-znakovnu abecedu može kodirati u [(log 2 (log 2 N))] bitovima, dobijamo vrlo efikasan algoritam. Zadržat ćemo se na tome detaljnije.
Pretpostavimo da je veličina abecede N=256 i da komprimujemo običan tekstualnu datoteku(ASCII). Najvjerovatnije nećemo pronaći svih N znakova naše abecede u takvoj datoteci. Postavimo zatim dužinu koda znakova koji nedostaju jednaka nuli. U ovom slučaju, sačuvana lista dužina kodova će sadržavati dovoljno veliki broj nule (dužine koda znakova koji nedostaju) grupisane zajedno. Svaka takva grupa može se komprimirati korištenjem takozvanog grupnog kodiranja - RLE (Run - Length - Encoding). Ovaj algoritam je izuzetno jednostavan. Umjesto niza od M identičnih elemenata u nizu, pohranit ćemo prvi element ovog niza i broj njegovih ponavljanja, tj. (M-1). Primjer: RLE("AAABBBBCDDDDDD")=A3 B2 C0 D6.
Štaviše, ova metoda se može donekle proširiti. Možemo se prijaviti RLE algoritam ne samo na grupe nulte dužine, već i na sve ostale. Ova metoda prenošenja kodnog stabla je općenito prihvaćena i koristi se u većini modernih implementacija.
Implementacija: SHCODEC
Dodatak: biografija D. Huffmana
David Huffman rođen je 1925. godine u Ohaju, SAD. Huffman je diplomirao elektrotehniku od državni univerzitet Ohajo (Ohio State University) sa 18 godina. Potom je služio u vojsci kao časnik radarske podrške na razaraču koji je pomagao u čišćenju mina u japanskim i kineskim vodama nakon Drugog svjetskog rata. Nakon toga je magistrirao na Univerzitetu Ohajo i doktorirao na Massachusetts Institute of Technology (MIT). Iako je Hafman najpoznatiji po razvoju metode za konstruisanje minimalno redundantnih kodova, dao je važan doprinos i mnogim drugim poljima (uglavnom elektronici). On dugo vremenaŠef Odsjeka za kompjuterske nauke na MIT-u. 1974. godine, već kao profesor emeritus, dao je ostavku. Huffman je dobio niz vrijednih nagrada. 1999 - Medalja Richarda W. Hamminga sa Instituta inženjera elektrotehnike i elektronike (IEEE) za izuzetan doprinos teoriji informacija, Louis E. Levy medalja sa Franklin instituta za njegovu doktorsku disertaciju o sekvencijalnim sklopnim krugovima, nagrada W. Wallacea McDowella, IEEE Computer Society Award i IEEE Golden Anniversary Technology Innovation Award 1998. U oktobru 1999., u 74. godini, David Huffman je umro od raka. R.L. Milidiu, A.A. Pessoa, E.S. Laber, "Efikasna implementacija algoritma zagrijavanja za konstrukciju prefiksnih kodova s ograničenom dužinom", Proc. ALENEX-a (Međunarodna radionica o algoritamskom inženjerstvu i eksperimentiranju), str. 1-17, Springer, januar. 1999. |
Danas je malo korisnika zainteresovano za pitanje u vezi sa mehanizmom kompresije datoteka. Proces rada sa PC U poređenju sa prošlošću, postalo je mnogo lakše za implementaciju.
Danas gotovo svaki korisnik koji radi sa sistem podataka, koristi arhive. Međutim, malo korisnika je razmišljalo o tome kako se datoteke komprimiraju.
Huffmanovi kodovi su postali prva opcija. Još uvijek se koriste u raznim arhivima. Većina korisnika čak i ne razmišlja o tome koliko je lako komprimirati datoteku pomoću ove sheme. IN ovu recenziju pogledat ćemo kako se vrši kompresija, koje karakteristike pomažu da se ubrza i pojednostavi proces kodiranja. Također ćemo pokušati razumjeti osnovne principe konstruiranja stabla kodiranja.
Algoritam: istorija
Prvi algoritam dizajniran za izvođenje efikasnog kodiranja elektronske informacije, postao je kod koji je predložio Huffman 1952. godine. To je taj kod koji se danas može smatrati osnovni element većina programa dizajniranih za kompresiju informacija. Neki od najpopularnijih izvora koji se koriste ovaj kod, danas su RAR arhive, ARJ, ZIP. Ovaj algoritam se također koristi za kompresiju JPEG slike I grafičkih objekata. Također, svi moderni faksovi koriste algoritam za kodiranje koji je izmišljen davne 1952. godine. Unatoč činjenici da je prošlo dosta vremena od stvaranja ovog koda, on se učinkovito koristi u opremi starog tipa, kao iu novoj opremi i školjkama.
Princip efikasnog kodiranja
Huffmanov algoritam baziran je na shemi koja vam omogućava zamjenu najvjerovatnijih i najčešćih znakova kodovima binarni sistem. Oni znakovi koji se pojavljuju rjeđe zamjenjuju se dugim kodovima. Prijelaz na duge kodove Huffman se izvodi tek nakon što je sistem iskoristio sve minimalne vrijednosti. Ova tehnika omogućava da se minimizira dužina koda po karakteru originalne poruke. IN u ovom slučaju Posebnost je u tome što već moraju biti poznate vjerovatnoće pojave slova na početku kodiranja. Konačna poruka će biti sastavljena od njih. Na osnovu ovih informacija, konstruiše se Huffmanovo kodno stablo. Na osnovu toga će se izvršiti proces kodiranja slova u arhivi.
Huffman kod: primjer
Da biste ilustrirali Huffmanov algoritam, razmotrite grafičku verziju konstrukcije kodnog stabla. Koristiti ovu metodu bio efikasniji, potrebno je razjasniti definiciju nekih vrijednosti koje su neophodne za koncept ove metode. Čitav skup čvorova i lukova koji su usmjereni od čvora do čvora naziva se graf. Samo drvo je graf sa skupom određenih svojstava. Svaki čvor ne smije sadržavati više od jednog od svih lukova. Jedan od čvorova mora biti korijen stabla. To znači da u njemu uopće ne bi trebalo biti uključenih lukova. Ako krenete od korijena kretanja duž lukova, tada bi vam ovaj proces trebao omogućiti da dođete do bilo kojeg čvora.
Huffmanovi kodovi također uključuju koncept lista drveta. Predstavlja čvor iz kojeg ne bi trebao nastati luk. Ako su dva čvora povezana lukom, onda je jedan od njih roditelj, a drugi dijete. Ako dva čvora imaju zajednički roditeljski čvor, oni se nazivaju bratski i sestrinski čvorovi. Ako, osim listova, čvorovi imaju nekoliko lukova, onda se takvo stablo naziva binarnim. To je upravo ono što je Hafmanovo drvo. Posebnost čvorova ove strukture je da je težina svakog roditelja jednaka zbroju težina djece čvora.
Huffmanovo drvo: konstrukcijski algoritam
Konstrukcija Huffmanovog koda se izvodi od slova ulazne abecede. Formira se lista čvorova koji su slobodni u budućem kodnom stablu. Na ovoj listi, težina svakog čvora mora biti ista kao vjerovatnoća pojavljivanja slova poruke koje odgovara ovaj čvor. Među nekoliko slobodnih čvorova odabire se onaj koji ima najmanju težinu. Ako se istovremeno promatraju minimalni pokazatelji u nekoliko čvorova, tada možete slobodno odabrati bilo koji par. Nakon toga, kreira se roditeljski čvor. Trebalo bi da teži isto kao i zbir datog para čvorova. Roditelj se zatim šalje na listu sa slobodnim čvorovima. Djeca se uklanjaju. Lukovi dobijaju odgovarajuće indikatore, nule i jedinice. Ovaj proces se ponavlja onoliko puta koliko je potrebno dok ne ostane samo jedan čvor. Nakon toga, binarne cifre se pišu od vrha do dna.
Kako poboljšati efikasnost kompresije
Da bi se poboljšala efikasnost kompresije, prilikom izgradnje kodnog stabla potrebno je koristiti sve podatke o vjerovatnoći pojavljivanja slova u određenom fajlu koji je priložen stablu. Ne bi trebalo dozvoliti da budu razbacani po velikom broju tekstualnih dokumenata. Ako prvo prođete ovaj fajl, tada možete dobiti statistiku o tome koliko često se slova pojavljuju iz objekta koji se kompresuje.
Kako ubrzati proces kompresije
Da bi se ubrzao rad algoritma, identifikaciju slova treba vršiti ne prema indikatorima pojavljivanja određenih slova, već po učestalosti njihovog pojavljivanja. Zahvaljujući tome, algoritam postaje jednostavniji, a rad s njim značajno se ubrzava. Ovo također omogućava izbjegavanje operacija dijeljenja i plutajućeg zareza. Takođe, kada radite u ovom režimu, algoritam se ne može menjati. Ovo je uglavnom zbog činjenice da su vjerovatnoće direktno proporcionalne frekvencijama. Također je vrijedno obratiti pažnju na činjenicu da će konačna težina korijenskog čvora biti jednaka zbroju broja slova u objektu koji se obrađuje.
Zaključak
Huffmanovi kodovi su jednostavan i dugo razvijen algoritam koji se još uvijek koristi u mnogima popularni programi. Jednostavnost i jasnoća ovog koda vam omogućavaju da to postignete efektivna kompresija datoteke bilo koje veličine.
Relativno jednostavan način kompresije podataka može se postići stvaranjem takozvanih Huffmanovih stabala na datoteci i korištenjem za kompresiju i dekomprimiranje podataka unutar nje. Za većinu aplikacija koriste se binarna Huffmanova stabla (na primjer, svaki čvor je ili list ili ima tačno dva podčvora). Moguće je, međutim, konstruisati Huffmanovo stablo bilo koji broj podstabla (na primjer, ternarna ili, in opšti slučaj, N-arno drveće).
Huffmanovo stablo za datoteku koja sadrži Z različiti likovi Ima Z listovi. Put od korijena do lista koji predstavlja određeni znak određuje kodiranje, a svaki korak duž puta do lista određuje kodiranje (koje može biti 0 , 1 , ..., (N-1)). Postavljanjem znakova koji se često pojavljuju bliže korijenu, a znakova koji se rjeđe pojavljuju dalje od korijena, postiže se željena kompresija. Strogo govoreći, takvo stablo će biti Huffmanovo stablo samo ako kodiranje rezultira korištenjem minimalnog broja N-ari znakovi za kodiranje date datoteke.
U ovom problemu ćemo razmotriti samo stabla gdje je svaki čvor ili interni čvor ili list za kodiranje znakova, i nema izoliranih listova koji ne kodiraju znak.
Slika ispod prikazuje primjer Huffmanovog ternarnog stabla, simboli " a" i " e" su kodirani pomoću jednog ternarnog znaka; znakovi koji se rjeđe pojavljuju " s" i " str" su kodirani pomoću dva ternarna znaka i znakova koji se najčešće pojavljuju " x", "q" i " y" su kodirani korištenjem po tri ternarna znaka.
Naravno, ako želimo možemo proširiti listu N-arnih simbola pa nazad, važno je znati koje stablo se koristi za kompresiju podataka. To se može učiniti na nekoliko načina. U ovom zadatku ćemo koristiti sledeća metoda: Ulaznom toku podataka će prethoditi zaglavlje koje se sastoji od vrijednosti kodiranih znakova Z nalazi se u izvorni fajl po leksikografskom redu.
Poznavanje broja ulaznih znakova Z, značenje N, što znači " N-arity" Huffmanovog stabla i samog zaglavlja, potrebno je pronaći primarno značenje kodiranih znakova.
Ulazni podaci
Ulazni podaci počinju cijelim brojem T, koji se nalazi u posebnom redu i označava broj narednih test slučajeva. Svako od sljedećeg je specificirano T test slučajevima, od kojih se svaki nalazi u 3 -x linije kako slijedi:
- Broj različitih znakova u testnom slučaju Z (2 ≤ Z ≤ 20 );
- Broj N, što znači " N-arnost" Hafmanovog drveta ( 2 ≤ N ≤ 10 );
- Niz koji predstavlja naslov primljene poruke, svaki znak će biti cifra u opsegu . Ovaj red će sadržavati manje 200 karaktera.
Bilješka: Iako rijetko, moguće je da naslov ima višestruka tumačenja kada se dešifruje (na primjer, za naslov " 010011101100 “, i vrijednosti Z=5 I N=2). Garantovano je da u svim test slučajevima predloženim u ulaznim podacima postoji jedinstveno rešenje.
Izlaz
Za svaku od T prikaz test slučajeva Z linije koje daju dekodiranu verziju svakog od njih Z znakova u rastućem redoslijedu. Koristite format original->kodiranje, Gdje original- Ovo decimalni broj u rasponu i odgovarajući kodirani niz kodiranih cifara za te znakove (svaka cifra ≥ 0 I< N).
- kod = sljedeći bit iz toka, dužina = 1
- Ćao kod< base
kod = kod<< 1
kod = kod + sljedeći bit iz toka
dužina = dužina + 1 - simbol = simbol + kod - baza]
Drugim riječima, gurati ćemo bit po bit iz ulaznog toka u varijablu koda slijeva do koda< base. При этом на каждой итерации будем увеличивать переменную length на 1 (т.е. продвигаться вниз по дереву). Цикл в (2) остановится когда мы определим длину кода (уровень в дереве, на котором находится искомый символ). Остается лишь определить какой именно символ на этом уровне нам нужен.
Pretpostavimo da se petlja u (2), nakon nekoliko iteracija, zaustavlja. U ovom slučaju, izraz (kod - baza) je serijski broj željenog čvora (simbola) na nivou dužine. Prvi čvor (simbol) koji se nalazi na nivou dužine u stablu nalazi se u nizu simbola na indeksnim isključenjima. Ali nas ne zanima prvi znak, već numerirani znak (kod - baza). Stoga je indeks traženog simbola u nizu simbola jednak (offs + (code - base)). Drugim riječima, željeni simbol simbol=symb + kod - baza].
Navedimo konkretan primjer. Koristeći opisani algoritam, dekodiramo poruku Z / .
Z / ="0001 1 00001 00000 1 010 011 1 011 1 010 011 0001 1 0010 010 011 011 1 1 1 010 1 1 1 0010 01 01 01 01 01 01 10 1 1"
- kod = 0, dužina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 1 = 1
dužina = 3 + 1 = 4
kod = 1 = baza = 1 - simbol = symb = 2 + kod = 1 - baza = 1] = symb = A
- kod = 1, dužina = 1
- kod = 1 = baza = 1
- simbol = symb = 7 + kod = 1 - baza = 1] = symb = H
- kod = 0, dužina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
dužina = 3 + 1 = 4
kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 1 = 1
dužina = 4 + 1 = 5
kod = 1 > baza = 0 - simbol = symb = 0 + kod = 1 - baza = 0] = symb = F
Dakle, dekodirali smo prva 3 znaka: A, H, F. Jasno je da ćemo slijedeći ovaj algoritam dobiti upravo poruku S.
Proračun dužine koda
Da bismo kodirali poruku, moramo znati kodove znakova i njihove dužine. Kao što je navedeno u prethodnom odeljku, kanonski kodovi su u potpunosti određeni svojim dužinama. Dakle, naš glavni zadatak je izračunavanje dužine kodova.
Ispostavilo se da ovaj zadatak, u velikoj većini slučajeva, ne zahtijeva eksplicitnu konstrukciju Huffmanovog stabla. Štaviše, algoritmi koji koriste internu (ne eksplicitnu) reprezentaciju Hafmanovog stabla su mnogo efikasniji u smislu brzine i troškova memorije.
Danas postoji mnogo efikasnih algoritama za izračunavanje dužine koda (,). Ograničićemo se na razmatranje samo jednog od njih. Ovaj algoritam je prilično jednostavan, ali je unatoč tome vrlo popularan. Koristi se u programima kao što su zip, gzip, pkzip, bzip2 i mnogi drugi.
Vratimo se algoritmu za konstruisanje Hafmanovog stabla. Na svakoj iteraciji, izvršili smo linearnu pretragu za dva čvora s najmanjom težinom. Jasno je da je prioritetni red kao što je piramidalni (minimalni) pogodniji za ovu svrhu. Čvor sa najmanjom težinom imaće najveći prioritet i biće na vrhu piramide. Hajde da predstavimo ovaj algoritam.
Uključimo sve kodirane simbole u piramidu.
Iz piramide ćemo uzastopno izdvojiti 2 čvora (to će biti dva čvora s najmanjom težinom).
Formirajmo novi čvor i priložimo mu, kao djeca, dva čvora uzeta iz piramide. U ovom slučaju postavljamo težinu formiranog čvora jednaku zbroju težina podređenih čvorova.
Uključimo formirani čvor u piramidu.
Ako postoji više od jednog čvora u piramidi, ponovite 2-5.
Pretpostavićemo da je za svaki čvor pohranjen pokazivač na njegov roditelj. U korijenu stabla postavljamo ovaj pokazivač jednak NULL. Odaberimo sada čvor (simbol) lista i, prateći sačuvane pokazivače, penjaćemo se po stablu sve dok sledeći pokazivač ne postane NULL. Poslednji uslov znači da smo stigli do korena drveta. Jasno je da je broj prelazaka sa nivoa na nivo jednak dubini lisnog čvora (simbola), a samim tim i dužini njegovog koda. Prolaskom kroz sve čvorove (simbole) na ovaj način dobijamo dužine njihovih kodova.
Maksimalna dužina koda
U pravilu se kod kodiranja koristi tzv šifrarnik (šifarnik), jednostavna struktura podataka, u suštini dva niza: jedan sa dužinama, drugi sa kodovima. Drugim riječima, kod (kao niz bitova) je pohranjen na memorijskoj lokaciji ili registru fiksne veličine (obično 16, 32 ili 64). Kako bismo spriječili prelijevanje, moramo biti sigurni da će kod stati u registar.
Ispostavilo se da na abecedi N znakova maksimalna veličina koda može biti dužine do (N-1) bita. Drugim riječima, sa N=256 (uobičajena opcija) možemo dobiti kod dug 255 bita (iako za ovo datoteka mora biti veoma velika: 2.292654130570773*10^53~=2^177.259)! Jasno je da se takav kod neće uklopiti u registar i sa njim treba nešto učiniti.
Prvo, hajde da saznamo pod kojim uslovima dolazi do prelivanja. Neka frekvencija i-tog simbola bude jednaka i-tom Fibonačijevom broju. Na primjer: A-1, B-1, C-2, D-3, E-5, F-8, G-13, H-21. Konstruirajmo odgovarajuće Huffmanovo stablo.
ROOT /\ / \ / \ /\ H / \ / \ /\ G / \ / \ /\ F / \ / \ /\ E / \ / \ /\ D / \ / \ /\ C / \ / \ A B
Takvo drvo se zove degenerisati. Da bi ga primili, frekvencije simbola moraju rasti barem onoliko koliko su Fibonačijevi brojevi ili čak i brže. Iako je u praksi, koristeći stvarne podatke, gotovo nemoguće dobiti takvo stablo, vrlo ga je lako generirati umjetno. U svakom slučaju, ova opasnost se mora uzeti u obzir.
Ovaj problem se može riješiti na dva prihvatljiva načina. Prvi od njih se oslanja na jedno od svojstava kanonskih kodova. Činjenica je da u kanonskom kodu (bit string) nijedan bitniji bit ne može biti različit od nule. Drugim riječima, svi ostali bitovi možda uopće neće biti sačuvani, jer uvijek su nula. U slučaju N=256, potrebno je samo pohraniti najmanje značajnih 8 bitova iz svakog koda, pod pretpostavkom da su svi ostali bitovi jednaki nuli. Ovo rješava problem, ali samo djelimično. Ovo će značajno zakomplicirati i usporiti i kodiranje i dekodiranje. Stoga se ova metoda rijetko koristi u praksi.
Drugi metod je da se veštački ograniči dužina kodova (bilo tokom izgradnje ili posle). Ova metoda je općenito prihvaćena, pa ćemo se na njoj detaljnije zadržati.
Postoje dvije vrste algoritama za ograničavanje dužine koda. Heuristički (približan) i optimalan. Algoritmi drugog tipa su prilično složeni za implementaciju i po pravilu zahtijevaju više vremena i memorije od prvog. Efikasnost heuristički ograničenog koda određena je njegovim odstupanjem od optimalno ograničenog koda. Što je ova razlika manja, to bolje. Vrijedi napomenuti da je za neke heurističke algoritme ova razlika vrlo mala ( , , ), i oni vrlo često generiraju optimalan kod (iako ne garantuju da će to uvijek biti slučaj). Štaviše, jer u praksi se prelivanje događa izuzetno rijetko (osim ako ne postoji vrlo striktno ograničenje na maksimalnu dužinu koda); s malom veličinom alfabeta preporučljivije je koristiti jednostavne i brze heurističke metode.
Pogledat ćemo jedan prilično jednostavan i vrlo popularan heuristički algoritam. Našao je svoju primjenu u programima kao što su zip, gzip, pkzip, bzip2 i mnogim drugim.
Problem ograničavanja maksimalne dužine koda je ekvivalentan problemu ograničavanja visine Hafmanovog stabla. Imajte na umu da po konstrukciji svaki nelisni čvor u Huffmanovu stablu ima tačno dva djeteta. Pri svakoj iteraciji našeg algoritma smanjit ćemo visinu stabla za 1. Dakle, neka je L maksimalna dužina koda (visina stabla) i želimo je ograničiti na L / < L. Neka je dalje RN i krajnji desni listni čvor na nivou i, a LN i je krajnji lijevi.
Počnimo sa radom od nivoa L. Pomerimo čvor RN L na mesto njegovog roditelja. Jer čvorovi idu u parovima; moramo pronaći mjesto za čvor pored RN L. Da bismo to uradili, nalazimo nivo j najbliži L, koji sadrži čvorove lista, tako da je j < (L-1). Umjesto LN j, formiraćemo nelisni čvor i priložiti mu kao podređeni čvor LN j i preostali neupareni čvor sa nivoa L. Primijeniti istu operaciju na sve preostale parove čvorova na nivou L. Jasno je da smo preraspodjelom čvorova na ovaj način smanjili visinu našeg stabla za 1. Sada je jednako (L-1). Ako sada L / < (L-1), onda ćemo isto učiniti sa nivoom (L-1) itd. dok se ne dostigne traženi limit.
Vratimo se na naš primjer, gdje je L=5. Ograničimo maksimalnu dužinu koda na L / =4.
ROOT /\ / \ / \ /\ H C E / \ / \ / \ / \ /\ A D G / \ / \ B F
Može se vidjeti da je u našem slučaju RN L = F, j=3, LN j = C. Prvo pomjerimo čvor RN L = F na mestu svog roditelja.
ROOT /\ / \ / \ /\ H / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ C E / \ / \ / \ / \ F A D G B(nespareni čvor)
Sada na mjestu LN j = C Formiramo čvor bez lista.
ROOT /\ / \ / \ /\ H E / \ / \ / \ / \ / \ / \ F A D G ? ? B(nespareni čvor) C(nespareni čvor)
Pričvrstimo dva neuparena na formirani čvor: B I C.
ROOT /\ / \ / \ /\ H / \ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ /\ E / \ / \ / \ / \ / \ / \ F A D G B C
Stoga smo maksimalnu dužinu koda ograničili na 4. Jasno je da smo promjenom dužine koda malo izgubili u efikasnosti. Dakle, poruka S, kodirana takvim kodom, imat će veličinu od 92 bita, tj. 3 bita više u poređenju sa minimalno redundantnim kodom.
Jasno je da što više ograničimo maksimalnu dužinu koda, to će kod biti manje efikasan. Hajde da saznamo koliko možemo ograničiti maksimalnu dužinu koda. Očigledno ne kraće.
Računanje kanonskih kodova
Kao što smo više puta napomenuli, dužine kodova su dovoljne za generisanje samih kodova. Hajde da pokažemo kako se to može uraditi. Pretpostavimo da smo već izračunali dužine kodova i izbrojali koliko kodova svake dužine imamo. Neka je L maksimalna dužina koda, a T i broj kodova dužine i.
Izračunajmo S i - početnu vrijednost koda dužine i, za sve i iz
S L = 0 (uvijek)
S L-1 = (S L + T L) >> 1
S L-2 = (S L-1 + T L-1) >> 1
...
S 1 = 1 (uvijek)
Za naš primjer, L = 5, T 1 .. 5 = (1, 0, 2 , 3, 2).
S 5 = 00000 bin = 0 dec
S 4 = (S 5 =0 + T 5 =2) >> 1 = (00010 bin >> 1) = 0001 bin = 1 dec
S 3 = (S 4 =1 + T 4 =3) >> 1 = (0100 bin >> 1) = 010 bin = 2 dec
S 2 = (S 3 =2 + T 3 =2) >> 1 = (100 bin >> 1) = 10 bin = 2 dec
S 1 = (S 2 =2 + T 2 =0) >> 1 = (10 bin >> 1) = 1 bin = 1 dec
Može se vidjeti da su S 5, S 4, S 3, S 1 upravo znakovni kodovi B, A, C, H. Zajedničko ovim simbolima je da su svi prvi, svaki na svom nivou. Drugim riječima, pronašli smo početnu vrijednost koda za svaku dužinu (ili nivo).
Sada dodijelimo kodove preostalim simbolima. Šifra prvog znaka na nivou i je S i , drugog je S i + 1, trećeg je S i + 2, itd.
Zapišimo preostale kodove za naš primjer:
B= S 5 = 00000 bin | A= S 4 = 0001 bin | C= S 3 = 010 bin | H= S 1 = 1 bin |
F= S 5 + 1 = 00001 bin | D= S 4 + 1 = 0010 bin | E= S 3 + 1 = 011 bin | |
G= S 4 + 2 = 0011 bin |
Može se vidjeti da smo dobili potpuno iste kodove kao da smo eksplicitno izgradili kanonsko Huffmanovo stablo.
Transfer stabla koda
Da bi kodirana poruka bila dekodirana, dekoder mora imati isto stablo koda (u ovom ili onom obliku) koje je korišteno tokom kodiranja. Stoga smo, zajedno sa kodiranim podacima, primorani da sačuvamo odgovarajuće stablo koda. Jasno je da što je kompaktniji, to bolje.
Postoji nekoliko načina za rješavanje ovog problema. Najočiglednije rješenje je eksplicitno pohraniti stablo (tj. kao uređeni skup čvorova i pokazivača ove ili one vrste). Ovo je možda najrasipniji i najneefikasniji način. U praksi se ne koristi.
Možete pohraniti listu frekvencija simbola (tj. frekvencijski rečnik). Uz njegovu pomoć, dekoder može lako rekonstruirati kodno stablo. Iako je ova metoda manje rasipnička od prethodne, nije najbolja.
Konačno, možete koristiti jedno od svojstava kanonskih kodova. Kao što je ranije navedeno, kanonski kodovi su u potpunosti određeni svojim dužinama. Drugim riječima, sve što je dekoderu potrebno je lista dužina znakovnog koda. Uzimajući u obzir da se u prosjeku dužina jednog koda za N-znakovnu abecedu može kodirati u [(log 2 (log 2 N))] bitovima, dobijamo vrlo efikasan algoritam. Zadržat ćemo se na tome detaljnije.
Pretpostavimo da je veličina abecede N=256 i da komprimujemo običan tekstualni fajl (ASCII). Najvjerovatnije nećemo pronaći svih N znakova naše abecede u takvoj datoteci. Postavimo zatim dužinu koda znakova koji nedostaju jednaku nuli. U ovom slučaju, pohranjena lista dužina kodova će sadržavati prilično veliki broj nula (dužine kodova znakova koji nedostaju) grupisanih zajedno. Svaka takva grupa može se komprimirati korištenjem takozvanog grupnog kodiranja - RLE (Run - Length - Encoding). Ovaj algoritam je izuzetno jednostavan. Umjesto niza od M identičnih elemenata u nizu, pohranit ćemo prvi element ovog niza i broj njegovih ponavljanja, tj. (M-1). Primjer: RLE("AAABBBBCDDDDDD")=A3 B2 C0 D6.
Štaviše, ova metoda se može donekle proširiti. RLE algoritam možemo primijeniti ne samo na grupe nulte dužine, već i na sve ostale. Ova metoda prenošenja kodnog stabla je općenito prihvaćena i koristi se u većini modernih implementacija.
Implementacija: SHCODEC
Dodatak: biografija D. Huffmana
David Huffman rođen je 1925. godine u Ohaju, SAD. Huffman je sa 18 godina diplomirao elektrotehniku na Državnom univerzitetu u Ohaju. Potom je služio u vojsci kao časnik radarske podrške na razaraču koji je pomagao u čišćenju mina u japanskim i kineskim vodama nakon Drugog svjetskog rata. Nakon toga je magistrirao na Univerzitetu Ohajo i doktorirao na Massachusetts Institute of Technology (MIT). Iako je Hafman najpoznatiji po razvoju metode za konstruisanje minimalno redundantnih kodova, dao je važan doprinos i mnogim drugim poljima (uglavnom elektronici). Bio je dugogodišnji predsjedavajući Odsjeka za kompjuterske nauke na MIT-u. 1974. godine, već kao profesor emeritus, dao je ostavku. Huffman je dobio niz vrijednih nagrada. 1999 - Medalja Richarda W. Hamminga sa Instituta inženjera elektrotehnike i elektronike (IEEE) za izuzetan doprinos teoriji informacija, Louis E. Levy medalja sa Franklin instituta za njegovu doktorsku disertaciju o sekvencijalnim sklopnim krugovima, nagrada W. Wallacea McDowella, IEEE Computer Society Award i IEEE Golden Anniversary Technology Innovation Award 1998. U oktobru 1999., u 74. godini, David Huffman je umro od raka. |