Algoritmi i Strukture Podataka (FER)

January 30, 2017 | Author: Anamaria Donkov | Category: N/A
Share Embed Donate


Short Description

Download Algoritmi i Strukture Podataka (FER)...

Description

Strukture podataka i algoritmi Sadržaj • Osnove složenosti algoritama. • Apstraktni tipovi podataka. • Elementi apstraktnog tipa, pregled apstraktnih tipova (lista, stog, vektor, skup, stabla, prioritetni red, graf). Uređeni i neuređeni kontejneri. • Najčešće strukture korištene za implementaciju apstraktnih tipova (polje, lista, hash, hrpa, stabla). • Algoritmi za osnovne operacije nad apstraktnim tipovima. • Opći pristupi rješavanja problema – rekurzivni algoritmi ("podijeli i vladaj", backtracking), pohlepni algoritmi. • Složenije operacije nad apstraktnim tipovima (sortiranje, aritmetički izrazi, obilasci, najkraći 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 rješavanje određene klase problema. • Sastoji se od niza instrukcija (naredbi). • Izvođenje algoritma mora biti reproducibilno. Primjer: pečenje palačinki • pripremi smjesu • ulij ulje u tavu • zagrij tavu • dok ima smjese ponavljaj: ulij pravu količinu smjese u tavu čekaj dok se donja strana ispeče okreni palačinku čekaj dok se gornja strana ispeče premjesti palačinku na tanjur ako je tava suha dolij malo ulja

Ovakav opis je neprikladan za računalo. Neprecizan i nejasan. Npr. Što je smjesa? Koliko ulja? Koliko zagrijati? Kada je palačinka pečena?

Da bi algoritam bio prikladan za izvođenje, bilo na papiru, bilo na računalu mora udovoljavati nekim formalnim zahtjevima: 1. Postoji početna instrukcija. 2. Po završetku neke instrukcije točno se zna koja se sljedeća izvršava (determinizam). 3. Postupak završava u konačno mnogo koraka za svaki problem iz klase koju rješavamo (instrukcija se može izvršiti više puta). 4. Postupak se sastoji od konačnog broja instrukcija. Svojstva algoritma: 1. Ulaz: Algoritam ima 0 ili više ulaznih (ali konačno mnogo) vrijednosti (objekata). 2. Izlaz: Svaki algoritam mora imati barem 1 izlaz (rezultat, rješenje). 3. Konačnost: Algoritam mora završiti u konačno mnogo koraka za svaki ulaz. Ako je nužno, to se može postići i ograničavanjem skupa problema. 4. Definiranost i nedvosmislenost: Svaka osnovna instrukcija mora biti jednoznačno definirana za izvršitelja (računalo). 5. Efikasnost: Mora imati "razumnu" složenost – mora završiti u "razumnom" vremenu uz "razumni" utrošak prostora.

Analiza algoritma Analiza algoritma ili analiza složenosti algoritma je izuzetno važna u dizajnu algoritama i programiranju. Često postoji više načina za rješavanje istog problema i moramo znati koji od njih je najefikasniji pod uvjetima koji nas zanimaju. Najčešće gledamo prostornu i vremensku složenost. Vremenska složenost: Vrijeme potrebno za izvođenje algoritma. Mjeri se u nekim osnovnim jedinicama (strojne instrukcije, aritmetičke operacije). Jedinica se bira tako da uspoređujemo algoritme, a ne računala ili arhitekture računala. Prostorna složenost: Memorija potrebna (obično maksimalna) za izvođenje algoritma. Jedinica mjere može biti bit, bajt, riječ, cijeli broj, broj sa pomičnim zarezom. Kod cijelih i floating-point brojeva se uzima da će konkretna implementacija zauzimati neki stalan broj bitova po svakom broju (32, 64 ...) Prostorna složenost je uglavnom manje ograničavajuća jer algoritam istu memorijsku lokaciju često koristi više puta tokom izvođenja. Primjer: Pečenje "n" palačinki. Podijelimo postupak u pripremu i pečenje. • Priprema: Koliko nam je vremena/posuđa potrebno za pripremu smjese? Koliko palačinki možemo napraviti od određene količine smjese? • Pečenje: Koliko nam posuđa treba tokom pečenja? Koliko traje pečenje jedne palačinke? n palačinki?

Vidimo da se broj posuđa ne mijenja ovisno o broju palačinki koje moramo ispeći, dok se vrijeme povećava. Razumna pretpostavka je da pečenje svake palačinke traje jednako, dakle slijedi da vrijeme pečenja linearno ovisi o broju palačinki. Međutim š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. Izračunajte vrijednost polinoma Pn(x)= anxn + an-1xn-1 + ... + a1x + a0. U problemu se pojavljuje n+2 brojeva. Pristupimo problemu preko indukcije – svodimo rješavanje zadanog problema na rješavanje manjeg problema. Pokušajmo ukloniti vodeći koeficijent an. Pretpostavimo da znamo izračunati Pn-1(x) = an-1xn-1 + an-2xn-2 + ... + a1x + a0. Baza indukcije je trivijalna: P0(x) = a0. Sada riješimo problem Pn(x) pomoću rješenja manjeg problema ( Pn-1(x) ). Korak indukcije je: Pn(x) = anxn + Pn-1(x). Program: for (P=0,i=0;i (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 poboljšali algoritam koristiti ćemo se čestom i poznatom tehnikom "podijeli i vladaj" (divide and conquer). Više o ovoj tehnici saznat ćemo kasnije. Za sada pokušajmo samo umjesto proširivanja rješenja za n-1 zgradu na n, proširiti rješenje za n/2 zgrada na rješenje za n. Kod podijeli i vladaj strategije problem se dijeli u manje potprobleme koji se rješavaju rekurzivno. Rješenja manjih problema se zatim spajaju u konačno rješenje. Općenito je efikasnije podijeliti problem na potprobleme jednake veličine osim ako priroda algoritma nije takva da ćemo imati problema pri spajanju rješenja (prevelika složenost). Pokušajmo dakle spojiti rješenja dva potproblema, u našem slučaju dva obrisa. Primjenjujemo gotovo isti postupak kao i kod dodavanja jedne zgrade. Pretražujemo oba obrisa slijeva nadesno tražimo susjedne horizontalne točke i postavljamo visinu obrisa na veću od dvije postojeće visine. Spajanje traje n/2+n/2 = n koraka. Ako sada idemo izraziti složenost algoritma dobijamo T(n) = 2⋅ T(n/2) +c⋅ n. Ovdje nam je c neka konstanta koju ne moramo točno odrediti jer nam ne utječe bitnije na rješenje relacije. O ovakvom zanemarivanju konstanti ćemo više govoriti na kraju ovog uvoda u složenost algoritama (vidi O-notacija). Rješenje gornje relacije je T(n) = c1⋅ n⋅ log(n) + c2, gdje su c1 i c2 neke druge konstante. Usporedimo li to sa brojem koraka potrebnim za izvršavanje prvog algoritma vidimo da je složenost drugog algoritma primjetno manja. Grafička i tablična usporedba broja koraka za navedene algoritme 100

n

80

60

n(n-1)/2

n⋅ log n 2 9 24 461

2 1 5 10 10 45 10 4950 0 Možemo općenito zapamtiti da ako podijelimo problem u dva problema jednake veličine, a zatim rješenja kombiniramo u linearnom broju koraka imati ćemo složenost n⋅ log n. Ključna činjenica u poboljšanju ovog algoritma je da dodavanje jedne zgrade u obris traje jednako kao spajanje dva upola kraća obrisa. Ako u algoritmu imamo korak koji je općenitiji nego što nam se čini potrebnim, pokušajmo ga primjeniti na složeniji dio problema. 40

20

2

4

6

8

10

12

14

Kako mjeriti složenost? U gornjim primjerima smo nekako prirodno uzimali broj "ulaznih elemenata". Međutim pokušati ćemo tu mjeru malo formalizirati. Zadaća je konkretni problem iz skupa koji rješavamo. Ako algoritam nalazi namanji broj u nizu cijelih brojeva, primjer zadaće je: "Nađi najmanji broj u nizu 1, 5, 4".

Imamo npr. 2 algoritma za isti problem (isti ulaz). Kako ih usporediti? Najkorektnije – izmjeriti složenost za svaku zadaću. Vrlo nepraktično – treba izvršiti oba algoritma za svaku zadaću. Skup zadaća je u najboljem slučaju veoma velik, a najčešće je beskonačan. Moramo nekako podijeliti skup zadaća na manje dijelove i elemente unutar tih dijelova proglasiti ekvivalentnim. Npr. u gornjem primjeru skup zadaća je beskonačan skup X = { "pečenje 0 palačinki", "pečenje 1 palačinke", ..., "pečenje n palačinki", ... }. Uvodimo složenost kao funkciju veličine zadaće koju rješavamo. Veličina zadaće nije jednoznačna, ovisit će o problemu. To je mjera količine podataka na ulazu u algoritam. Ako je zadaća označena sa x, tada njenu veličinu označavamo sa |x|. Formalno to je broj bitova potreban za reprezentaciju zadaće x na ulazu u algoritam, uz korištenje jednoznačnog i razumno kratkog načina 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 ograničili na vrijednosti koje možemo prikazati u zadanom broju bitova (232, 264...). "Dugi brojevi" su način prikazivanja velikih brojeva u računalu. Tu nam je broj prikazan kao niz manjih brojeva – najčešće gorespomenutih. Sve operacije nad dugim brojevima se zatim izvode preko osnovnih operacija (nad fiksnim integerima) koje nam pruža računalo. Ovo moramo uzeti u obzir i kod analize ulaza u algoritam, kao i kod analize njegove složenosti jer "elementarne operacije" nad njima više nisu elementarne i moramo uzeti u obzir i njihovo trajanje i zauzeće prostora. Primjer: Nalaženje najmanjeg elementa u polju cijelih brojeva. Ulaz je polje cijelih brojeva duljine n. Uzmimo da se radi o integerima fiksne duljine. Označimo ulazno polje sa A. Njegovi elementi su A1, A2, ..., An. Neka je duljina jednog broja k bitova, tada je njihova ukupna duljina n⋅ k bitova. Pošto je duljina fiksna uzeti ćemo kao jedinicu za mjeru riječ duljine k bitova. Ulazno polje je tada duljine n. Primjer: Određivanje da li je broj prost. Ulaz je prirodan broj n. Što je razumno kodiranje za ovaj slučaj? Ne promatramo niti jedan konkretan algoritam za testiranje broja, promatramo samo vrstu ulaza! Uzmimo pozicioni prikaz u sustavu sa bazom b, b≥ 2. Prikažimo ulazni broj n kao: n = akbk + ... + a1b + a0, 0 ≤ ai < b, za i = 0..k-1, 0 < ak N vrijedi g(n)≤c·f(n). Oznaka se čita "o" ili "veliko o". Drugim riječima, za dovoljno velike n naša funkcija g nije veća od f puta konstanta, kao što smo i najavljivali u uvodu. Vrijednosti g(n) mogu biti manje, čak i bitno manje of c·f(n), O notacija nam daje samo gornju ogradu. Sada možemo pisati npr. 2·n2 + 10 = O(n2), 5·n + 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 slijedeće: 2·n+5 = O(n) = O(n2) ... Kod ocjene algoritma moramo paziti da ne damo previsoku gornju ogradu. Nije greška napisati npr O(3·n+5) ali to je identično sa O(n). Slično pišemo i O(log n) bez baze logaritma, jer promjena baze mijenja samo konstantni faktor. O(1) nam označava konstantu. Ponekad želimo malo preciznije opisati složenost pa možemo pisati npr. T(n) = 2·n2 +O(n). Za svaku monotono rastuću1 funkciju f i konstante c>0 i a>1 vrijedi (f(n))c = O(af(n)). Riječima, eksponencijalna funkcija raste brže 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). Slijedeća pravila nam pokazuju kako zbrajamo i množimo 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 možemo primjeniti slična pravila. O-notacija nam u grubo odgovara "≤" relaciji. Evo još nekih jednostavnih relacija koje vrijede za O notaciju: f(n) c·O(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. Međutim to nam nije dovoljno. Većina algoritama koje ćemo spominjati ima O(2n), tj. ne treba im više od eksponencijalnog vremena. To je međutim dosta gruba ocjena. Većina ih je mnogo brža 1

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

od toga. Zato nas zanima i donja ograda na algoritam. Gornja granica nam je opet lakša za računati jer ona govori da postoji neki algoritam koji ne treba više vremena od prikazanog. Donja ograda znači da nijedan algoritam ne može imati bolju ogradu za taj problem. Ako postoje konstante c i N, takve da za svaki n≥N broj koraka T(n) za rješavanje problema veličine n je barem c·g(n) tada kažemo 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 kažemo da je f(n)=Θ(g(n)). Npr. 5·n·log2n-10=Θ(n·log 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 "" uvodimo i novu oznaku. Kažemo 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/10≠o(n). Slično pišemo da je f(n)=ω(g(n)), ako je g(n)=o(f(n)). Za svaku monotono rastuću funkciju f i konstante c>0 i a>1 vrijedi (f(n))c = o(af(n)). Riječima, eksponencijalna funkcija raste brže 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 izvršavaju svoju zadaću. Sučelje ATP-a je specifikacija modela u nekom programskom jeziku. Implementacija ATP-a je realizacija modela u nekom programskom jeziku. Isti ATP često može imati više rezličitih implementacija. Operacije su jednoznačno definirane bez obzira na implementaciju. Korištenje ATP-ova nam omogućava skrivanje interne strukture podataka i odvajanje kôda koji upravlja internim strukturama od aplikacijskog kôda. Pristupanje internim podacima vrši se isključivo preko strogo definiranih operacija (metoda). Tako se smanjuje mogućnost greške, ali i omogućava se zamjena implementacije apstraktnog tipa bez potrebe za mijenjanjem cjelokupnog kôda. Apstraktni tipovi podataka u C-u Kod implementacije apstraktnih tipova podataka u C-u, osnove značajke jezika koje koristimo su nam anonimni pointeri (forward deklarirani) i dinamičko alociranje memorije. struct nepoznata_struktura *p; Anonimni pointer se ponaša vrlo slično kao void pointer. Dok je struktura definirana samo unaprijed, ne možemo pristupiti njenoj "unutrašnjosti'', tj. nijednom njenom članu ona je neprozirna. Pokazivač na strukturu koristimo samo kao handle kojim pristupamo našem tipu. U C-u će nam obično header file sadržavati deklaracije za naš neprozirni pokazivač, te prototipove funkcija koje će izvršavati operacije nad našim tipom.

ATP STACK Apstrakcija stoga (složaja) - 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 uništava stog ubacuje element na vrh stoga uklanja element s vrha stoga vraća element na vrhu stoga vraća logičku vrijednost koja govori da li je stog prazan

Implementacija pomoću niza. Implementacija pomoću vezane liste.

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

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

Sučelje 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) uništava red enqueue(QUEUE q, element e) ubacuje element na kraj reda dequeue(QUEUE q) uklanja element s početka reda element front(QUEUE q) vraća element na početku reda bool is_empty(QUEUE q) vraća logičku vrijednost koja govori da li je red prazan Implementacija pomoću cirkularnog niza. Implementacija pomoću vezane liste.

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

A A

B

C

D

B

C

D

Element sa početka - element front(QUEUE q); D

A

B

C

D

A

B

C

D

Izbacivanje sa početka - dequeue(QUEUE q);

A

B

C

A

B

C

D

Sučelje 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 uništava skup ubacuje element u skup uklanja element iz skupa vraća logičku vrijednost koja govori da li je element u skupu vraća logičku vrijednost koja govori da li je skup s1 podskup skupa s2 vraća uniju skupova s1 i s2 vraća presjek skupova s1 i s2 vraća razliku skupova s1 i s2

Implementacija pomoću bit-vektora. Implementacija pomoću 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

Sučelje 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 rječnika -(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 rječnik uništava rječnik ubacuje element u rječnik uklanja element iz rječnika vraća logičku vrijednost koja govori da li je element u rječniku vraća logičku vrijednost koja govori da li je rječnik prazan

Implementacija pomoću hash tablice. Implementacija pomoću binarnog stabla traženja. Proširenje na asocijativnu kolekciju. Sučelje 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

A

B

C

D

Dvostruko povezana vezana lista

A

B

C

D

Jednostruko povezana vezana lista sa zaglavljem

A

B

C

Dvostruko povezana vezana lista sa zaglavljem

A

B

Prednost zaglavlja je uklanjanje specijalnih slučajeva 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) bool next(LIST l)

stvara novu praznu listu uništava listu ubacuje element na trenutnu poziciju u listi uklanja element sa trenutne pozicije iz liste vraća element na trenutnoj poziciji u listi postavlja listu na prvu poziciju postavlja listu na zadnju poziciju postavlja listu na sljedeću poziciju

bool previous(LIST l)

postavlja listu na prethodnu poziciju

Implementacija pomoću niza. Implementacija pomoću vezane liste. Mane ovakvog pristupa: - kako manipulirati sa dvije različite 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 uništava listu ubacuje element na zadanu poziciju u listi uklanja element sa zadane pozicije iz liste vraća element na zadanoj poziciji u listi vraća prvu poziciju u listi vraća poziciju nakon zadnje u listi vraća sljedeću poziciju u listi vraća prethodnu poziciju u listi

Bolje rješenje, ali u sučelju 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)

stvara novu praznu listu uništava listu ubacuje element na zadanu poziciju u listi uklanja element sa zadane pozicije iz liste vraća prvu poziciju u listi vraća poziciju nakon zadnje u listi

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)

uništava iterator vraća element na zadanoj poziciji u listi postavlja iterator na sljedeću poziciju pokazuje li iterator na ispravnu poziciju pokazuju li oba iteratora na istu poziciju

. Kolekcija

Klijent

Iterator

Odvajamo sučelje liste od sučelja za prolazak (iteriranje) po listi. U sučelju liste ne odajemo kako se može prolaziti po njoj (možda postoji više načina). Još malo bolje odvajanje sučelja i implementacije nego kod liste sa pozicijama. Gornje sučelje 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)

