You are on page 1of 29

Strukture podataka i algoritmi

Sadraj Osnove sloenosti algoritama. Apstraktni tipovi podataka. Elementi apstraktnog tipa, pregled apstraktnih tipova (lista, stog, vektor, skup, stabla, prioritetni red, graf). Ureeni i neureeni kontejneri. Najee strukture koritene za implementaciju apstraktnih tipova (polje, lista, hash, hrpa, stabla). Algoritmi za osnovne operacije nad apstraktnim tipovima. Opi pristupi rjeavanja problema rekurzivni algoritmi ("podijeli i vladaj", backtracking), pohlepni algoritmi. Sloenije operacije nad apstraktnim tipovima (sortiranje, aritmetiki izrazi, obilasci, najkrai put). Literatura: 1. Knuth, Donald E: "The Art of Computer Programming, Vol. 1: Fundamental Algorithms", 3rd edition, Addison-Wesley, 1997 2. Knuth, Donald E: "The Art of Computer Programming, Vol. 3: Sorting and Searching", 2nd edition, Addison-Wesley, 1998 3. Manber, Udi: Introduction To Algorithms - A Creative Approach, Addison-Wesley, 1989. 4. NIST: Dictionary of Algorithms and Data Structures, http://www.nist.gov/dads/

Definicija algoritma
Algoritam je postupak (pravilo) za rjeavanje odreene klase problema. Sastoji se od niza instrukcija (naredbi). Izvoenje algoritma mora biti reproducibilno. Primjer: peenje palainki pripremi smjesu ulij ulje u tavu zagrij tavu dok ima smjese ponavljaj: ulij pravu koliinu smjese u tavu ekaj dok se donja strana ispee okreni palainku ekaj dok se gornja strana ispee premjesti palainku na tanjur ako je tava suha dolij malo ulja

Ovakav opis je neprikladan za raunalo. Neprecizan i nejasan. Npr. to je smjesa? Koliko ulja? Koliko zagrijati? Kada je palainka peena?

Da bi algoritam bio prikladan za izvoenje, bilo na papiru, bilo na raunalu mora udovoljavati nekim formalnim zahtjevima: 1. Postoji poetna instrukcija. 2. Po zavretku neke instrukcije tono se zna koja se sljedea izvrava (determinizam). 3. Postupak zavrava u konano mnogo koraka za svaki problem iz klase koju rjeavamo (instrukcija se moe izvriti vie puta). 4. Postupak se sastoji od konanog broja instrukcija. Svojstva algoritma: 1. Ulaz: Algoritam ima 0 ili vie ulaznih (ali konano mnogo) vrijednosti (objekata). 2. Izlaz: Svaki algoritam mora imati barem 1 izlaz (rezultat, rjeenje). 3. Konanost: Algoritam mora zavriti u konano mnogo koraka za svaki ulaz. Ako je nuno, to se moe postii i ograniavanjem skupa problema. 4. Definiranost i nedvosmislenost: Svaka osnovna instrukcija mora biti jednoznano definirana za izvritelja (raunalo). 5. Efikasnost: Mora imati "razumnu" sloenost mora zavriti u "razumnom" vremenu uz "razumni" utroak prostora.

Analiza algoritma
Analiza algoritma ili analiza sloenosti algoritma je izuzetno vana u dizajnu algoritama i programiranju. esto postoji vie naina za rjeavanje istog problema i moramo znati koji od njih je najefikasniji pod uvjetima koji nas zanimaju. Najee gledamo prostornu i vremensku sloenost. Vremenska sloenost: Vrijeme potrebno za izvoenje algoritma. Mjeri se u nekim osnovnim jedinicama (strojne instrukcije, aritmetike operacije). Jedinica se bira tako da usporeujemo algoritme, a ne raunala ili arhitekture raunala. Prostorna sloenost: Memorija potrebna (obino maksimalna) za izvoenje algoritma. Jedinica mjere moe biti bit, bajt, rije, cijeli broj, broj sa pominim zarezom. Kod cijelih i floating-point brojeva se uzima da e konkretna implementacija zauzimati neki stalan broj bitova po svakom broju (32, 64 ...) Prostorna sloenost je uglavnom manje ograniavajua jer algoritam istu memorijsku lokaciju esto koristi vie puta tokom izvoenja. Primjer: Peenje "n" palainki. Podijelimo postupak u pripremu i peenje. Priprema: Koliko nam je vremena/posua potrebno za pripremu smjese? Koliko palainki moemo napraviti od odreene koliine smjese? Peenje: Koliko nam posua treba tokom peenja? Koliko traje peenje jedne palainke? n palainki?

Vidimo da se broj posua ne mijenja ovisno o broju palainki koje moramo ispei, dok se vrijeme poveava. Razumna pretpostavka je da peenje svake palainke traje jednako, dakle slijedi da vrijeme peenja linearno ovisi o broju palainki. Meutim to ako nestane smjese, a nismo dovoljno ispekli? Korekcija algoritma, nova analiza. Primjer (Izvrednjavanje polinoma): Zadan je niz realnih brojeva an, an-1,..., a1, a0 i realni broj x. Izraunajte vrijednost polinoma Pn(x)= anxn + an-1xn-1 + ... + a1x + a0. U problemu se pojavljuje n+2 brojeva. Pristupimo problemu preko indukcije svodimo rjeavanje zadanog problema na rjeavanje manjeg problema. Pokuajmo ukloniti vodei koeficijent an. Pretpostavimo da znamo izraunati Pn-1(x) = an-1xn-1 + an-2xn-2 + ... + a1x + a0. Baza indukcije je trivijalna: P0(x) = a0. Sada rijeimo problem Pn(x) pomou rjeenja manjeg problema ( Pn-1(x) ). Korak indukcije je: Pn(x) = anxn + Pn-1(x). Program:
for (P=0,i=0;i<=n;++i) { for (xi=1,j=0;j<i;++j) xi *= x; P += a[i]*xi; }

