- kod = sljedeći bit iz toka, duljina = 1
- Dok kod< base
kod = kod<< 1
kod = kod + sljedeći bit iz toka
duljina = duljina + 1 - simbol = simbol + kod - baza]
Drugim riječima, gurat ćemo s lijeve strane u varijablu koda bit po bit od ulaznog toka, do koda< base. При этом на каждой итерации будем увеличивать переменную length на 1 (т.е. продвигаться вниз по дереву). Цикл в (2) остановится когда мы определим длину кода (уровень в дереве, на котором находится искомый символ). Остается лишь определить какой именно символ на этом уровне нам нужен.
Pretpostavimo da se petlja u (2), nakon nekoliko iteracija, zaustavlja. U ovom slučaju izraz (kod - baza) je redni broj traženog čvora (znaka) na razini duljine. Prvi čvor (simbol) na razini duljine u stablu nalazi se u nizu simbola na indeksu isključenja. No, ne zanima nas prvi znak, već znak ispod broja (šifra - baza). Stoga je indeks željenog simbola u nizu simbola (offs + (code - base)). Drugim riječima, traženi simbol je simbol = simbol + 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 11 0 1 0 1 0 1 0 1
- kod = 0, duljina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 1 = 1
duljina = 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, duljina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 3 + 1 = 4
kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 1 = 1
duljina = 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.
Izračunavanje duljine koda
Da bismo kodirali poruku, moramo znati kodove znakova i njihove duljine. Kao što je navedeno u prethodnom odjeljku, kanonski kodovi dobro su definirani svojim duljinama. Stoga je naš glavni zadatak izračunati duljine kodova.
Ispada da ovaj zadatak, u ogromnoj većini slučajeva, ne zahtijeva eksplicitnu konstrukciju Huffmanovog stabla. Štoviše, algoritmi koji koriste interni (implicitni) prikaz Huffmanovog stabla mnogo su učinkovitiji u smislu brzine i potrošnje memorije.
Danas postoji mnogo učinkovitih algoritama za izračun duljine kodova (,). Ograničit ćemo se na razmatranje samo jednog od njih. Ovaj algoritam je prilično jednostavan, ali unatoč tome vrlo je popularan. Koristi se u programima kao što su zip, gzip, pkzip, bzip2 i mnogi drugi.
Vratimo se algoritmu konstrukcije Huffmanovog stabla. Na svakoj iteraciji izvršili smo linearnu pretragu za dva čvora s najmanjom težinom. Jasno je da je prioritetni red poput piramide (minimum) prikladniji za ovu svrhu. Čvor s najmanjom težinom imat će najveći prioritet i bit će na vrhu piramide. Dajemo ovaj algoritam.
Uključimo sve kodirane znakove u piramidu.
Izdvojmo uzastopno 2 čvora iz piramide (to će biti dva čvora s najmanjom težinom).
Formirajmo se novi čvor i pričvrstite na njega, kao djeca, dva čvora uzeta iz piramide. U ovom slučaju, težina formiranog čvora je postavljena jednaka zbroju težina podređenih čvorova.
Uključimo formirani čvor u piramidu.
Ako u piramidi ima više čvorova, ponovite 2-5.
Pretpostavit ćemo da je pointer na njegov roditelj pohranjen za svaki čvor. Postavljamo ovaj pokazivač jednak NULL u korijenu stabla. Sada odaberimo lisni čvor (simbol) i slijedeći spremljene pokazivače popeti ćemo se na stablo sve dok sljedeći pokazivač ne postane NULL. Posljednji uvjet znači da smo došli do korijena stabla. Jasno je da je broj prijelaza s razine na razinu jednak dubini lisnog čvora (simbola), a time i duljini njegovog koda. Zaobilazeći na ovaj način sve čvorove (simbole), dobivamo duljine njihovih kodova.
Maksimalna duljina koda
U pravilu, tzv šifrarnik, jednostavna struktura podataka, u biti dva niza: jedan s duljinama, drugi s kodovima. Drugim riječima, kod (kao bitni niz) je pohranjen na memorijskoj lokaciji ili registru fiksne veličine (obično 16, 32 ili 64). Kako bismo izbjegli prelijevanje, moramo biti sigurni da će kod stati u registar.
Ispada da na abecedi N znakova maksimalna veličina koda može biti duljine do (N-1) bita. Drugim riječima, za N = 256 (uobičajena varijanta) možemo dobiti kod od 255 bita (iako za to datoteka mora biti vrlo velika: 2,292654130570773 * 10 ^ 53 ~ = 2 ^ 177,259)! Jasno je da takva šifra neće stati u registar i s njom morate nešto poduzeti.
Prvo, otkrijmo pod kojim uvjetima dolazi do preljeva. Neka je frekvencija i-tog simbola jednaka i-tom Fibonaccijevom broju. Na primjer: A-1, B-1, C-2, D-3, E-5, F-8, G-13, H-21. Konstruirajmo odgovarajuće Huffmanovo stablo.
KORIJEN / \ / \ / \ / \ H / \ / \ /\ G / \ / \ /\ F / \ / \ /\ E / \ / \ /\ D / \ / \ /\ C / \ / \ A B
Takvo stablo se zove degenerirati... Da bi ga dobili, frekvencije simbola moraju rasti barem kao Fibonaccijevi brojevi ili čak brže. Iako je u praksi, na stvarnim podacima, takvo stablo gotovo nemoguće dobiti, vrlo ga je lako generirati umjetno. U svakom slučaju, tu opasnost treba uzeti u obzir.
Ovaj se problem može riješiti na dva prihvatljiva načina. Prvi se temelji na jednom od svojstava kanonskih kodova. Poanta je u tome da u kanoničkom kodu (bit string) najmanje bitni bitovi mogu biti različiti od nule. Drugim riječima, svi ostali bitovi možda uopće neće biti spremljeni, budući da uvijek su nula. U slučaju N = 256, dovoljno nam je da iz svakog koda spremimo samo najmanje značajnih 8 bitova, uz pretpostavku da su svi ostali bitovi jednaki nuli. Ovo rješava problem, ali samo djelomično. To će uvelike zakomplicirati i usporiti i kodiranje i dekodiranje. Stoga se ova metoda rijetko koristi u praksi.
Drugi način je umjetno ograničavanje duljine kodova (bilo tijekom izgradnje ili poslije). Ova metoda je općeprihvaćena, pa ćemo se na njoj detaljnije zadržati.
Postoje dvije vrste algoritama koda za ograničavanje duljine. Heuristički (približan) i optimalan. Algoritmi drugog tipa prilično su složeni u implementaciji i u pravilu zahtijevaju više vremena i memorije od prvih. Učinkovitost heuristički ograničenog koda određena je njegovim odstupanjem od optimalno ograničenog koda. Što je razlika manja, to bolje. Vrijedi napomenuti da je za neke heurističke algoritme ta razlika vrlo mala (,,), štoviše, oni vrlo često generiraju optimalan kod (iako ne jamče da će to uvijek biti tako). Štoviše, budući da u praksi se prelijevanje događa iznimno rijetko (osim ako nije postavljeno vrlo strogo ograničenje maksimalne duljine koda); s malom veličinom abecede, svrsishodnije je koristiti jednostavne i brze heurističke metode.
Razmotrit ćemo jedan prilično jednostavan i vrlo popularan heuristički algoritam. Pronašao je put u programe kao što su zip, gzip, pkzip, bzip2 i mnogi drugi.
Problem ograničavanja maksimalne duljine koda je ekvivalentan problemu ograničavanja visine Huffmanovog stabla. Imajte na umu da, po konstrukciji, svaki nelisni čvor Huffmanovog stabla ima točno dva potomka. Pri svakoj iteraciji našeg algoritma smanjit ćemo visinu stabla za 1. Dakle, neka je L maksimalna duljina koda (visina stabla) i potrebno ju je ograničiti na L / & lt L. Neka je daljnji RN i krajnji desni lisni čvor na razini i, a LN i - krajnji lijevi.
Počnimo od razine L. Pomaknite RN L čvor na mjesto njegovog roditelja. Jer čvorovi idu u paru, moramo pronaći mjesto za čvor uz RN L. Da biste to učinili, pronađite razinu j najbližu L, koja sadrži čvorove lista, tako da je j & lt (L-1). Umjesto LN j formiramo čvor koji nije u obliku lista i kao podrijetli mu pripajamo čvor LN j i nespareni čvor s razine L. Istu operaciju primjenjujemo na sve preostale parove čvorova na razini 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 / & lt (L-1), onda ćemo isto učiniti s razinom (L-1) itd. dok se ne dosegne traženo ograničenje.
Vratimo se na naš primjer, gdje je L = 5. Ograničimo maksimalnu duljinu koda na L / = 4.
KORIJEN / \ / \ / \ / \ H C E / \ / \ / \ / \ /\ A D G / \ / \ B F
Vidi se da je u našem slučaju RN L = F, j = 3, LN j = C... Prvo pomaknite čvor RN L = F umjesto svog roditelja.
KORIJEN / \ / \ / \ / \ H / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ C E / \ / \ / \ / \ F A D G B(nespareni čvor)
Sada na mjestu LN j = C Formiramo nelisni čvor.
KORIJEN / \ / \ / \ / \ H E / \ / \ / \ / \ / \ / \ F A D G ? ? B(nespareni čvor) C(nespareni čvor)
Priložimo dva nesparena na formirani čvor: B i C.
KORIJEN / \ / \ / \ / \ H / \ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ /\ E / \ / \ / \ / \ / \ / \ F A D G B C
Stoga smo maksimalnu duljinu koda ograničili na 4. Jasno je da smo promjenom duljine koda malo izgubili na učinkovitosti. Dakle, poruka S, kodirana takvim kodom, bit će veličine 92 bita, t.j. Još 3 bita iznad minimalne redundance.
Jasno je da što više ograničimo maksimalnu duljinu koda, to će kod biti manje učinkovit. Hajde da saznamo koliko možete ograničiti maksimalnu duljinu koda. Očito ne kraće od malo.
Izračun kanonskih kodova
Kao što smo već mnogo puta primijetili, duljine kodova dovoljne su za generiranje samih kodova. Pokazat ćemo vam kako se to može učiniti. Pretpostavimo da smo već izračunali duljine kodova i izbrojali koliko kodova svake duljine imamo. Neka je L maksimalna duljina koda, a T i broj kodova duljine i.
Računamo S i - početna vrijednost kod duljine 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... Ove simbole ujedinjuje činjenica da su svi na prvom mjestu, svaki na svojoj razini. Drugim riječima, pronašli smo početnu vrijednost koda za svaku duljinu (ili razinu).
Sada dodijelimo kodove ostalim simbolima. Šifra prvog znaka na razini i je S i, drugog S i + 1, trećeg S i + 2, itd.
Napiš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 koš |
F= S 5 + 1 = 00001 bin | D= S 4 + 1 = 0010 bin | E= S 3 + 1 = 011 bin | |
G= S 4 + 2 = 0011 bin |
Vidi se da smo dobili potpuno iste kodove kao da smo eksplicitno izgradili kanonsko Huffmanovo stablo.
Prenošenje kodnog stabla
Da bi se kodirana poruka mogla dekodirati, dekoder mora imati isto stablo koda (u ovom ili onom obliku) koje je korišteno za kodiranje. Stoga smo zajedno s kodiranim podacima prisiljeni spremiti odgovarajuće stablo koda. Jasno je da što je kompaktniji, to bolje.
Postoji nekoliko načina za rješavanje ovog problema. Najviše očito rješenje- eksplicitno spremite stablo (tj. kao uređeni skup čvorova i pokazivača ove ili one vrste). Ovo je možda najrasipniji i najneučinkovitiji način. U praksi se ne koristi.
Popis frekvencija simbola (tj. frekvencijski rječnik) može se spremiti. Uz njegovu pomoć, dekoder može lako rekonstruirati kodno stablo. Iako je ova metoda manje rasipna od prethodne, nije najbolja.
Konačno, može se koristiti jedno od svojstava kanonskih kodova. Kao što je ranije navedeno, kanonski kodovi su potpuno određeni svojim duljinama. Drugim riječima, sve što dekoder treba je popis duljina znakovnog koda. Uzimajući u obzir da se u prosjeku duljina jednog koda za abecedu N znakova može kodirati u [(log 2 (log 2 N))] bitovima, dobivamo vrlo učinkovit algoritam. Zadržat ćemo se na tome detaljnije.
Pretpostavimo da je veličina abecede N = 256, a mi komprimiramo običnu tekstualnu datoteku(ASCII). Najvjerojatnije u takvoj datoteci nećemo pronaći svih N znakova naše abecede. Zatim stavljamo duljinu koda znakova koji nedostaju jednaka nuli... U tom slučaju će spremljeni popis duljina kodova sadržavati dovoljno veliki broj nule (duljine znakovnih kodova koji nedostaju) grupirane zajedno. Svaka takva grupa može se komprimirati pomoću tzv. grupnog kodiranja - RLE (Run - Length - Encoding). Ovaj algoritam je izuzetno jednostavan. Umjesto niza od M identičnih elemenata u nizu, spremit ćemo prvi element ovog niza i broj njegovih ponavljanja, t.j. (M-1). Primjer: RLE ("AAABBBCDDDDDDD") = A3 B2 C0 D6.
Štoviše, ova se metoda može donekle proširiti. Možemo se prijaviti RLE algoritam ne samo na skupine nulte duljine, već na sve ostale. Ovaj način prosljeđivanja kodnog stabla je uobičajen i koristi se u većini modernih implementacija.
Implementacija: SHCODEC
Dodatak: biografija D. Huffmana
![]() |
David Huffman rođen je 1925. godine u Ohiju, SAD. Huffman je diplomirao elektrotehniku od državno sveučilište Ohio (Ohio State University) u dobi od 18 godina. Potom je služio u vojsci kao časnik radarske potpore na razaraču koji je pomogao u deaktiviranju mina u japanskim i kineskim vodama nakon Drugog svjetskog rata. Nakon toga je magistrirao na Sveučilištu Ohio i doktorirao na Massachusetts Institute of Technology (MIT). Iako je Huffman najpoznatiji po razvoju metode za konstruiranje minimalno redundantnih kodova, također je dao važan doprinos mnogim drugim poljima (uglavnom u elektronici). On dugo vremena vodio je Odjel za informatiku na MIT-u. Godine 1974. već kao profesor emeritus dao je ostavku. Huffman je dobio niz vrijednih nagrada. 1999. - Medalja Richarda W. Hamminga s Instituta inženjera elektrotehnike i elektronike (IEEE) za izniman doprinos teoriji informacija, medalja Louisa E. Levyja s Instituta Franklin za njegovu doktorsku tezu o sekvencijalnim krugovima, nagrada W. Wallacea McDowella, IEEE Computer Nagrada društva, IEEE Gold Jubilee Technology Innovation Award 1998. U listopadu 1999., u dobi od 74 godine, David Huffman je umro od raka. R.L. Milidiu, A.A. Pessoa, E.S. Laber, "Učinkovita implementacija algoritma zagrijavanja za konstrukciju prefiksnih kodova s ograničenom duljinom", Proc. ALENEX-a (Međunarodna radionica o algoritamskom inženjerstvu i eksperimentiranju), pp. 1-17, Springer, siječanj. 1999. |
Danas je malo korisnika zainteresirano za pitanje vezano uz mehanizam kompresije datoteka. Proces rada sa osobno računalo u usporedbi s prošlošću, postalo je mnogo lakše implementirati.
Danas gotovo svaki korisnik koji radi s sustav datoteka koristi arhive. Međutim, malo je korisnika razmišljalo o tome kako se datoteke komprimiraju.
Huffmanovi kodovi bili su prva opcija. Još uvijek se koriste u raznim arhivima. Većina korisnika niti ne razmišlja o tome kako je jednostavno komprimirati datoteku pomoću ove sheme. V ovu recenziju pogledat ćemo kako se kompresija provodi, koje značajke pomažu ubrzati i pojednostaviti proces kodiranja. Također ćemo pokušati razumjeti osnovne principe izgradnje stabla kodiranja.
Algoritam: povijest
Prvi algoritam dizajniran za provođenje učinkovitog kodiranja elektroničke informacije, postao je kod koji je predložio Huffman 1952. godine. To je taj kod koji se danas može razmatrati osnovni element većina programa dizajniranih za komprimiranje informacija. Neki od popularnijih izvora koji koriste dati kod, danas su RAR arhive, ARJ, ZIP. Ovaj algoritam se također koristi za kompresiju JPEG slike i grafički objekti... Također, svi moderni faksovi koriste algoritam kodiranja koji je izmišljen davne 1952. godine. Unatoč činjenici da je prošlo puno vremena od stvaranja ovog koda, on se učinkovito koristi u opremi starog tipa, kao iu novoj opremi i školjkama.
Načelo učinkovitog kodiranja
U srcu Huffmanovog algoritma je shema koja vam omogućuje zamjenu najvjerojatnijih i najčešćih znakova kodovima binarni sustav... Oni znakovi koji su rjeđi zamjenjuju se dugim kodovima. Prijelaz na dugi kodovi Huffman se provodi tek nakon što sustav koristi sve minimalne vrijednosti. Ova tehnika omogućuje minimiziranje duljine koda po znaku izvorne poruke. V u ovom slučaju posebnost je u tome što se već moraju znati vjerojatnosti pojave slova na početku kodiranja. Konačna poruka bit će sastavljena od njih. Na temelju ovih informacija konstruira se Huffmanovo stablo kodiranja. Na temelju toga će se provesti proces kodiranja slova u arhivi.
Huffmanov kod: primjer
Kako biste ilustrirali Huffmanov algoritam, razmotrite grafičku verziju izgradnje kodnog stabla. Koristiti ovu metodu bila učinkovitija, potrebno je pojasniti definiciju nekih od vrijednosti koje su neophodne za koncept ove metode. Cijela zbirka skupa čvorova i lukova koji su usmjereni od čvora do čvora naziva se graf. Samo stablo je graf sa skupom specifičnih svojstava. Svaki čvor ne smije uključivati više od jednog od svih lukova. Jedan od čvorova mora biti korijen stabla. To znači da lukovi uopće ne bi trebali ulaziti u njega. 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 takav koncept kao što je list drveta. Predstavlja čvor iz kojeg ne smije izlaziti luk. Ako su dva čvora povezana lukom, onda je jedan od njih roditelj, a drugi dijete. Ako dva čvora imaju zajednički roditeljski čvor, onda se nazivaju bratski i sestrinski čvorovi. Ako uz lišće čvorovi imaju nekoliko lukova, tada se takvo stablo naziva binarnim. To je upravo ono što je Huffmanovo stablo. Značajka čvorova ove strukture je da je težina svakog roditelja jednaka zbroju težine djece čvorova.
Huffmanovo stablo: konstrukcijski algoritam
Konstrukcija Huffmanovog koda izvodi se od slova ulazne abecede. Formira se popis čvorova koji su slobodni u budućem kodnom stablu. Na ovom popisu težina svakog čvora treba biti jednaka vjerojatnosti pojavljivanja slova poruke koje odgovara ovaj čvor... Među nekoliko slobodnih čvorova odabire se onaj koji ima najmanju težinu. Ako se istodobno promatraju minimalni pokazatelji u nekoliko čvorova, tada možete slobodno odabrati bilo koji par. Nakon toga se kreira roditeljski čvor. Trebao bi težiti onoliko koliko teži zbroj zadanog para čvorova. Roditelj se zatim šalje na popis sa slobodnim čvorovima. Djeca se uklanjaju. U tom slučaju lukovi dobivaju odgovarajuće indikatore, nule i jedinice. Ovaj proces ponavlja onoliko puta koliko je potrebno da se ostavi samo jedan čvor. Nakon toga se binarne znamenke zapisuju od vrha prema dolje.
Kako poboljšati učinkovitost kompresije
Kako bi se povećala učinkovitost kompresije, prilikom izrade kodnog stabla potrebno je koristiti sve podatke o vjerojatnosti pojave slova u pojedinoj datoteci koja je priložena stablu. Ne smije se dopustiti da budu razbacani po velikom broju tekstualnih dokumenata. Ako hodate kroz ovu datoteku, tada možete dobiti statistiku o tome koliko se često slova nalaze u objektu koji treba komprimirati.
Kako ubrzati proces kompresije
Da bi se ubrzao rad algoritma, identifikaciju slova treba provoditi ne pokazateljima izgleda određenih slova, već učestalošću njihovog pojavljivanja. Zahvaljujući tome, algoritam postaje jednostavniji, a rad s njim značajno se ubrzava. Također omogućuje izbjegavanje operacija dijeljenja i s pomičnim zarezom. Također, kada radite u ovom načinu rada, algoritam se ne može promijeniti. To je uglavnom zbog činjenice da su vjerojatnosti izravno proporcionalne frekvencijama. Također je vrijedno obratiti pozornost 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 uspostavljen algoritam koji se i danas koristi u mnogima popularni programi... Jednostavnost i jasnoća ovog koda omogućuje vam postizanje učinkovita kompresija datoteke bilo koje veličine.
Relativno jednostavna metoda sažimanja podataka može se postići stvaranjem takozvanih Huffmanovih stabala za datoteku koja se koriste za njihovo komprimiranje i dekomprimiranje podataka u njoj. Većina aplikacija koristi binarna Huffmanova stabla (na primjer, svaki čvor je ili list ili ima točno dva podčvora). Međutim, moguće je konstruirati Huffmanova stabla proizvoljan broj podstabla (na primjer, ternarna ili, in opći slučaj, N-kao drveće).
Huffmanovo stablo za datoteku koja sadrži Z različiti likovi Ima Z lišće. 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 uobičajenih znakova bliže korijenu, a manje uobičajenih znakova dalje od korijena, postiže se željena kompresija. Strogo govoreći, takvo će stablo biti Huffmanovo stablo samo ako, kao rezultat kodiranja, minimalni broj N-arnih znakova za kodiranje navedene datoteke.
U ovom problemu razmatrat ćemo samo stabla, gdje je svaki čvor ili unutarnji č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"kodirano s jednim ternarnim znakom; manje uobičajeni znakovi" s"i" str"kodirani su pomoću dva ternarna znaka i najrjeđih znakova" x", "q"i" y"kodirani su korištenjem po tri ternarna znaka.
Naravno, ako želimo proširiti popis N-ary znakove pa natrag, važno je znati koje se stablo koristi za komprimiranje podataka. To se može učiniti na nekoliko načina. U ovom zadatku koristit ćemo se sljedeća metoda: toku ulaznih podataka prethodit će zaglavlje koje se sastoji od vrijednosti kodiranih znakova Z nalazi se u izvorna datoteka leksikografskim redom.
Poznavanje broja ulaznih znakova Z, značenje N označavajući " N-arity" Huffmanovog stabla i samog zaglavlja, potrebno je pronaći primarnu vrijednost kodiranih znakova.
Ulazni podaci
Ulazni podaci počinju cijelim brojem T, koji se nalazi u zasebnom retku i označava broj sljedećih testnih slučajeva. Zatim, svaki od T test slučajevima, od kojih se svaki nalazi u 3 -ti redovi kako slijedi:
- Broj različitih znakova u testnom slučaju Z (2 ≤ Z ≤ 20 );
- Broj N označavajući " N-arnost "Huffmanovog stabla ( 2 ≤ N ≤ 10 );
- Niz koji predstavlja zaglavlje primljene poruke, svaki znak će biti znamenka u rasponu ... Ovaj red će sadržavati manje 200 likovima.
Bilješka: Iako rijetko, moguće je da zaglavlje ima više tumačenja prilikom dekodiranja (na primjer, za zaglavlje " 010011101100 “, i vrijednosti Z = 5 i N = 2). Zajamčeno je da u svim testnim slučajevima predloženim u ulaznim podacima postoji jedinstveno rješenje.
Izlaz
Za svaku od T izlaz testnih slučajeva Z linije koje daju dekodiranu verziju svakog od Z znakova u rastućem redoslijedu. Koristite format original-> kodiranje, gdje izvornik- to decimalni broj u rasponu i odgovarajući niz kodiranih znamenki za te znakove (svaka znamenka ≥ 0 i< N).
- kod = sljedeći bit iz toka, duljina = 1
- Dok kod< base
kod = kod<< 1
kod = kod + sljedeći bit iz toka
duljina = duljina + 1 - simbol = simbol + kod - baza]
Drugim riječima, gurat ćemo s lijeve strane u varijablu koda bit po bit od ulaznog toka, do koda< base. При этом на каждой итерации будем увеличивать переменную length на 1 (т.е. продвигаться вниз по дереву). Цикл в (2) остановится когда мы определим длину кода (уровень в дереве, на котором находится искомый символ). Остается лишь определить какой именно символ на этом уровне нам нужен.
Pretpostavimo da se petlja u (2), nakon nekoliko iteracija, zaustavlja. U ovom slučaju izraz (kod - baza) je redni broj traženog čvora (znaka) na razini duljine. Prvi čvor (simbol) na razini duljine u stablu nalazi se u nizu simbola na indeksu isključenja. No, ne zanima nas prvi znak, već znak ispod broja (šifra - baza). Stoga je indeks željenog simbola u nizu simbola (offs + (code - base)). Drugim riječima, traženi simbol je simbol = simbol + 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 11 0 1 0 1 0 1 0 1
- kod = 0, duljina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 1 = 1
duljina = 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, duljina = 1
- kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 1 + 1 = 2
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 2 + 1 = 3
kod = 0< base = 2
kod = 0<< 1 = 0
kod = 0 + 0 = 0
duljina = 3 + 1 = 4
kod = 0< base = 1
kod = 0<< 1 = 0
kod = 0 + 1 = 1
duljina = 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.
Izračunavanje duljine koda
Da bismo kodirali poruku, moramo znati kodove znakova i njihove duljine. Kao što je navedeno u prethodnom odjeljku, kanonski kodovi dobro su definirani svojim duljinama. Stoga je naš glavni zadatak izračunati duljine kodova.
Ispada da ovaj zadatak, u ogromnoj većini slučajeva, ne zahtijeva eksplicitnu konstrukciju Huffmanovog stabla. Štoviše, algoritmi koji koriste interni (implicitni) prikaz Huffmanovog stabla mnogo su učinkovitiji u smislu brzine i potrošnje memorije.
Danas postoji mnogo učinkovitih algoritama za izračun duljine kodova (,). Ograničit ćemo se na razmatranje samo jednog od njih. Ovaj algoritam je prilično jednostavan, ali unatoč tome vrlo je popularan. Koristi se u programima kao što su zip, gzip, pkzip, bzip2 i mnogi drugi.
Vratimo se algoritmu konstrukcije Huffmanovog stabla. Na svakoj iteraciji izvršili smo linearnu pretragu za dva čvora s najmanjom težinom. Jasno je da je prioritetni red poput piramide (minimum) prikladniji za ovu svrhu. Čvor s najmanjom težinom imat će najveći prioritet i bit će na vrhu piramide. Dajemo ovaj algoritam.
Uključimo sve kodirane znakove u piramidu.
Izdvojmo uzastopno 2 čvora iz piramide (to će biti dva čvora s najmanjom težinom).
Formirajmo novi čvor i priložimo mu, kao djeca, dva čvora preuzeta iz piramide. U ovom slučaju, težina formiranog čvora je postavljena jednaka zbroju težina podređenih čvorova.
Uključimo formirani čvor u piramidu.
Ako u piramidi ima više čvorova, ponovite 2-5.
Pretpostavit ćemo da je pointer na njegov roditelj pohranjen za svaki čvor. Postavljamo ovaj pokazivač jednak NULL u korijenu stabla. Sada odaberimo lisni čvor (simbol) i slijedeći spremljene pokazivače popeti ćemo se na stablo sve dok sljedeći pokazivač ne postane NULL. Posljednji uvjet znači da smo došli do korijena stabla. Jasno je da je broj prijelaza s razine na razinu jednak dubini lisnog čvora (simbola), a time i duljini njegovog koda. Zaobilazeći na ovaj način sve čvorove (simbole), dobivamo duljine njihovih kodova.
Maksimalna duljina koda
U pravilu, tzv šifrarnik, jednostavna struktura podataka, u biti dva niza: jedan s duljinama, drugi s kodovima. Drugim riječima, kod (kao bitni niz) je pohranjen na memorijskoj lokaciji ili registru fiksne veličine (obično 16, 32 ili 64). Kako bismo izbjegli prelijevanje, moramo biti sigurni da će kod stati u registar.
Ispada da na abecedi N znakova maksimalna veličina koda može biti duljine do (N-1) bita. Drugim riječima, za N = 256 (uobičajena varijanta) možemo dobiti kod od 255 bita (iako za to datoteka mora biti vrlo velika: 2,292654130570773 * 10 ^ 53 ~ = 2 ^ 177,259)! Jasno je da takva šifra neće stati u registar i s njom morate nešto poduzeti.
Prvo, otkrijmo pod kojim uvjetima dolazi do preljeva. Neka je frekvencija i-tog simbola jednaka i-tom Fibonaccijevom broju. Na primjer: A-1, B-1, C-2, D-3, E-5, F-8, G-13, H-21. Konstruirajmo odgovarajuće Huffmanovo stablo.
KORIJEN / \ / \ / \ / \ H / \ / \ /\ G / \ / \ /\ F / \ / \ /\ E / \ / \ /\ D / \ / \ /\ C / \ / \ A B
Takvo stablo se zove degenerirati... Da bi ga dobili, frekvencije simbola moraju rasti barem kao Fibonaccijevi brojevi ili čak brže. Iako je u praksi, na stvarnim podacima, takvo stablo gotovo nemoguće dobiti, vrlo ga je lako generirati umjetno. U svakom slučaju, tu opasnost treba uzeti u obzir.
Ovaj se problem može riješiti na dva prihvatljiva načina. Prvi se temelji na jednom od svojstava kanonskih kodova. Poanta je u tome da u kanoničkom kodu (bit string) najmanje bitni bitovi mogu biti različiti od nule. Drugim riječima, svi ostali bitovi možda uopće neće biti spremljeni, budući da uvijek su nula. U slučaju N = 256, dovoljno nam je da iz svakog koda spremimo samo najmanje značajnih 8 bitova, uz pretpostavku da su svi ostali bitovi jednaki nuli. Ovo rješava problem, ali samo djelomično. To će uvelike zakomplicirati i usporiti i kodiranje i dekodiranje. Stoga se ova metoda rijetko koristi u praksi.
Drugi način je umjetno ograničavanje duljine kodova (bilo tijekom izgradnje ili poslije). Ova metoda je općeprihvaćena, pa ćemo se na njoj detaljnije zadržati.
Postoje dvije vrste algoritama koda za ograničavanje duljine. Heuristički (približan) i optimalan. Algoritmi drugog tipa prilično su složeni u implementaciji i u pravilu zahtijevaju više vremena i memorije od prvih. Učinkovitost heuristički ograničenog koda određena je njegovim odstupanjem od optimalno ograničenog koda. Što je razlika manja, to bolje. Vrijedi napomenuti da je za neke heurističke algoritme ta razlika vrlo mala (,,), štoviše, oni vrlo često generiraju optimalan kod (iako ne jamče da će to uvijek biti tako). Štoviše, budući da u praksi se prelijevanje događa iznimno rijetko (osim ako nije postavljeno vrlo strogo ograničenje maksimalne duljine koda); s malom veličinom abecede, svrsishodnije je koristiti jednostavne i brze heurističke metode.
Razmotrit ćemo jedan prilično jednostavan i vrlo popularan heuristički algoritam. Pronašao je put u programe kao što su zip, gzip, pkzip, bzip2 i mnogi drugi.
Problem ograničavanja maksimalne duljine koda je ekvivalentan problemu ograničavanja visine Huffmanovog stabla. Imajte na umu da, po konstrukciji, svaki nelisni čvor Huffmanovog stabla ima točno dva potomka. Pri svakoj iteraciji našeg algoritma smanjit ćemo visinu stabla za 1. Dakle, neka je L maksimalna duljina koda (visina stabla) i potrebno ju je ograničiti na L / & lt L. Neka je daljnji RN i krajnji desni lisni čvor na razini i, a LN i - krajnji lijevi.
Počnimo od razine L. Pomaknite RN L čvor na mjesto njegovog roditelja. Jer čvorovi idu u paru, moramo pronaći mjesto za čvor uz RN L. Da biste to učinili, pronađite razinu j najbližu L, koja sadrži čvorove lista, tako da je j & lt (L-1). Umjesto LN j formiramo čvor koji nije u obliku lista i kao podrijetli mu pripajamo čvor LN j i nespareni čvor s razine L. Istu operaciju primjenjujemo na sve preostale parove čvorova na razini 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 / & lt (L-1), onda ćemo isto učiniti s razinom (L-1) itd. dok se ne dosegne traženo ograničenje.
Vratimo se na naš primjer, gdje je L = 5. Ograničimo maksimalnu duljinu koda na L / = 4.
KORIJEN / \ / \ / \ / \ H C E / \ / \ / \ / \ /\ A D G / \ / \ B F
Vidi se da je u našem slučaju RN L = F, j = 3, LN j = C... Prvo pomaknite čvor RN L = F umjesto svog roditelja.
KORIJEN / \ / \ / \ / \ H / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ C E / \ / \ / \ / \ F A D G B(nespareni čvor)
Sada na mjestu LN j = C Formiramo nelisni čvor.
KORIJEN / \ / \ / \ / \ H E / \ / \ / \ / \ / \ / \ F A D G ? ? B(nespareni čvor) C(nespareni čvor)
Priložimo dva nesparena na formirani čvor: B i C.
KORIJEN / \ / \ / \ / \ H / \ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ / \ / \ / \ / \ / \ / \ / \ / \ /\ /\ /\ E / \ / \ / \ / \ / \ / \ F A D G B C
Stoga smo maksimalnu duljinu koda ograničili na 4. Jasno je da smo promjenom duljine koda malo izgubili na učinkovitosti. Dakle, poruka S, kodirana takvim kodom, bit će veličine 92 bita, t.j. Još 3 bita iznad minimalne redundance.
Jasno je da što više ograničimo maksimalnu duljinu koda, to će kod biti manje učinkovit. Hajde da saznamo koliko možete ograničiti maksimalnu duljinu koda. Očito ne kraće od malo.
Izračun kanonskih kodova
Kao što smo već mnogo puta primijetili, duljine kodova dovoljne su za generiranje samih kodova. Pokazat ćemo vam kako se to može učiniti. Pretpostavimo da smo već izračunali duljine kodova i izbrojali koliko kodova svake duljine imamo. Neka je L maksimalna duljina koda, a T i broj kodova duljine i.
Izračunavamo S i - početnu vrijednost koda duljine 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... Ove simbole ujedinjuje činjenica da su svi na prvom mjestu, svaki na svojoj razini. Drugim riječima, pronašli smo početnu vrijednost koda za svaku duljinu (ili razinu).
Sada dodijelimo kodove ostalim simbolima. Šifra prvog znaka na razini i je S i, drugog S i + 1, trećeg S i + 2, itd.
Napiš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 koš |
F= S 5 + 1 = 00001 bin | D= S 4 + 1 = 0010 bin | E= S 3 + 1 = 011 bin | |
G= S 4 + 2 = 0011 bin |
Vidi se da smo dobili potpuno iste kodove kao da smo eksplicitno izgradili kanonsko Huffmanovo stablo.
Prenošenje kodnog stabla
Da bi se kodirana poruka mogla dekodirati, dekoder mora imati isto stablo koda (u ovom ili onom obliku) koje je korišteno za kodiranje. Stoga smo zajedno s kodiranim podacima prisiljeni spremiti odgovarajuće stablo koda. Jasno je da što je kompaktniji, to bolje.
Postoji nekoliko načina za rješavanje ovog problema. Najočitije 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 najneučinkovitiji način. U praksi se ne koristi.
Popis frekvencija simbola (tj. frekvencijski rječnik) može se spremiti. Uz njegovu pomoć, dekoder može lako rekonstruirati kodno stablo. Iako je ova metoda manje rasipna od prethodne, nije najbolja.
Konačno, može se koristiti jedno od svojstava kanonskih kodova. Kao što je ranije navedeno, kanonski kodovi su potpuno određeni svojim duljinama. Drugim riječima, sve što dekoder treba je popis duljina znakovnog koda. Uzimajući u obzir da se u prosjeku duljina jednog koda za abecedu N znakova može kodirati u [(log 2 (log 2 N))] bitovima, dobivamo vrlo učinkovit algoritam. Zadržat ćemo se na tome detaljnije.
Pretpostavimo da je veličina abecede N = 256 i da komprimiramo običnu tekstualnu datoteku (ASCII). Najvjerojatnije u takvoj datoteci nećemo pronaći svih N znakova naše abecede. Postavimo zatim duljinu koda znakova koji nedostaju jednaku nuli. U tom slučaju, spremljeni popis duljina kodova sadržavat će dovoljno velik broj nula (duljine kodova znakova koji nedostaju) grupiranih zajedno. Svaka takva grupa može se komprimirati pomoću tzv. grupnog kodiranja - RLE (Run - Length - Encoding). Ovaj algoritam je izuzetno jednostavan. Umjesto niza od M identičnih elemenata u nizu, spremit ćemo prvi element ovog niza i broj njegovih ponavljanja, t.j. (M-1). Primjer: RLE ("AAABBBCDDDDDDD") = A3 B2 C0 D6.
Štoviše, ova se metoda može donekle proširiti. RLE algoritam možemo primijeniti ne samo na grupe nulte duljine, već i na sve ostale. Ovaj način prosljeđivanja kodnog stabla je uobičajen i koristi se u većini modernih implementacija.
Implementacija: SHCODEC
Dodatak: biografija D. Huffmana
![]() |
David Huffman rođen je 1925. godine u Ohiju, SAD. Huffman je diplomirao elektrotehniku na Državnom sveučilištu Ohio u dobi od 18 godina. Potom je služio u vojsci kao časnik radarske potpore na razaraču koji je pomogao u deaktiviranju mina u japanskim i kineskim vodama nakon Drugog svjetskog rata. Nakon toga je magistrirao na Sveučilištu Ohio i doktorirao na Massachusetts Institute of Technology (MIT). Iako je Huffman najpoznatiji po razvoju metode za konstruiranje minimalno redundantnih kodova, također je dao važan doprinos mnogim drugim poljima (uglavnom u elektronici). Bio je dugogodišnji voditelj Odjela za informatiku na MIT-u. Godine 1974. već kao profesor emeritus dao je ostavku. Huffman je dobio niz vrijednih nagrada. 1999. - Medalja Richarda W. Hamminga s Instituta inženjera elektrotehnike i elektronike (IEEE) za izniman doprinos teoriji informacija, medalja Louisa E. Levyja s Instituta Franklin za njegovu doktorsku tezu o sekvencijalnim krugovima, nagrada W. Wallacea McDowella, IEEE Computer Nagrada društva, IEEE Gold Jubilee Technology Innovation Award 1998. U listopadu 1999., u dobi od 74 godine, David Huffman je umro od raka. |