- postavlja iterator na n-tu poziciju.

Novi tip indeks - pozitivni cijeli broj. Moramo paziti na brisanje iteratora koji se više ne koriste. Iteratore možemo dijeliti i po vrsti pristupa koji omogućuju elementu - read only, read / write.

Operacije za read / write iterator: - sve kao i za odgovarajući read only, - put(ITERATOR i, element e) poziciji novom vrijednošću.

- zamjenjuje element na tekućoj

ATP MAP Apstrakcija proizvoljnog (korisnički 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 uništava preslikavanje ubacuje element sa ključem e.key i vrijednošću e.value uklanja element sa ključem k iz mape pronalazi vrijednost zadanu ključem 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)

uništava iterator vraća element na zadanoj poziciji u listi postavlja iterator na sljedeću poziciju pokazuje li iterator na ispravnu poziciju pokazuju li oba iteratora na istu poziciju

Implementacija pomoću hash tablice ili binarnog stabla pretraživanja. Implementacije traže dodatnu funkcionalnost nad ključem.

Hash ¤ ¤ ¤ ¤

Uglavnom za ubacivanje i pretraživanje. Ključevi od 1 do n → polje od n elemenata. Za male n OK. JMBG kao ključ? Imamo n ključeva iz skupa U, |U| = M. Imamo polje od m elemenata. Uvodimo hash funkciju h: U → [1…m]. 1 m