Ovaj algoritam nam oito daje tono rjeenje, meutim nije efikasan. Izvravanje zahtjeva n + (n 1) + (n 2) + ... + 1 = n(n 1)/2 mnoenja i n zbrajanja. Bolje rjeenje moemo dobiti ako uzmemo jau pretpostavku indukcije: Znamo izraunati Pn-1(x) i znamo izraunati xn-1. Sada u koraku indukcije xn dobijemo iz xn-1 uz jedno mnoenje, a zatim anxn uz jo jedno mnoenje. Pn(x) zahtjeva jo jedno zbrajanje. Ukupno imamo 2n mnoenja i n zbrajanja. Iako smo u koraku indukcije zahtjevali vie, ukupan broj operacija je znatno manji. Program:
for (P=0,xi=1,i=0;i<=n;++i,xi *= x) P += a[i]*xi;

Ovaj algoritam je jednostavan, efikasan i naoko optimalan. Meutim postoji i bolji algoritam. Pokuajmo sada ukloniti prvi koeficijent a0. Zapiimo na polinom kao: Pn(x) = (anxn-1 + an-1xn-2 + ... + a1)x + a0 = P'n-1(x)x + a0 Pretpostavka indukcije je sada: Znamo izraunati P'n-1(x) zadan brojevima an, an-1, ... , a1. Iz njega jednim mnoenjem i jednim zbrajanjem dobijamo Pn(x). Ako polinom raspiemo do kraja dobijemo: Pn(x) = ((...((anx + an-1)x + an-2)x + ..... )x + a1)x + a0. Ovakav postupak izvrednjavanja polinoma zovemo Hornerovo pravilo (po W.G. Horneru). Vidimo da smo za cijeli postupak koristili samo n mnoenja i n zbrajanja, te jednu dodatnu memorijsku lokaciju.

Program glasi:
P = a[n]; for (i=n-1;i>=0;--i) P = x*P + a[i];

Primjer (Obris grada): Zadani su poloaj i dimenzije n pravokutnih zgrada. Treba nai dvodimenzionalni obris tih zgrada uklanjanjem nevidljivih linija.
( 1, 11, 5) Left = 1, Height = 11, Right = 15, ( 2, 6, 7) Left = 2, Height = 6, Right = 7, ( 3, 13, 9) Left = 3, Height = 13, Right = 9, (12, 7, 16) Left = 12, Height = 7, Right = 16, (14, 3, 25) Left = 14, Height = 3, Right = 25, (19, 18, 22) Left = 19, Height = 18, Right = 22, (23, 13, 29) Left = 23, Height = 13, Right = 29, (24, 4, 28) Left = 24, Height = 4, Right = 28.

20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 8 10 12 14 16 18 20 22 24 26 28 30

Ulaz za Skyline algoritam


20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 8 10 12 14 16 18 20 22 24 26 28 30

Izlaz iz Skyline algoritma Zgrade su nam predstavljene pravokutnicima, a zanima nas samo dvodimenzionalni prikaz. Sve zgrade imaju dno na istoj visini (0). Svaku zgradu Bi tako moemo prikazati kao trojku (Li,Hi,Ri) (Left, Height, Right). Li i Ri su horizontalne koordinate lijevog i desnog ruba zgrade, Hi je visina zgrade. Izlaz je lista toaka koje predstavljaju mjesta na kojima se visina obrisa mijenja. Uzmimo kao primjer listu (1,11,5), (2,6,7), (3,13,9), (12,7,16), (14,3,25), (19,18,22), (23,13,29), (24,4,28). Radi lakeg snalaenja visine su podebljane. Obris za ove zgrade izgleda ovako (1,11,3,13,9,0,12,7,16,3,19,18,22,3,23,13,29,0). Najjednostavniji pristup ovom problemu je dodavanje jedne po jedne zgrade u obris. Pretpostavka indukcije je da znamo rijeiti problem za n-1 zgrada, zatim dodajemo ntu zgradu. Kad dodajemo jednu zgradu uzmemo postojei obris i sijeemo novu zgradu sa

njim. Prvo pretraujemo obris da bi nali gdje ubacujemo lijevu koordinatu nove zgrade. Tada idemo nadesno po obrisu i korigiramo visinu na mjestima gdje je ona manja od visine nove zgrade. To radimo dok ne doemo do toke u obrisu koja je desnije od kraja nove zgrade. Npr. na poetku (1,11,5,0) -> (1,11,5,7,7,0) -> (1,11,3,13,9,0). Prolaz za k-tu zgradu traje "otprilike" k koraka. To nam daje ukupno 1 + 2 + ... + n-1 + n = (n+1)n/2 koraka. Da bi poboljali algoritam koristiti emo se estom i poznatom tehnikom "podijeli i vladaj" (divide and conquer). Vie o ovoj tehnici saznat emo kasnije. Za sada pokuajmo samo umjesto proirivanja rjeenja za n-1 zgradu na n, proiriti rjeenje za n/2 zgrada na rjeenje za n. Kod podijeli i vladaj strategije problem se dijeli u manje potprobleme koji se rjeavaju rekurzivno. Rjeenja manjih problema se zatim spajaju u konano rjeenje. Openito je efikasnije podijeliti problem na potprobleme jednake veliine osim ako priroda algoritma nije takva da emo imati problema pri spajanju rjeenja (prevelika sloenost). Pokuajmo dakle spojiti rjeenja dva potproblema, u naem sluaju dva obrisa. Primjenjujemo gotovo isti postupak kao i kod dodavanja jedne zgrade. Pretraujemo oba obrisa slijeva nadesno traimo susjedne horizontalne toke i postavljamo visinu obrisa na veu od dvije postojee visine. Spajanje traje n/2+n/2 = n koraka. Ako sada idemo izraziti sloenost algoritma dobijamo T(n) = 2T(n/2) +cn. Ovdje nam je c neka konstanta koju ne moramo tono odrediti jer nam ne utjee bitnije na rjeenje relacije. O ovakvom zanemarivanju konstanti emo vie govoriti na kraju ovog uvoda u sloenost algoritama (vidi O-notacija). Rjeenje gornje relacije je T(n) = c1nlog(n) + c2, gdje su c1 i c2 neke druge konstante. Usporedimo li to sa brojem koraka potrebnim za izvravanje prvog algoritma vidimo da je sloenost drugog algoritma primjetno manja. Grafika i tablina usporedba broja koraka za navedene algoritme
100

80

60

40

20

n 2 5 10 100
2 4 6 8 10 12 14

n(n-1)/2 1 10 45 4950

nlog n 2 9 24 461

Moemo openito zapamtiti da ako podijelimo problem u dva problema jednake veliine, a zatim rjeenja kombiniramo u linearnom broju koraka imati emo sloenost nlog n. Kljuna injenica u poboljanju ovog algoritma je da dodavanje jedne zgrade u obris traje jednako kao spajanje dva upola kraa obrisa. Ako u algoritmu imamo korak koji je openitiji nego to nam se ini potrebnim, pokuajmo ga primjeniti na sloeniji dio problema. Kako mjeriti sloenost? U gornjim primjerima smo nekako prirodno uzimali broj "ulaznih elemenata". Meutim pokuati emo tu mjeru malo formalizirati. Zadaa je konkretni problem iz skupa koji rjeavamo. Ako algoritam nalazi namanji broj u nizu cijelih brojeva, primjer zadae je: "Nai najmanji broj u nizu 1, 5, 4".

Imamo npr. 2 algoritma za isti problem (isti ulaz). Kako ih usporediti? Najkorektnije izmjeriti sloenost za svaku zadau. Vrlo nepraktino treba izvriti oba algoritma za svaku zadau. Skup zadaa je u najboljem sluaju veoma velik, a najee je beskonaan. Moramo nekako podijeliti skup zadaa na manje dijelove i elemente unutar tih dijelova proglasiti ekvivalentnim. Npr. u gornjem primjeru skup zadaa je beskonaan skup X = { "peenje 0 palainki", "peenje 1 palainke", ..., "peenje n palainki", ... }. Uvodimo sloenost kao funkciju veliine zadae koju rjeavamo. Veliina zadae nije jednoznana, ovisit e o problemu. To je mjera koliine podataka na ulazu u algoritam. Ako je zadaa oznaena sa x, tada njenu veliinu oznaavamo sa |x|. Formalno to je broj bitova potreban za reprezentaciju zadae x na ulazu u algoritam, uz koritenje jednoznanog i razumno kratkog naina kodiranja. Kada se govori o cijelim brojevima (integer) u kontekstu algoritama trebamo biti oprezni jer nekad e se taj izraz nekad koristiti uz podrazumijevanje zapisa fiksne duljine (fixed integer), a nekad uz "duge brojeve" koji su posebna struktura podataka. Kod fiksne duljine unaprijed znamo koliko prostora svaki broj zauzima i tu smo skup cijelih brojeva ograniili na vrijednosti koje moemo prikazati u zadanom broju bitova (232, 264...). "Dugi brojevi" su nain prikazivanja velikih brojeva u raunalu. Tu nam je broj prikazan kao niz manjih brojeva najee gorespomenutih. Sve operacije nad dugim brojevima se zatim izvode preko osnovnih operacija (nad fiksnim integerima) koje nam prua raunalo. Ovo moramo uzeti u obzir i kod analize ulaza u algoritam, kao i kod analize njegove sloenosti jer "elementarne operacije" nad njima vie nisu elementarne i moramo uzeti u obzir i njihovo trajanje i zauzee prostora. Primjer: Nalaenje najmanjeg elementa u polju cijelih brojeva. Ulaz je polje cijelih brojeva duljine n. Uzmimo da se radi o integerima fiksne duljine. Oznaimo ulazno polje sa A. Njegovi elementi su A1, A2, ..., An. Neka je duljina jednog broja k bitova, tada je njihova ukupna duljina nk bitova. Poto je duljina fiksna uzeti emo kao jedinicu za mjeru rije duljine k bitova. Ulazno polje je tada duljine n. Primjer: Odreivanje da li je broj prost. Ulaz je prirodan broj n. to je razumno kodiranje za ovaj sluaj? Ne promatramo niti jedan konkretan algoritam za testiranje broja, promatramo samo vrstu ulaza! Uzmimo pozicioni prikaz u sustavu sa bazom b, b2. Prikaimo ulazni broj n kao: n = akbk + ... + a1b + a0, 0 ai < b, za i = 0..k-1, 0 < ak <b za i = 0..k, ai,su znamenke broja. k nam je mjera duljine zapisa. Ulaz u algoritam je niz znamenki, duljina ulaza je broj znamenki. Za gornji primjer, za zadau s ulazom n, x = {n}; duljinu zadae u bazi b oznaimo sa |x|b, to je u ovom sluaju: |x|b = k+1 = logb n + 1 Ako usporeujemo zapise u bazama b i c, zbog relacije:

logbn = logcn/logcb, Imati emo da su zapisi u te dvije baze "gotovo proporcionalni" (logcb je konstanta). Za prikaz u bazi 2 imamo |x|2 = log2 n + 1, log2 emo ubudue oznaavati kao lg.

20

40

60

80

100

Duljina prikaza broja u bazi 2

U prethodnom sluaju cijeli broj nam je za nau analizu bio "nedjeljiva cjelina", dok smo ovom sluaju prirodni broj razbijali na manje jedinice znamenke, iji broj nismo unaprijed odredili. Prednost je mogunost prikaza velikih brojeva, ali jedan ulazni broj vie ne moemo raunati kao 1 jedinicu prostora. Prikaz u "bazi 1": Broj n napiemo kao n znakova. 1 = x, 5 = xxxxx. Duljina ovakvog zapisa je |x|1 = n. Ovo je bitno dulji zapis od bilo koje druge baze nije "razumno kompaktan".

O-notacija
Ve smo spominjali da kod usporedbe algoritama esto traimo okvirnu efikasnost. Ovdje emo opisati najei nain takve aproksimacije. Ova metoda se koncentrira na ponaanje algoritama kod veoma velikog broja ulaznih podataka. Uzmimo na primjer da nam je ulaz neka matrica sa n elemenata. Neka prvom algoritmu treba 100n koraka, a drugome 2n2+50. Kod prvog algoritma zanemarimo konstantu 100 i kaemo da je njegovo trajanje n, kod drugog zanemarimo 2 i 50, te je njegovo trajanje n2. Kaemo da je drugi algoritam sporiji, makar npr. za n=5 prvome treba 500 koraka, a drugome 100. Naa aproksimacija vrijedi za dovoljno veliki n, konkretno n50. Ako bi prvi algoritam trebao npr. 100n1.8 on je po ovoj metodi opet bri od drugoga, makar n mora biti oko 300,000,000 da bi 100n1.8 bilo manje od 2n2+50. Ipak ovakvi primjeri nisu esti, jer veina algoritama ima male konstante. Ovdje koristimo ve definiranu duljinu zadae. Za istu duljinu postoji velik broj razliitih zadaa i algoritam se ne mora ponaati isto za sve njih. Za ovu metodu moramo odabrati nekog predstavnika tih zadaa iste duljine. Najee e to biti najgori sluaj, odnosno zadaa za koju moramo izvriti najvei broj koraka. Najbolji sluaj nam nije ba reprezentativan, jer obino postoji neka zadaa za koju je rjeenje trivijano. Prosjean

sluaj je opet dosta teko definirati i izmjeriti. Ako idemo varirati neke ulazne parametre, pa raunamo prosjenu sloenost moe nam se dogoditi da odaberemo sluajeve koji se jako rijetko pojavljuju u praksi te nam mjerenje nee odgovarati stvarnim rezultatima. Za analizu prosjenog sluaja potrebno je najee jednako posla kao i za potpunu analizu algoritma. Uvedimo sada neke oznake za ovakve usporedbe. Funkcija g(n) je O(f(n)) ako postoje konstante c i N takve da za svaki n>N vrijedi g(n)cf(n). Oznaka se ita "o" ili "veliko o". Drugim rijeima, za dovoljno velike n naa funkcija g nije vea od f puta konstanta, kao to smo i najavljivali u uvodu. Vrijednosti g(n) mogu biti manje, ak i bitno manje of cf(n), O notacija nam daje samo gornju ogradu. Sada moemo pisati npr. 2n2 + 10 = O(n2), 5n + 3 = O(n). Konvencija je da je kod jednakosti sa O oznakama O uvijek na desnoj strani (nije prava jednakost). Primjetimo da zbog toga to je O gornja ograda vrijedi i slijedee: 2n+5 = O(n) = O(n2) ... Kod ocjene algoritma moramo paziti da ne damo previsoku gornju ogradu. Nije greka napisati npr O(3n+5) ali to je identino sa O(n). Slino piemo i O(log n) bez baze logaritma, jer promjena baze mijenja samo konstantni faktor. O(1) nam oznaava konstantu. Ponekad elimo malo preciznije opisati sloenost pa moemo pisati npr. T(n) = 2n2 +O(n). Za svaku monotono rastuu1 funkciju f i konstante c>0 i a>1 vrijedi (f(n))c = O(af(n)). Rijeima, eksponencijalna funkcija raste bre od bilo kojeg polinoma. Ako za f(n) uzmemo n imamo da je nc = O(an). Ako za f(n) uzmemo loga n, dobijemo (loga n)c = O(aloga n) = O(n). Slijedea pravila nam pokazuju kako zbrajamo i mnoimo u O-notaciji. 1. Ako je f(n)=O(s(n)) i g(n)=O(r(n)) tada je f(n) + g(n) = O(s(n) + r(n)). 2. Ako je f(n)=O(s(n)) i g(n)=O(r(n)) tada je f(n) g(n) = O(s(n) r(n)). Za oduzimanje i dijeljenje ne moemo primjeniti slina pravila. O-notacija nam u grubo odgovara "" relaciji. Evo jo nekih jednostavnih relacija koje vrijede za O notaciju: f(n) cO(f(n)) O(f(n)) + O(f(n)) O(O(f(n))) O(f(n))O(g(n)) O(f(n)g(n)) = = = = = = O(f(n)) O(f(n)), za konstantu c>0 O(f(n)) O(f(n)) O(f(n)g(n)) f(n)O(g(n))

O notacija nam daje gornju ogradu za trajanje nekog algoritma. Meutim to nam nije dovoljno. Veina algoritama koje emo spominjati ima O(2n), tj. ne treba im vie od
1

n1 n2 => f(n1) f(n2)

eksponencijalnog vremena. To je meutim dosta gruba ocjena. Veina ih je mnogo bra od toga. Zato nas zanima i donja ograda na algoritam. Gornja granica nam je opet laka za raunati jer ona govori da postoji neki algoritam koji ne treba vie vremena od prikazanog. Donja ograda znai da nijedan algoritam ne moe imati bolju ogradu za taj problem. Ako postoje konstante c i N, takve da za svaki nN broj koraka T(n) za rjeavanje problema veliine n je barem cg(n) tada kaemo da je T(n)=(g(n)). Npr. n2 = (n2100), n=(n0.9). notacija nam odgovara "" relaciji. Ako f(n) zadovoljava f(n)=O(g(n)) i f(n)=(g(n)), tada kaemo da je f(n)=(g(n)). Npr. 5nlog2n-10=(nlog n). Konstante koje se javljaju za O i ne moraju biti iste. Sada imamo O, i koji nam otprilike odgovaraju "","" i "=". Za relacije koje odgovaraju "<" i ">" uvodimo i novu oznaku. Kaemo da f(n)=o(g(n)) (itamo "malo o") ako vrijedi:
lim
n

f ( n) = 0. g (n)

Npr. n/log2n=o(n), ali n/10o(n). Slino piemo da je f(n)=(g(n)), ako je g(n)=o(f(n)). Za svaku monotono rastuu funkciju f i konstante c>0 i a>1 vrijedi (f(n))c = o(af(n)). Rijeima, eksponencijalna funkcija raste bre od bilo kojeg polinoma.

Strukture podataka
Apstraktni tipovi podataka
Apstraktni tip podataka (ATP) je definiran modelom podataka i operacija nad njima. Model podataka i operacija definira to su podaci sa kojima radimo, te to operacije rade nad podacima. Njime nije definirano kako se podaci predstavljaju u memoriji niti kako operacije izvravaju svoju zadau. Suelje ATP-a je specifikacija modela u nekom programskom jeziku. Implementacija ATP-a je realizacija modela u nekom programskom jeziku. Isti ATP esto moe imati vie rezliitih implementacija. Operacije su jednoznano definirane bez obzira na implementaciju. Koritenje ATP-ova nam omoguava skrivanje interne strukture podataka i odvajanje kda koji upravlja internim strukturama od aplikacijskog kda. Pristupanje internim podacima vri se iskljuivo preko strogo definiranih operacija (metoda). Tako se smanjuje mogunost greke, ali i omoguava se zamjena implementacije apstraktnog tipa bez potrebe za mijenjanjem cjelokupnog kda. Apstraktni tipovi podataka u C-u Kod implementacije apstraktnih tipova podataka u C-u, osnove znaajke jezika koje koristimo su nam anonimni pointeri (forward deklarirani) i dinamiko alociranje memorije. struct nepoznata_struktura *p; Anonimni pointer se ponaa vrlo slino kao void pointer. Dok je struktura definirana samo unaprijed, ne moemo pristupiti njenoj "unutranjosti'', tj. nijednom njenom lanu ona je neprozirna. Pokaziva na strukturu koristimo samo kao handle kojim pristupamo naem tipu. U C-u e nam obino header file sadravati deklaracije za na neprozirni pokaziva, te prototipove funkcija koje e izvravati operacije nad naim tipom.

ATP STACK
Apstrakcija stoga (sloaja) - kolekcije podataka sa LIFO (Last In - First Out) pristupom Tipovi: - STACK - element Operacije: STACK new() delete (STACK s) push(STACK s, element e) pop(STACK s) element top(STACK s) bool is_empty(STACK s) stvara novi prazni stog unitava stog ubacuje element na vrh stoga uklanja element s vrha stoga vraa element na vrhu stoga vraa logiku vrijednost koja govori da li je stog prazan

Implementacija pomou niza. Implementacija pomou vezane liste.

Prikaz operacija nad ATP STACK Ubacivanje elementa - push(STACK s, element e); 5
3 10

5 3 10

Gornji element - element top(STACK s);

5 5 3 10 5 3 10

Uklanjanje s vrha - pop(STACK s); 5 3 10 3 10

Suelje za ATP STACK typedef int stack_element_t; struct stack_tag; typedef struct stack_tag *stack_t; stack_t stack_new(); void stack_delete (stack_t S); void stack_push(stack_t S, stack_element_t void stack_pop(stack_t S); stack_element_t stack_top(stack_t S); bool stack_is_empty(stack_t S);

e);

ATP QUEUE
Apstrakcija reda - kolekcije podataka sa FIFO (First In - First Out) pristupom Tipovi: - QUEUE - element Operacije: QUEUE new() stvara novi (prazni) red delete (QUEUE q) unitava red enqueue(QUEUE q, element e) ubacuje element na kraj reda dequeue(QUEUE q) uklanja element s poetka reda element front(QUEUE q) vraa element na poetku reda bool is_empty(QUEUE q) vraa logiku vrijednost koja govori da li je red prazan Implementacija pomou cirkularnog niza. Implementacija pomou vezane liste.

Prikaz operacija nad ATP QUEUE Ubacivanje na kraj - enqueue(QUEUE q, element e);

A A

B B

C C

D D

Element sa poetka - element front(QUEUE q); D

A A

B B

C C

D D

Izbacivanje sa poetka - dequeue(QUEUE q);

Suelje za ATP QUEUE typedef char queue_element_t; struct queue_tag; typedef struct queue_tag *queue_t; queue_t queue_new(); void queue_delete(queue_t Q); void queue_enqueue(queue_t Q, queue_element_t e); void queue_dequeue(queue_t Q); queue_element_t queue_front(queue_t Q); bool queue_is_empty(queue_t Q);

ATP SET
Apstrakcija skupa. Tipovi: - SET - element Operacije: SET new() delete(SET s) insert(SET s, element e) remove(SET s, element e) bool is_member(SET s, element e) bool is_subset(SET s1, SET s2)

SET union(SET s1, SET s2) SET intersection(SET s1, SET s2) - SET difference(SET s1, SET s2)

stvara novi prazan skup unitava skup ubacuje element u skup uklanja element iz skupa vraa logiku vrijednost koja govori da li je element u skupu vraa logiku vrijednost koja govori da li je skup s1 podskup skupa s2 vraa uniju skupova s1 i s2 vraa presjek skupova s1 i s2 vraa razliku skupova s1 i s2

Implementacija pomou bit-vektora. Implementacija pomou sortirane vezane liste. Bit - vektor reprezentacija za ATP SET

A B C D E F G H I J K L M N O P
0 0 1 0 0 1 0 0 0 1 1 1 0 0 0 0

Suelje za ATP SET typedef char set_element_t; struct set_tag; typedef struct set_tag *set_t; set_t set_new(); void set_delete(set_t void set_insert(set_t void set_remove(set_t

S); S, set_element_t S, set_element_t

e); e);

bool set_is_member(set_t S, set_element_t e); bool set_is_subset(set_t S1, set_t S2); set_t set_union(set_t S1, set_t S2); set_t set_intersection(set_t S1, set_t S2); set_t set_difference(set_t S1, set_t S2);

ATP DICTIONARY
Apstrakcija rjenika -(skupa za kojeg nisu definirani pojmovi podskup, unija, presjek, razlika)-. Tipovi: - DICTIONARY - element Operacije: DICTIONARY new() delete(DICTIONARY d) insert(DICTIONARY d, element e) remove(DICTIONARY d, element e) bool is_member(DICTIONARY d, element e) bool is_empty(DICTIONARY d) stvara novi prazan rjenik unitava rjenik ubacuje element u rjenik uklanja element iz rjenika vraa logiku vrijednost koja govori da li je element u rjeniku vraa logiku vrijednost koja govori da li je rjenik prazan

Implementacija pomou hash tablice. Implementacija pomou binarnog stabla traenja. Proirenje na asocijativnu kolekciju. Suelje za ATP DICTIONARY
typedef int dictionary_element_t; struct dictionary_tag; typedef struct dictionary_tag *dictionary_t; dictionary_t dictionary_new(); void dictionary_delete(dictionary_t D); void dictionary_insert(dictionary_t D, dictionary_element_t e); void dictionary_remove(dictionary_t D, dictionary_element_t e); bool dictionary_is_member(dictionary_t D, dictionary_element_t e); bool dictionary_is_empty(dictionary_t D);

Jednostruko povezana vezana lista

Dvostruko povezana vezana lista

Jednostruko povezana vezana lista sa zaglavljem

Dvostruko povezana vezana lista sa zaglavljem

Prednost zaglavlja je uklanjanje specijalnih sluajeva za ubacivanje i brisanje elemenata. Mana je to to moramo definirati da li je to regularan ili poseban vor.

ATP LIST
Apstrakcija liste -(kolekcije/kontejner podataka s definiranim poretkom)-. Tipovi: - LIST - element Operacije: LIST_new() delete(LIST l) insert(LIST l, element e) remove(LIST l) element_get(LIST l) first(LIST l) last(LIST l) stvara novu praznu listu unitava listu ubacuje element na trenutnu poziciju u listi uklanja element sa trenutne pozicije iz liste vraa element na trenutnoj poziciji u listi postavlja listu na prvu poziciju postavlja listu na zadnju poziciju

bool next(LIST l) bool previous(LIST l)

postavlja listu na sljedeu poziciju postavlja listu na prethodnu poziciju

Implementacija pomou niza. Implementacija pomou vezane liste. Mane ovakvog pristupa: - kako manipulirati sa dvije razliite pozicije u listi, - kako implementirati npr. Bubble Sort?

ATP LIST + POSITION Tipovi: - LIST - position - element Operacije: LIST_new() delete(LISt l) insert(LIST l, position p, element e) remove(LIST l, position p) element_get(LIST l, position p) position first(LIST l) position end(LIST l) position next(LIST l, position p) position previous(LIST l, position p) stvara novu praznu listu unitava listu ubacuje element na zadanu poziciju u listi uklanja element sa zadane pozicije iz liste vraa element na zadanoj poziciji u listi vraa prvu poziciju u listi vraa poziciju nakon zadnje u listi vraa sljedeu poziciju u listi vraa prethodnu poziciju u listi

Bolje rjeenje, ali u suelju liste prejudiciramo da je implementacija dvostruko vezana lista ili polje.

ATP LIST + ITERATOR Tipovi: - LIST - ITERATOR - element Operacije

- predstavlja poziciju

LIST_new() delete(LIST l) insert(LIST l, ITERATOR i, element e) remove(LIST l, ITERATOR i) ITERATOR first(LIST l) ITERATOR end(LIST l) Operacije na ATP ITERATOR: delete(ITERETOR i) element_get(ITERATOR i) next(ITERATOR i) bool is_valid(ITERATOR i) bool is_equal(ITERATOR i1, ITERATOR i2) . Kolekcija

stvara novu praznu listu unitava listu ubacuje element na zadanu poziciju u listi uklanja element sa zadane pozicije iz liste vraa prvu poziciju u listi vraa poziciju nakon zadnje u listi

unitava iterator vraa element na zadanoj poziciji u listi postavlja iterator na sljedeu poziciju pokazuje li iterator na ispravnu poziciju pokazuju li oba iteratora na istu poziciju

Klijent

Iterator

Odvajamo suelje liste od suelja za prolazak (iteriranje) po listi. U suelju liste ne odajemo kako se moe prolaziti po njoj (moda postoji vie naina). Jo malo bolje odvajanje suelja i implementacije nego kod liste sa pozicijama. Gornje suelje nam zadaje forward only iterator.

Operacije za BIDIRECTIONAL ITERATOR: - sve kao i za forward only, - previous(ITERATOR i) - postavlja iterator na prethodnu poziciju. Operacije za RANDOM ACCESS ITERATOR: - sve isto kao i za bidirectional, - seek(ITERATOR i, index n) Novi tip indeks - pozitivni cijeli broj. Moramo paziti na brisanje iteratora koji se vie ne koriste. Iteratore moemo dijeliti i po vrsti pristupa koji omoguuju elementu - read only, read / write.

- postavlja iterator na n-tu poziciju.

Operacije za read / write iterator: - sve kao i za odgovarajui read only, - put(ITERATOR i, element e) poziciji novom vrijednou.

- zamjenjuje element na tekuoj

ATP MAP
Apstrakcija proizvoljnog (korisniki definiranog) preslikavanja. Tipovi: -

MAP ITERATOR key value element = (key, value)

Operacije: MAP_new() delete (MAP m) insert(MAP m, element e) remove(MAP m, key ITERATOR find(MAP k) ITERATOR first(MAP ITERATOR end(MAP k) m, key m) m)

stvara novo prazno preslikavanje unitava preslikavanje ubacuje element sa kljuem e.key i vrijednou e.value uklanja element sa kljuem k iz mape pronalazi vrijednost zadanu kljuem k daje iterator na "prvi" element daje iterator na kraj mape (iza zadnjeg elementa) .

MAP ITERATOR: delete(ITERETOR i) element_get(ITERATOR i) next(ITERATOR i) bool is_valid(ITERATOR i) bool is_equal(ITERATOR i1, ITERATOR i2) unitava iterator vraa element na zadanoj poziciji u listi postavlja iterator na sljedeu poziciju pokazuje li iterator na ispravnu poziciju pokazuju li oba iteratora na istu poziciju

Implementacija pomou hash tablice ili binarnog stabla pretraivanja. Implementacije trae dodatnu funkcionalnost nad kljuem.

Hash

Uglavnom za ubacivanje i pretraivanje. Kljuevi od 1 do n polje od n elemenata. Za male n OK. JMBG kao klju? Imamo n kljueva iz skupa U, |U| = M. Imamo polje od m elemenata. Uvodimo hash funkciju h: U [1m]. 1 m

Svodimo veliki skup (M moguih) na m pretinaca. Rezultat funkcije je indeks u polju. Vie kljueva zavri u istom pretincu - sudar (collision). 1. Hash funkcija koja minimizira sudare, 2. Obrada sudara. U idealnom sluaju ubacivanje i pretraga O(1). Hash funkcije: Treba rasporeivati elemente to sluajnije i to uniformnije, h(x) = x mod m, za prosti m, - nekad ne moemo prilagoditi veliinu tablice na prosti broj, h(x) = (x mod p) mod m, za prosti p, m < p << M, - kp + r? 0 p Rasprivanje elemenata mod p 0 m Fiziko polje (rasprivanje po mod m)

h(x) = ((ax + b) mod p) mod m; a, b sluajni; a, b < p, a 0, - u prosjeku vrlo dobra za sve ulaze, Moemo sluajno odabirati i p.

Obrada sudara Ulanavanje: - pretraivanje: prolaz kroz listu, - ubacivanje: test na duplikate, - dinamika alokacija, dodatna memorija za pokazivae, - radi za bilo koji broj kljueva.

1 2

Re-hashing: otvoreno adresiranje (koristimo samo primarnu tablicu), veliina tablice fiksna, kod sudara na neki nain traimo alternativnu poziciju.

Prelijevanje: - dvije tablice - primarna i preljevna, - stvaramo veze iz primarne u preljevnu (slino ulanavanju). 1 k Primarna tablica

Preljevna tablica

- ako se ponovi ista vrijednost kljua element stavljamo u prvi slobodan pretinac u preljevnoj tablici.

Re-hashing Linearno ispitivanje - kod sudara pokuamo sa sljedeim praznim mjestom (h(x) + 1, h(x) + 2, ), - lako dolazi do sekundarnih sudara, stvaranje grozdova (clustering), pretraga se moe pretvoriti u linearnu.

k k+1

Kvadratno ispitivanje - h2(x) = h(x) + c * i2, i je redni broj re-hasha,

- kljuevi sa istim primarnim hash-em slijede isti niz pozicija (sekundarni grozdovi).
1 k k+1 k+2 k+4 m

1 2

Dvostruko hash-iranje - h(x) + h2(x), h(x) + 2 * h2(x), - h2(x) i n (broj pretinaca) relativno prosti. k = h(x); a = h2(x);
1 k k+a k+2a m

Organizacija
ulanavanje

Prednosti
neogranien broj elemenata neogranien broj sudara brzo re-hashiranje brz pristup (primarna tablica) brz pristup sudari ne zapunjavaju primarnu tablicu

Mane
gubici veliki za relativno prazne tablice unaprijed zadan maksimalan broj elemenata viestruki sudari brisanje tea procjena veliine primarne i preljevne tablice

re-hashing

preljevanje

Stabla
Formalna definicija: stablo je 1. Prazno, 2. Posjeduje korijen i 0 ili vie podstabala. Hijerarhijska struktura, Skup vorova (verteksa) i bridova koji ih povezuju vor je povezan i sa roditeljem (parent) i sa djecom (child) Poseban vor - korijen stabla (root) nema roditelja (nivo 0) vor koji nema djece - list (leaf) Djeca istog vora meusobno su siblinzi, redoslijed djece je bitan. Root

Parent

Child Leaf Stupanj stabla je najvei broj djece koje posjeduje neki vor u stablu. Stablo stupnja 2 zovemo binarno stablo, djecu oznaavamo sa lijevo i desno. kod binarnog stabla ako imamo samo jedno dijete bitno je da li je lijevo ili desno Visina stabla je najvea udaljenost izmeu korijena i lista. vor sadri klju i podatke. Klju mora biti iz potpuno ureenog skupa. - (x1 x2 => x1 < x2 ili x2 < x1) - najee cijeli brojevi ili brojevi sa pominim zarezom. Reprezentacija stabla eksplicitna - vor sa k djece sadri polje od k pokazivaa - ponekad se uva i pokaziva na roditelja - stablo bilo kojeg stupnja moe se prikazati kao binarno stablo - preko pokazivaa na prvo dijete i pokazivaa na svog siblinga. Siblinzi formiraju vezanu listu.

implicitna - koristi se polje, veze su implicirane pozicijom u polju. - za binarno stablo najee: korijen je na A[1], - lijevo dijete vora na A[i] je A[2i], desno na A[2i + 1], - ako je stablo duboko, a nije balansirano - puno praznog prostora, - ako je dobro balansirano, tedimo na pokazivaima, - dimenzije moramo znati unaprijed.

~ balansirano binarno stablo.

~ duboko, nebalansirano stablo (velika dubina za mali broj vorova).

Obilazak stabla - procedura u kojoj obiemo svaki vor tono jednom. A D B C B E D E F G H F A C H G

(A(B(D, E, F), A

C(G, H)))

+
obilaska stabla: preorder =>
16

B D E DEFBGHCA, F ispis -

postorder => ABDEFCGH, C G H

- inorder => DBEAGCH (ignorirali smo tree dijete, slova F nema).

Obilasci stabla: preorder - prvo obiemo redom svu djecu, a zatim trenutni vor, postorder - prvo obiemo trenutni vor, a zatim svu djecu, inorder - obiemo lijevo dijete, zatim trenutni vor, zatim desno dijete. Ima smisla samo za binarno stablo.

Prikaz aritmetikih izraza (5 * 3) + (6 - 2) 53*62-+ +*53-62 - inorder, - preorder, - postorder.

*
+
3 (5 + 2) * 3 52+3* *+523 - inorder, - preorder, - postorder.

((16 + 4) * 5) / (9 - 7) 16 4 + 5 * 9 7 - /

- inorder, - preorder.

/ * + 16 4 5 - 9 7

- postorder.

Binarna stabla pretraivanja (Binary Search Tree) Efikasno implementiraju: traenje, ubacivanje i brisanje. Svi potomci lijevo od vora imaju manju vrijednost kljua, a svi desno od njega veu. Stablo je konzistentno ako svi kljuevi zadovoljavaju uvjet. Zbog jednostavnosti ne dozvoljavamo jednake kljueve. Operacije: Pretraivanje 1. Usporedimo traenu vrijednost (x) sa vrijednou u korijenu (root). 2. x = r - pronali smo, x<r - trai u lijevom podstablu, x>r - trai u desnom podstablu.

- u najgorem sluaju pretraga traje do zadnjeg lista (leaf) u stablu (dubina stabla). Npr. Da nam je traena vrijednost x = 8.
10

14

12

20

Ubacivanje 1. Pretraimo stablo za novu vrijednost (x) 2. Ako ga pronaemo kraj, inae (pretraga zavri na nekom listu) umetnemo kao lijevo ili desno dijete.

Brisanje - brisanje lista jednostavno, - brisanje vora sa jednim djetetom slino, - brisanje vore sa oba djeteta. Neka je B taj vor: 1. Zamijenimo klju od B sa kljuem vora X takvim da: a. X ima najvie jedno dijete, b. Brisanje vora X ostavlja stablo konzistentnim. 2. Briemo X - ima samo jedno dijete.

- vor X mora biti velik barem kao kljuevi u lijevom podstablu od B i manji od svih kljueva u desnom podstablu od B (najvei klju u lijevom podstablu od B). X se naziva prethodnik od B i sigurno nema desno dijete. Zato?
10 10

6 6
14

13

4 4 7
12 20

12

20

8 8
13

Sloenost Sloenost svih operacija nad stablom ovisi o obliku stabla. U najgorem sluaju pretraujemo do dna stabla. Ostale operacije su u konstantnom vremenu (prespajanja, zamjene). Sve operacije ovise o pretrazi. Ako je stablo balansirano oekujemo da je visina lg (n + 1), gdje je n broj vorova. Ubacivanje u sluajnom poretku nam daje visinu O(log n), preciznije 2ln n. Najgori sluaj - stablo degenerira u listu. Npr. ubacivanje sortiranih brojeva. Brisanja takoer mogu debalansirati stablo - uvijek uzimamo prethodnika. Sluajna ubacivanja i brisanja nam daju stablo visine O( n ) AVL stabla Adel' son-Vel' skii i Landis (1962). Prva struktura koja garantira O(log n) u najgorem sluaju za sve operacije. Troimo dodatno vrijeme kod ubacivanja i brisanja da bi balansirali stablo. Vrijeme za balansiranje ne smije prijei O(log n). AVL stablo je binarno stablo pretraivanja takvo da je za svaki vor razlika u visini lijevog i desnog podstabla najvie 1. Visina AVL stabla sa n unutranjih vorova je:

lg(n + 1) < h < 1.4404 lg(n + 2) - 0.3277 Sloenost za pretraivanje O(log n). Razlikujemo optimalno stablo - svi listovi na istoj dubini. vor dodatno uva razliku dubina, oznake +, , -. Ubacivanje: 1. Ubacimo x na dno kao kod BST, 2. Ako je stablo ostalo AVL stablo gotovo inae rebalansiramo.

You might also like