¤ Svodimo veliki skup (M mogućih) na m pretinaca. Rezultat funkcije je indeks u polju. ¤ Više ključeva završi u istom pretincu - sudar (collision). 1. Hash funkcija koja minimizira sudare, 2. Obrada sudara. ¤ U idealnom slučaju ubacivanje i pretraga O(1). Hash funkcije: ¤ Treba raspoređivati elemente što slučajnije i što uniformnije, ¤ h(x) = x mod m, za prosti m, - nekad ne možemo prilagoditi veličinu tablice na prosti broj, ¤ h(x) = (x mod p) mod m, za prosti p, m < p x1 < x2 ili x2 < x1) - najčešće cijeli brojevi ili brojevi sa pomičnim zarezom. Reprezentacija stabla ¤ eksplicitna - čvor sa k djece sadrži polje od k pokazivača - ponekad se čuva i pokazivač na roditelja - stablo bilo kojeg stupnja može se prikazati kao binarno stablo - preko pokazivača na prvo dijete i pokazivača na svog siblinga. Siblinzi formiraju vezanu listu.

¤ implicitna - koristi se polje, veze su implicirane pozicijom u polju. - za binarno stablo najčešće: 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 pokazivačima, - dimenzije moramo znati unaprijed.

~ balansirano binarno stablo.

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

5

Obilazak stabla - procedura u kojoj obiđemo svaki čvor točno jednom. A D B

C

F B

D

E

F

G

A

H

CG

/

*

-

E

H (A(B(D, E, F), C(G,7 H))) 5 9

+ A B D 4 E F

1 6

C

¤ ispis obilaska stabla: - preorder => DEFBGHCA, - postorder => ABDEFCGH, - inorder => DBEAGCH (ignorirali smo treće dijete, slova F nema).

G H

Obilasci stabla: ¤ preorder - prvo obiđemo redom svu djecu, a zatim trenutni čvor, ¤ postorder - prvo obiđemo trenutni čvor, a zatim svu djecu, ¤ inorder - obiđemo lijevo dijete, zatim trenutni čvor, zatim desno dijete. Ima smisla samo za binarno stablo. Prikaz aritmetičkih izraza (5 * 3) + (6 - 2) 53*62-+ +*53-62

+

- inorder, - preorder, - postorder.

*

5

3

*6

2 (5 + 2) * 3 52+3* 3* + 5 2 3

+

- inorder,

- preorder, - postorder.

2

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

- inorder,

16 4 + 5 * 9 7 - /

- preorder.

/ * + 16 4 5 - 9 7

- postorder.

Binarna stabla pretraživanja (Binary Search Tree) Efikasno implementiraju: traženje, ubacivanje i brisanje. Svi potomci lijevo od čvora imaju manju vrijednost ključa, a svi desno od njega veću. Stablo je konzistentno ako svi ključevi zadovoljavaju uvjet. Zbog jednostavnosti ne dozvoljavamo jednake ključeve. Operacije: ¤ Pretraživanje 1. Usporedimo traženu vrijednost (x) sa vrijednošću u korijenu (root). 2. x = r - pronašli smo, xr - traži u desnom podstablu.

- u najgorem slučaju pretraga traje do zadnjeg lista (leaf) u stablu (dubina stabla). Npr. Da nam je tražena vrijednost x = 8. 1 0

¤ Ubacivanje 1 4 za novu vrijednost (x) 6 1. Pretražimo stablo 2. Ako ga pronađemo kraj, inače (pretraga završi na nekom listu) 8 desno dijete. 4

7

1 2

umetnemo kao lijevo ili

2 0

¤ Brisanje - brisanje lista jednostavno, - brisanje čvora sa jednim djetetom slično, - brisanje čvore sa oba djeteta. Neka je B taj čvor: 1. Zamijenimo ključ od B sa ključem čvora X takvim da: a. X ima najviše jedno dijete, b. Brisanje čvora X ostavlja stablo konzistentnim. 5 2. Brišemo X - ima samo jedno dijete. - čvor X mora biti velik barem kao ključevi u lijevom podstablu

od B i manji od svih ključeva u desnom podstablu od B (najveći ključ u lijevom podstablu od B). X se naziva prethodnik od B i sigurno nema desno dijete. Zašto? 1 0

1 0

6

4

1 2

7

8

1 3

6

1 4

2 0

4

1 3

1 2

7

2 0

8

Složenost Složenost svih operacija nad stablom ovisi o obliku stabla. U najgorem slučaju pretražujemo do dna stabla. Ostale operacije su u konstantnom vremenu (prespajanja, zamjene). Sve operacije ovise o pretrazi. Ako je stablo balansirano očekujemo da je visina  lg (n + 1) , gdje je n broj čvorova. Ubacivanje u slučajnom poretku nam daje visinu O(log n), preciznije 2ln n. Najgori slučaj - stablo degenerira u listu. Npr. ubacivanje sortiranih brojeva. Brisanja također mogu debalansirati stablo - uvijek uzimamo prethodnika. Slučajna 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 slučaju za sve operacije. Trošimo dodatno vrijeme kod ubacivanja i brisanja da bi balansirali stablo. Vrijeme za balansiranje ne smije prijeći O(log n). AVL stablo je binarno stablo pretraživanja takvo da je za svaki čvor razlika u visini lijevog i desnog podstabla najviše 1. Visina AVL stabla sa n unutrašnjih čvorova je:

lg(n + 1) < h < 1.4404 lg(n + 2) - 0.3277 Složenost za pretraživanje 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 inače rebalansiramo.

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF