You are on page 1of 54

DINAMIČKO

PROGRAMIRANJE
Dinamičko programiranje
Na prethodnim časovima razmatrali smo vrlo elegantne algoritamske strate-
gije, kao što su podeli-i-savladaj, pretraživanje grafova ili pohlepni algoritmi.
Ove strategije obezbeđuju oruđa za rešavanje čitave lepeze važnih računarskih
problema, ali ipak, mogu se primeniti na vrlo specifične tipove problema.
Dinamičko programiranje i linearno programiranje, o kojima ćemo govo-
riti na narednim časovima, predstavljaju algoritamske tehnike sa vrlo širokim
poljem primene, koje se mogu koristiti onda kada pomenuti specijalizovani
metodi ne daju rezultate.
Međutim, ta opštost dinamičkog i linearnog programiranja je često praćena
smanjenjem efikasnosti (u odnosu na prethodne metode).
Dinamičko programiranje
Inače, kao što ćemo videti, sam termin dinamičko programiranje nema mnogo
zajedničkog sa onim što obično zovemo programiranjem (pisanje koda).
Dinamičko programiranje, kao i sam termin, uveo je 1950tih godina Ričard
Belman (Richard Bellman), u vreme kada se programiranjem u današnjem
smislu bavilo samo nekoliko ljudi i kada još nije postojao poseban termin za to.
Termin „programiranje“ koji se javlja u gornjem nazivu se zapravo može shvati-
ti kao „planiranje“, jer je „dinamičko programiranje“ zamišljeno kao optimalno
planiranje višestepenih procesa (multistage processes).
Sličnu etimologiju ima i termin „linearno programiranje“, o kome će kasnije
biti reči.
Najkraći putevi u DAG-u (još jednom)
Kada smo ranije razmatrali problem najkraćih puteva, istakli smo da je taj
problem posebno lak kada se radi sa usmerenim acikličnim grafovima – DAG-
ovima.
Reći ćemo još par reči o Dag-ovima, jer oni leže u samom srcu dinamičkog
programiranja.
Posebno važno svojstvo DAG-ova je to da mogu biti linearizovani – mogu
biti aranžirani na pravoj tako da sve grane idu isključivo sleva na desno (Fig.
6.1).
Najkraći putevi u DAG-u (još jednom)

Pretpostavimo da imamo zadatak da izračunamo rastojanja od čvora 𝑆 do


svih ostalih čvorova. Rauzmotrimo, na primer, čvor 𝐷.
Jedini način da se dođe do čvora 𝐷 je da se to uradi preko jednog od njegovih
prethodnika, čvora B ili C , pa treba samo da uporedimo te dve rute:

Do sličnih formula možemo doći i u slučaju svih ostalih čvorova.


Najkraći putevi u DAG-u (još jednom)

Ako računamo vrednosti dist sleva na desno kao na Slici 6.1, onda možemo
uvek biti sigurni da u trenutku kada smo došli do čvora 𝑣 mi već imamo sve
potrebne informacije da bi izračunali 𝑑𝑖𝑠𝑡(𝑣).
Dakle, sve distance možemo izračunati u jednom prolazu
Najkraći putevi u DAG-u (još jednom)
Primetimo da ovaj algoritam rešava kolekciju podproblema {𝑑𝑖𝑠𝑡(𝑢) | 𝑢 ∈ 𝑉}.
Mi krećemo od najmanjeg od njih, 𝑑𝑖𝑠𝑡(𝑠), za koji znamo da će rešenje biti 0.
Onda postepeno prelazimo na „veće“ podprobleme – rastojanja do čvorova
koji su sve dalje i dalje od polaznog, u skladu sa linearizovanim DAG-om.
Zapravo, mi podproblem shvatamo onoliko većim koliko drugih podproblema
treba da rešimo pre no što pređemo na rešavanje tog podproblema.
To je veoma opšta tehnika. Za svaki čvor, izračunavamo neku funkciju koristeći
vrednosti prethodnika tog čvora.
U ovom slučaju ta funkcija je minimum suma, ali može biti i bilo šta drugo –
ako umesto minimuma uzmemo maksimum dobijamo najduže puteve, umesto
da sabiramo težine grana mi ih možemo množiti, i slično.
Definicija dinamičkog programiranja
Dinamičko programiranje je veoma moćna algoritamska strategija kojom se
problemi rešavaju na sledeći način:
▫ identifikuje se kolekcija podproblema glavnog problema
▫ ta kolekcija se linearizuje – podproblemi se rešavaju linearno, počev od
najmanjeg pa do najvećeg podproblema, našeg polaznog problema
▫ identifikuje se formula kojom se svaki podproblem izražava u funkciji
prethodnih, jednostavnijih podproblema

U dinamičkom programiranju nemamo eksplicitno prisutan DAG, ali u svakom


problemu dinamičkog programiranja on je implicitno prisutan, on je osnova
linearizacije o koju smo pomenuli
Čvorovi tog DAG-a su podproblemi koje smo definisali, dok grane predstavljaju
zavisnosti između podproblema – ako je, da bi rešili podproblem 𝐵, neophodno
da prethodno rešimo podproblem 𝐴, onda u tom DAG-u postoji grana iz 𝐴 u 𝐵.
U tom slučaju 𝐴 se smatra za podproblem manji od podproblema 𝐵.
Dinamičko programiranje vs. podeli-i-savladaj
Zanimljivo je uporediti dinamičko programiranje sa podeli-i-savladaj strategi-
jom, kod koje se takođe glavni problem rešava preko izvesnih podproblema.
Kod strategije podeli-i savladaj veći podproblemi se rešavaju rekurzivno,
pozivanjem na manje podprobleme.
U stablu rekurzije jedan isti problem može se javiti i rešavati veliki broj puta.
Međutim, eksponencijalni rast broja podproblema koji se rešavaju potire eks-
ponencijalni pad veličine problema.
Naime, problemi se dele na podprobleme koji su značajno manji od tog polaz-
nog problema (dva puta, tri puta, itd.)

Kakvi su podproblemi kod primene strategije dinamičkog programiranja?


Veličina podproblema nije mnogo manja od veličine polaznog problema.
Međutim, ovde se ne koristi rekurzija – činjenica da su podproblemi linearno
uređeni dovodi do toga da se nikada ne vraćamo na isti podproblem, pa se
svaki podproblem rešava samo jednom.
Najduži rastući podniz
Neka je dat niz brojeva 𝑎1 , … , 𝑎𝑛 .
Podniz tog niza je niz oblika 𝑎𝑖1 , 𝑎𝑖2 , … , 𝑎𝑖𝑘 , gde je 1 ≤ 𝑖1 < 𝑖2 < ⋯ < 𝑖𝑘 ≤ 𝑛.
Rastući podniz je takav podniz kod kojeg je svaki naredni član strogo veći od
prethodnog.
Kod problema najdužeg rastućeg podniza traži se da se iz datog niza izdvoji
rastući niz najveće moguće dužine.
Na primer, najduži rastući podniz niza 5, 2, 8, 6, 3, 6, 9, 7 je 2, 3, 6, 9:

Vratimo se na opšti problem i kreirajmo graf 𝑮 = (𝑽, 𝑬) dozvoljenih prelaza:


svakom elementu 𝑎𝑖 pridružimo čvor 𝑖, i formiramo usmerenu granu (𝑖, 𝑗) kad god
su 𝑎𝑖 i 𝑎𝑗 uzastopni elementi u nekom rastućem podnizu, tj. kad god je 𝑖 < 𝑗 i 𝑎𝑖 <
𝑎𝑗 (Figure 6.2).
Najduži rastući podniz

Primetimo sledeće:
1. Graf 𝐺 = (𝑉, 𝐸) je DAG, jer za svaku granu (𝑖, 𝑗) važi da je 𝑖 < 𝑗.
2. Postoji bijektivna korespondencija između rastućih podnizova i puteva u
tom DAG-u
Prema tome, problem nalaženja najdužeg rastućeg podniza je prosto problem
nalaženja najdužeg puta u DAG-u.
Najduži rastući podniz
Evo sada i algoritma:

𝐿(𝑗) je dužina najdužeg puta koji se završava u čvoru 𝑗 plus 1, odnosno dužina
najdužeg rastućeg podniza koji se završava sa 𝑎𝑗 (dodajemo 1 jer brojimo čvo-
rove a ne grane na putu).
Na isti način kao kod najkraćih puteva vidimo da svaki put u čvor 𝑗 mora da
prođe kroz neki od njegovih prethodnika, pa je 𝐿(𝑗) jednako 1 plus maksimum
𝐿(∙) vrednosti prethodnika čvora 𝑗.
Ukoliko nema grana u 𝑗, onda imamo maksimum nad praznim skupom, tj. 0.
Krajnji odgovor je je najveći 𝐿(𝑗), jer je dozvoljena bilo koja završna pozicija.
Opšta šema dinamičkog programiranja
Ovde možemo lepo da uočimo opštu šemu dinamičkog programiranja:
Da bi rešili polazni problem mi smo definisali kolekciju podproblema
𝐿 𝑗 1 ≤ 𝑗 ≤ 𝑛}
sa sledećim ključnim svojstvima:
1. Postoji uređenje podproblema – u ovom slučaju podproblemi su uređeni
po brojevima 𝑗.
2. Postoji relacija koja pokazuje kako rešiti podproblem ako znamo rešenja
manjih podproblema – u ovom slučaju to je relacija
𝐿 𝑗 = 1 + max 𝐿 𝑖 (𝑖, 𝑗) ∈ 𝐸}.

Ova ključna svojstva omogućavaju da se problem reši u jednom prolazu.


Najduži rastući podniz
Razmotrimo sada kompleksnost algoritma:
Najpre, potrebno je da budu poznati prethodnici čvora 𝑗, za šta se može isko-
ristiti lista susedstva reverznog grafa 𝐺 𝑅 , koja se može konstruisati u linear-
nom vremenu.
Tada izračunavanje 𝐿(𝑗) traje vreme proporcionalno ulaznom stepenu čvora 𝑗,
i to daje ukupno vreme koje je linearno u odnosu na |𝐸|, što ne može da bude
veće od 𝑂(𝑛2 ).
Postoji još jedna stvar koju treba rešiti: 𝐿(∙) vrednosti nam kažu samo dužinu
optimalnog podniza, ali nam ne daju sam taj podniz.
Međutim, i ovde pri izračunavanju 𝐿(𝑗) možemo definisati i funkciju 𝑝𝑟𝑒𝑣(𝑗) –
pretposlednji čvor na najdužem putu u 𝑗.
Drugim rečima, 𝑝𝑟𝑒𝑣(𝑗) je ono 𝑖 za koje 𝐿 𝑖 (𝑖, 𝑗) ∈ 𝐸} dostiže maksimum.
Kao i kod najkraćih puteva, funkcijom 𝑝𝑟𝑒𝑣(∙) rekonstruišemo najduži rastući
podniz.
Edit distanca
Kada spel-čeker uoči moguću grešku u spelovanju, on u svom rečniku traži
reči koje su bliske pročitanoj reči.
Kako u tom slučaju definisati pojam bliskosti reči, tj. rastojanja između reči?
Prirodna mera rastojanja između dve reči je mera do koje ih možemo porav-
nati, gde je poravnanje prosto način pisanja reči jedne iznad druge.
Na primer, postoje dva moguća poravnanja reči SNOWY i SUNNY:

Znak – označava prazninu, koju možemo ubaciti bilo gde u reč.


Cena poravnanja je broj kolona u kojima se reči razlikuju.
Edit distanca između dve reči je cena najboljeg mogućeg poravnanja, onog sa
najmanjom cenom.
Za reči SNOWY i SUNNY ne postoji bolje poravnanje od onog sa cenom 3.
Edit distanca
Edit distanca se tako zove jer predstavlja minimalan broj edit operacija koje
treba primeniti da bi se jedna reč transformisala u drugu.
Edit operacije su
▫ umetanje karaktera – insertion
▫ brisanje karaktera – deletion
▫ zamena jednog karaktera drugim – substitution

Na primer, poravnanje u prethodnom primeru sa cenom 3 odgovara edit


operacijama: umetanje 𝑈, zamena 𝑂 → 𝑁, i brisanje 𝑊.

U opštem slučaju, postoji mnogo mogućih poravnanja dveju reči i bilo bi užasno
neefikasno pretraživati sva moguća poravnanja da bi našli najbolje.

Umesto toga, primenićemo tehniku dinamičkog programiranja.


Edit distanca
Kada problem rešavamo dinamičkim programiranjem, ključno pitanje je: Kako
izabrati podprobleme?
Ukoliko su podproblemi izabrani tako da zadovolje napred navedene ključne
uslove, onda je lako napisati algoritam: iterativno rešavamo podprobleme je-
dan za drugim, u onom redosledu kako im raste veličina.
Naš zadatak je da pronađemo edit distancu između reči 𝑥[1 ⋯ 𝑚] i 𝑦[1 ⋯ 𝑛].
Šta bi ovde bio dobar podproblem?
Na primer, mogli bi da tražimo edit distancu između nekog prefiksa prve reči,
𝑥[1 ⋯ 𝑖] , i nekog prefiksa druge reči, 𝑦 1 ⋯ 𝑗 .
Označimo tu edit distancu sa 𝐸(𝑖, 𝑗). Naš krajnji cilj je da nađemo 𝐸(𝑚, 𝑛).
Edit distanca
Pošto smo odredili podprobleme, naš sledeći zadatak je da na neki način izra-
zimo 𝐸(𝑖, 𝑗) preko manjih podproblema.
Pretpostavimo da imamo najbolje poravnanje reči 𝑥[1 ⋯ 𝑖] i 𝑦[1 ⋯ 𝑗], i posma-
trajmo poslednju kolonu u tom poravnanju.
Moguć je jedan od sledeća tri slučaja:
𝑥[𝑖] − 𝑥[𝑖]
ili 𝑦[𝑗] ili
− 𝑦[𝑗]

U prvom slučaju imamo da ta kolona nosi cenu 1, i ostaje da poravnamo i na-


đemo edit distancu za reči 𝑥[1 ⋯ 𝑖 − 1] i 𝑦 1 ⋯ 𝑗 , tj. 𝐸(𝑖 − 1, 𝑗).
U drugom slučaju imamo takođe cenu 1 za poslednju kolonu, i preostaje da
nađemo edit distancu za reči 𝑥[1 ⋯ 𝑖] i 𝑦 1 ⋯ 𝑗 − 1 , tj. 𝐸(𝑖, 𝑗 − 1).
U trećem slučaju za poslednju kolonu imamo cenu 1, ako je 𝑥[𝑖] ≠ 𝑦[𝑗], ili cenu
0, ako je 𝑥 𝑖 = 𝑦[𝑗], i tražimo edit distancu za 𝑥[1 ⋯ 𝑖 − 1] i 𝑦 1 ⋯ 𝑗 − 1 , od-
nosno imamo 𝐸(𝑖 − 1, 𝑗 − 1).
Edit distanca
Prema tome, relacija koju smo tražili je

𝐸 𝑖, 𝑗 = min{1 + 𝐸 𝑖 − 1, 𝑗 , 1 + 𝐸 𝑖, 𝑗 − 1 , diff 𝑖, 𝑗 + 𝐸 𝑖 − 1, 𝑗 − 1 }

pri čemu je
0 ako je 𝑥 𝑖 = 𝑦[𝑗]
diff 𝑖, 𝑗 = ቊ .
1 ako je 𝑥 𝑖 ≠ 𝑦[𝑗]

Na primer, u izračunavanju edit distance za EXPONENTIAL i POLYNOMIAL,


za 𝐸 4,3 koji odgovara prefiksima EXPO i POL u poslednjoj koloni imamo
O − O
ili L ili
− L
odakle dobijamo da je 𝐸 4,3 = min{1 + 𝐸 3,3 , 1 + 𝐸 4,2 , 1 + 𝐸 3,2 }.
Edit distanca
Sledeće pitanje je: Kako linearizujemo podprobleme 𝐸 𝑖, 𝑗 ?
Primetimo da podprobleme možemo predstaviti dvo-dimenzionalnom tabe-
lom, kao na sledećoj slici

Kao što smo videli, da bi izračunali 𝐸 𝑖, 𝑗 , neophodno je da prethodno izraču-


namo 𝐸 𝑖 − 1, 𝑗 − 1 , 𝐸 𝑖 − 1, 𝑗 i 𝐸 𝑖, 𝑗 − 1 .
Dakle, dobar je bilo koji redosled popunjavanja tabele koji to uvažava.
Edit distanca
Na primer, možemo popunjavati tabalu vrsta po vrstu,
ili kolona po kolonu, i jedno i drugo je u redu.
Tabela ima i nultu vrstu i kolonu, koje se popunjavaju
koristeći formule
𝐸 𝑖, 0 = 𝑖 i 𝐸 0, 𝑗 = 𝑗,
za sve 𝑖, 𝑗.
Evo koda za slučaj kada tabelu popunjavamo vrstu po vrstu
Edit distanca
Jasno, svaka ćelija u tabeli se popunjava u konstantnom vremenu, pa je
ukupno vreme rada jednako dimenziji tabele, tj. 𝑂(𝑚𝑛).
Za reči EXPONENTIAL i POLYNOMIAL popunjena tabela izgleda ovako

Dakle, edit distanca je jednaka 6, a optimalno poravnanje je


Edit distanca
Nameće se pitanje: Kako doći do ovakvog optimalnog poravnjanja? Iz same
tabele se to ne vidi!
Ključ je u vrednostima za diff 𝑖, 𝑗 , koje se u tabeli ne vide.
Međutim, možemo formirati DAG koji odgovara problemu koji rešavamo, iz
koga će se to videti.
Čvorovi DAG-a biće pozicije 𝑖, 𝑗 u tabeli, a prelazi su oblika

𝑖 − 1, 𝑗 → 𝑖, 𝑗 , 𝑖, 𝑗 − 1 → 𝑖, 𝑗 i 𝑖 − 1, 𝑗 − 1 → 𝑖, 𝑗 .

Pri tome, granama ćemo dodati i težine, tako da sve grane budu težine 1, osim
grana oblika 𝑖 − 1, 𝑗 − 1 → 𝑖, 𝑗 za koje je 𝑥 𝑖 = 𝑦[𝑗], tj. diff 𝑖, 𝑗 = 0.
U tom slučaju, konačno rešenje našeg problema je rastojanje između čvorova
𝑠 = (0,0) i 𝑡 = (𝑚, 𝑛) u tom DAG-u.
Najkraći putevi između 𝑠 i 𝑡 odgovaraju optimalnim poravnanjima.
Edit distanca
Evo kako izgleda DAG za reči EXPONENTIAL i POLYNOMIAL :

punim strelicama označene su grane


težine 1, isprekidanim grane težine 0
prelazima na dole odgovara operacija
brisanja
prelazima na desno odgovara opera-
cija umetanja
puni dijagonalni prelazi odgovaraju
operaciji zamenei
isprekidani dijagonalni prelazi odgo-
varaju poklapanju slova
Opšti tipovi podproblema
Nalaženje pogodnog načina za definisanje podproblema je najčešće stvar kre-
ativnosti, ali i elsperimentisanja.
Ipak, postoje i neke karakteristične situacije koje se češće javljaju i gde može-
mo upotrebiti neki od standardnih načina iz-bora podproblema.
1. Ulaz je 𝑥1 , 𝑥2 , … , 𝑥𝑛 , a podproblemi su prefiksi: 𝑥1 , 𝑥2 , … , 𝑥𝑖 , 1 ≤ 𝑖 ≤ 𝑛.

U ovom slučaju broj podproblema je linearan – 𝑂(𝑛).


2. Ulaz je 𝑥1 , 𝑥2 , … , 𝑥𝑚 i 𝑦1 , 𝑦2 , … , 𝑦𝑛 , a podproblemi predstavljaju parove
prefiksa: 𝑥1 , 𝑥2 , … , 𝑥𝑖 i 𝑦1 , 𝑦2 , … , 𝑦𝑗 , 1 ≤ 𝑖 ≤ 𝑚, 1 ≤ 𝑗 ≤ 𝑛.

U ovom slučaju broj podproblema je 𝑂(𝑚𝑛).


Opšti tipovi podproblema
3. Ulaz je 𝑥1 , 𝑥2 , … , 𝑥𝑛 , a podproblemi su infiksi: 𝑥𝑖 , 𝑥𝑖+1 , … , 𝑥𝑗 , 1 ≤ 𝑖 ≤ 𝑗 ≤ 𝑛.

U ovom slučaju broj podproblema je 𝑂(𝑛2 ).


4. Ulaz je korensko stablo, a podproblemi su korenska podstabla

Ako stablo ima 𝑛 čvorova, broj podproblema je 𝑂(𝑛).


Par reči o primenama edit distance
Na početku smo već rekli da edit distanca ima primene kod spel-čekera, i
uopšte, u obradi teksta.
Veoma značajne primene edit distance (kao i drugih metoda za merenje stepe-
na bliskosti reči) su i u izučavanju DNK sekvenci.
Naša tela su izuzetne mašine: fleksibilne u funkcionisanju, prilagodljive novom
okruženju, sposobne za interakciju i reprodukciju.
Sve te sposobnosti specifikovane su programom koji je jedinstven za svakog od
nas, DNK sekvencom od preko 3 milijarde karaktera – baznih parova AT i CG.
DNK sekvence svaka dva ljudska bića razlikuju se ne više od 0.1%, ali to je sas-
vim dovoljno da obezbedi široku raznilikost ljudi, jer se radi o oko 3 miliona
pozicija na kojima se DNK sekvence razlikuju.
Te razlike su od ogromne naučne i medicinske važnosti, na primer, one mogu
pomoći da se predvidi podložnost ljudi određenim bolestima.
DNK je ogroman i na izgled nedokučiv program, ali se može podeliti u podpro-
grame sa manje-više specifičnim funkcijama, koje nazivamno geni.
Par reči o primenama edit distance
Računari su postali ključno oruđe u razumevanju gena. S tim u vezi, postoji ne-
koliko tipičnih problema računarske biologije.
Kada se otkrije novi gen, jedan od načina da se stegne uvid u njegovo funkcio-
nisanje je da se nađu poznati geni koji se sa blisko poklapaju.
To je posebno ko-risno za transfer znanja sa dobro izučenih vrsti, kakvi su
miševi, na ljude.
Osnovni zadatak u tom pretraživanju je da se definiše efektivno izračunljiv po-
jam kada se dva niza približno poklapaju.
Biologija sugeriše uopštenje edit distance, a za njeno izračunavanje koristi se
dinamičko programiranje.
Osnovni problem u tom pretraživanju je ogroman broj poznatih gena – baza
gena GenBank ima dužinu veću od 1010 , i iz dana u dan taj broj brzo raste.
Trenutno najkorišćeniji softver u računarskoj biologiji je BLAST – kombinacija
algoritamskih trikova i biološke intuicije.
Problem ranca
Tokom pljačke, lopov je naišao na mnogo više od onog što je očekivao, i ne
može sve da ponese, pa treba da se odluči šta da ponese.

Njegov ranac može da ponese ukupnu težinu od 𝑊 kilograma. Postoji 𝑛 stav-


ki koje može da uzme, težina 𝑤1 , 𝑤2 , … , 𝑤𝑛 i vrednosti 𝑣1 , 𝑣2 , … , 𝑣𝑛 , u dolarima.

Napomenimo da će u našim razmatranjima sve ove vrednosti biti celobrojne.

Koja je najvrednija kombinacija stavki koju njegov ranac može da ponese?

Postoje dve verzije problema:


▫ sa ponavljanjem, kada može da se uzme neograničen broj primeraka
svake stavke,
▫ bez ponavljanja, kada se može uzeti samo jedan primerak.
Problem ranca
Na primer, možemo uzeti da je 𝑊 = 10 i

Stavka Težina Vrednost


1 6 30
2 3 14
3 4 16
4 2 9

Ako se radi o problemu sa ponavljanjem, onda je optimalno rešenje da se


uzme stavka 1 i dva primerka stavke 4, što daje ukupnu vrednost od $48.
Ako se radi o problemu bez ponavljanja, onda je optimalno da se uzmu stavke
1 i 3, sa ukupnom vrednošću $46.
Problem ranca sa ponavljanjem
Razmotrimo verziju problema koja dozvoljava ponavljanje.
Naravno, i ovde prvo pitanje glasi: Kako odrediti podprobleme?
U ovom slučaju postoje dva načina na koje možemo suziti problem:
▫ prvi je da potražimo manji kapacitet ranca 𝑤 ≤ 𝑊,
▫ drugi je da idemo na manji broj stavki, na primer 1, 2, … , 𝑗, za 𝑗 ≤ 𝑛.
Obično treba malo eksperimentisati da bi se videlo šta od toga prolazi.
Razmotrimo prvi način, sa smanjenjem kapaciteta ranca. Uvedimo definiciju
𝐾 𝑤 = maksimalna vrednost koja se postiže rancem kapaciteta 𝑤

Dalje, kako izraziti 𝐾 𝑤 preko podproblema?


Ako optimalno rešenje za 𝐾 𝑤 uključuje neku stavku 𝑖, onda uklanjanjem ove
stavke iz ranca ostaje optimalno rešenje za 𝐾 𝑤 − 𝑤𝑖 . Drugim rečima, imamo
da je 𝐾 𝑤 = 𝐾 𝑤 − 𝑤𝑖 + 𝑣𝑖 , za neko 𝑖.
Problem ranca sa ponavljanjem
Kako ne znamo tačno o kome 𝑖 se radi, moramo da probamo sve mogućnosti, tj.

𝐾 𝑤 = max {𝐾 𝑤 − 𝑤𝑖 + 𝑣𝑖 } ,
𝑖:𝑤𝑖 ≤𝑤

gde, kao i obično, koristimo dogovor da je maksimum nad praznim skupom 0.


Sada možemo da ispišemo algoritam:

Ovaj algoritam popunjava jedno-dimenzionalnu tabelu (vektor) dužine 𝑊 + 1,


sleva na desno. Za izračunavanje svake odrednice u toj tabeli potrebno je vreme
𝑂(𝑛), pa ukupno vreme potrebno za realizaciju iznosi 𝑂(𝑛𝑊).
Problem ranca bez ponavljanja
U ovom slučaju su nam podproblemi koje smo definisali u prethodnom slučaju
potpuno beskorisni.
Na primer, ništa nam ne znači da znamo da je vrednost 𝐾 𝑤 − 𝑤𝑖 vrlo visoka,
jer ne znamo da li je stavka 𝑖 već korišćena u tom parcijalnom rešenju.
Zbog toga ćemo podprobleme definisane u prethodnom slučaju dopuniti dodat-
nom informacijom o stavkama koje su već korišćene (da ih više ne bi koristili).

Dakle, dodajemo i drugi parametar 𝑗, gde je 0 ≤ 𝑗 ≤ 𝑛, i definišemo:

𝐾 𝑤, 𝑗 = maksimalna vrednost koja se postiže rancem kapaciteta 𝑤


i stavkama 1, … , 𝑗 .

Konačno rešenje koje tražimo je 𝐾 𝑊, 𝑛 .


Problem ranca bez ponavljanja
Kako sada izraziti podproblem 𝐾 𝑤, 𝑗 preko manjih podproblema?
Stavka 𝑗 može biti deo nekog optimalnog rešenja za 𝐾 𝑤, 𝑗 , i u tom slučaju je
𝐾 𝑤, 𝑗 = 𝐾 𝑤 − 𝑤𝑗 , 𝑗 − 1 + 𝑣𝑗 .
To se može desiti samo ako je 𝑤𝑗 ≤ 𝑤.
Sa druge strane, ako 𝑗 nije deo nijednog optimalnog rešenja za 𝐾 𝑤, 𝑗 , onda je
𝐾 𝑤, 𝑗 = 𝐾 𝑤, 𝑗 − 1 .
Oba slučaja mogu se spojiti u

𝐾 𝑤, 𝑗 = max 𝐾 𝑤 − 𝑤𝑗 , 𝑗 − 1 + 𝑣𝑗 , 𝐾 𝑤, 𝑗 − 1 .

Prema tome, podproblem 𝐾 𝑤, 𝑗 smo izrazili preko 𝐾 ∙, 𝑗 − 1 .


Problem ranca bez ponavljanja
Algoritam se sada sastoji iz popunjavanja dvo-dimenzionalne tabele sa 𝑊 + 1
vrstom i 𝑛 + 1 kolonom.
Za izračunavanje svake odrednice u toj tabeli potrebno je konstantno vreme, pa
iako je ova tabela mnogo veća nego u prethodnom slučaju, ukupno vreme reali-
zacije algoritma ostaje isto i iznosi 𝑂(𝑛𝑊).
Evo i koda algoritma
Memoizacija
Već smo rekli da rekurzija dobro radi sa strategijom podeli-i-savladaj jer u tom
slučaju imamo eksponencijalni pad veličine podproblema, što se potire sa eks-
ponencijalnim rastom broja podproblema koji se rešavaju.
Kod dinamičkog programiranja pad veličine podproblema nije tako veliki, pa
bi primena rekurzije u tom slučaju bila izuzetno neefikasna.
Međutim, postoji jedan trik koji može poboljšati rezultate upotrebe rekurzije –
kada rešimo jedan podproblem možemo zapamtiti rezultate, i prilikom slede-
ćeg rekurzivnog poziva tog podproblema ne moramo ga ponovo rešavati.
Ovaj trik naziva se memoizacija.
Na primer, kod problema ranca sa ponavljanjem, izračunate vrednosti za 𝐾(∙)
smeštamo u haš tabelu.
Svaki put kada se rekurzivno poziva neki 𝐾(𝑤), algoritam će prvo proveriti da
li je taj rezultat već zapamćen u tabeli, i jedino ako nije će vršiti izračunavanje.
Memoizacija
Evo koda i za to

Kako se nijedan problem nikad neće ponoviti, vreme realizacije je 𝑂(𝑛𝑊), isto
kao kod dinamičkog programiranja. U tom smislu memoizacija ne daje bolje
rezultate od dinamičkog programiranja.
Međutim, ima i slučajeva kada se memoizacija isplati. Naime, dinamičko pro-
gramiranje automatski rešava svaki podproblem koji bi mogao biti potreban,
dok memoizacija rešava samo one koji se zaista koriste.
Na primer, ako su 𝑊 i svi 𝑤𝑖 umnošci broja 100, onda je podproblem 𝐾(𝑤)
beskorisan ako 100 ne deli 𝑤, i memoizirani algoritam nikada neće doći u
situaciju da ga rešava.
Lanačno matrično množenje
Pretpostavimo da treba da pomnožimo četiri matrice 𝐴, 𝐵, 𝐶 i 𝐷.
Zbog asocijativnosti množenja matrica svi proizvodi u kojima se te matrice
javljaju u datom redosledu, čitano sleva na desno, su jednaki
Taj proizvod se obično označava bez zagrada, u obliku 𝐴 × 𝐵 × 𝐶 × 𝐷.
Na primer, ako su dimenzije matrica 50 × 20, 20 × 1, 1 × 10 i 10 × 100, tim
redom, onda je proizvod matrica tipa 50 × 100.
Lanačno matrično množenje
Međutim, bez obzira na to što se uvek dobija isti rezultat, redosled množenja
(raspored zagrada) može da utiče na brzinu izvršenja tog množenja
Množenje matrica tipa 𝑚 × 𝑛 i 𝑛 × 𝑝 zahteva 𝑚𝑛𝑝 množenja, i na sledeći način
možemo uporediti različite načine izračunavanja proizvoda

Prema tome, drugačiji redosled množenja dovodi do ogromnih razlika u


vremenu potrebnom za izvršenje množenja
Ako bi iskoristili pohlepni pristup, da uvek vršimo najjeftinije množenje, došli
bi do redosleda množenja u drugoj vrsti, koji očigledno nije optimalan
Lanačno matrično množenje
Dakle, ako su date matrice 𝐴1 , 𝐴2 , ... , 𝐴𝑛 , dimenzija redom 𝑚0 × 𝑚1 , 𝑚1 × 𝑚2 ,
..., 𝑚𝑛−1 × 𝑚𝑛 , problem je: Kako na optimalan način izračunati proizvod
𝐴1 × 𝐴2 × ⋯ × 𝐴𝑛 ?

Raspoređivanje zagrada na prirodan način može predstaviti pomoću binarnog


stabla gde polazne matrice odgovaraju listovima, konačan proizvod korenu, a
međuproizvodi odgovaraju unutrašnjim čvorovima
Lanačno matrično množenje
Svi mogući redosledi množenja odgovaraju svim punim binarnim stablima sa
𝑛 listova, čiji broj je eksponencijalan u odnosu na 𝑛
Da bi stablo bilo optimalno, i njegova podstabla moraju da budu optimalna
To nas dovodi do toga da primenimo dinamičko programiranje
Podproblemi bi bili proizvodi oblika 𝐴𝑖 × 𝐴𝑖+1 × ⋯ × 𝐴𝑗 , za 1 ≤ 𝑖 ≤ 𝑗 ≤ 𝑛.
Preciznije, podprobleme definišemo na sledeći način

𝐶 𝑖, 𝑗 = minimalna cena množenja za 𝐴𝑖 × 𝐴𝑖+1 × ⋯ × 𝐴𝑗

Veličina problema bio bi broj množenja matrica, tj. |𝑗 − 𝑖|.


Najmanji problem je kada je 𝑖 = 𝑗, kada nema šta da se množi, tj. 𝐶 𝑖, 𝑗 = 0.
Ako proizvod podelimo u dva dela 𝐴𝑖 × ⋯ × 𝐴𝑘 i 𝐴𝑘+1 × ⋯ × 𝐴𝑗 , za neko 𝑘 iz-
među 𝑖 i 𝑗, onda cena podstabla iznosi
𝐶 𝑖, 𝑘 + 𝐶 𝑘 + 1, 𝑗 + 𝑚𝑖−1 ∙ 𝑚𝑘 ∙ 𝑚𝑗
Lanačno matrično množenje
Međutim, mi treba da nađemo optimalno mesto sečenja 𝑘, zbog čega je

𝐶 𝑖, 𝑗 = min 𝐶 𝑖, 𝑘 + 𝐶 𝑘 + 1, 𝑗 + 𝑚𝑖−1 ∙ 𝑚𝑘 ∙ 𝑚𝑗
𝑖≤𝑘≤𝑗

Evo sada i koda, gde promenljiva 𝑠 predstavlja veličinu podproblema

Podproblemi čine dvo-dimenzionalnu tabelu, gde popunjavanje svake


odrednice traje 𝑂(𝑛), što znači da ukupno vreme iznosi 𝑂(𝑛3 ).
Najkraći putevi sa datim brojem grana
Uzmimo da je dat težinski graf 𝐺 i čvorovi 𝑠 i 𝑡, i razmotrimo sledeću varijantu
problema najkraćih puteva: Naći najkraći put iz 𝑠 u 𝑡 sa najviše 𝑘 grana

Dijkstrin algoritam ne pamti broj grana u najkraćem putu koji nalazi

Kod dinamičkog programiranja, podproblemi se mogu izabrati tako da se


sve vitalne informacije zapamte i prenose dalje

Za čvor 𝑣 i 𝑖 ≤ 𝑘 neka je

dist 𝑣, 𝑖 = dužina najkraćeg puta iz 𝑠 u 𝑣 sa 𝑖 grana

Startne vrednosti biće dist 𝑣, 0 = ∞, za 𝑣 ≠ 𝑠, i dist 𝑠, 0 = 0, a ostale


vrednosti se računaju po formuli

dist 𝑣, 𝑖 = min dist 𝑢, 𝑖 − 1 + 𝑙(𝑢, 𝑣)


(𝑢,𝑣)∈𝐸

Sada ostaje samo da se ispiše kod!


Flojd-Voršolov algoritam
Sada tražimo algoritam koji će računati najkraći put od 𝑠 do 𝑡, za sve parove
čvorova 𝑠 i 𝑡.

Algoritam koji smo ranije dali radio je u vremenu 𝑂( 𝑉 2 𝐸 ).

Ovde prikazujemo nešto brži Flojd-Voršolov algoritam (Floyd-Warshall),


baziran na dinamičkom programiranju, koji radi u vremenu 𝑂( 𝑉 3 ).

Najkraći put 𝑢 → 𝑤1 → ⋯ → 𝑤𝑙 → 𝑣 između 𝑢 i 𝑣 koristi izvestan broj među-


čvorova (moguće nijedan)

Ideja je sledeća: najpre ćemo zabraniti sve međučvorove, i u tom slučaju naj-
kraći put između 𝑢 i 𝑣 je prosto grana (𝑢, 𝑣), ako postoji.

Zatim možemo postepeni širiti skup dozvoljenih međučvorova, dodavanjem


jednog po jednog čvora, i ažurirati dužine najkraćih puteva u svakom koraku.

Kada taj skup dozvoljenih međučvorova poraste do celog skupa čvorova 𝑉,


onda smo dobili prave najkraće puteve između čvorova grafa.
Flojd-Voršolov algoritam
Preciznije, neka je 𝑉 = 1, 2, … , 𝑛 , i neka je sa dist(𝑖, 𝑗, 𝑘) označena dužina
najkraćeg puta iz 𝑖 u 𝑗 preko međučvorova iz skupa 1, 2, … , 𝑘
Inicijalno, dist(𝑖, 𝑗, 0) je dužina (težina) grane (𝑖, 𝑗), ako takva grana postoji,
odnosno dist 𝑖, 𝑗, 0 = ∞, u suprotnom.
Šta se dešava kada u skup dozvoljenih međučvorova uključimo čvor 𝑘?
Onda moramo ponovo razmotriti sve parove 𝑖, 𝑗, i proveriti da li nam korišće-nje
čvora 𝑘 kao međučvora daje kraći put iz 𝑖 u 𝑗.
Najkraći put iz 𝑖 u 𝑗 koji koristi 𝑘 i druge čvorove iz 1, … , 𝑘 − 1 prolazi kroz 𝑘
samo jednom (razmatramo slučaj kada nema negativnih ciklusa)
Pri tome, mi smo već izračunali najkraće puteve iz 𝑖 u 𝑘 i 𝑘 u 𝑗 preko 1, … , 𝑘 − 1
Flojd-Voršolov algoritam

Korišćenje čvora 𝑘 daje nam kraći put iz 𝑖 u 𝑗 samo ako je


dist 𝑖, 𝑘, 𝑘 − 1 + dist 𝑘, 𝑗, 𝑘 − 1 < dist(𝑖, 𝑗, 𝑘 − 1)
i u tom slučaju odmah treba da ažuriramo dist(𝑖, 𝑗, 𝑘).
Evo sada Flojd-Voršolovog algoritma, koji očigledno radi u vremenu 𝑂( 𝑉 3 ).
Problem trgovačkog putnika
Traveling salesman problem (TSP)
Zamislimo da trgovački putnik treba da krene iz svog grada, da obiđe sve okol-
ne gradove, pri čemu će svaki posetiti tačno jednom, i da se vrati u svoj grad.
Postavlja se pitanje: Koji je optimalni redosled kojim treba da poseti te grado-ve
tako da pređe minimalni put?

Optimalni redosled je 𝐴𝐷𝐵𝐸𝐶𝐴, a pređeno rastojanje je 1 + 2 + 3 + 2 + 2 = 10


Problem trgovačkog putnika
Označimo gradove sa 1, … , 𝑛, i neka je 1 polazni grad.
Neka je 𝐷 = 𝑑𝑖𝑗 matrica rastojanja između gradova.
Treba isplanirati turu koja kreće i završava u 1, svaki grad obilazi jednom, i ima
minimalnu ukupnu dužinu.
Videćemo da ovaj problem nije baš lak. Za sada nije poznat algoritam koji bi ga
rešio u polinomijalnom vremenu, i veruje se da takav algoritam i ne postoji.
Ako bi primenili strategiju grube sile (brute-force), i procenili sve moguće
ture, kojih ima 𝑛 − 1 !, dobili bi algoritam koji radi u vremenu 𝑂 𝑛! .
Ovde ćemo videti da tehnika dinamičkog programiranja može dati brži algo-
ritam, mada ni on nije polinomijalni – radi u vremenu 𝑂 𝑛2 2𝑛 .

Kako ovde na pogodan način definisati podprobleme?


Pretpostavimo da smo krenuli iz grada 1, obišli neke gradove, i stigli u grad 𝑗.
Koje su nam informacije potrebne za nastavak te parcijalne ture?
Problem trgovačkog putnika
Pored toga što znamo grad 𝑗, treba da znamo i sve gradove koje smo posetili do
sada, da ih ne bui ponovo posećivali.
Dakle, podprobleme bi mogli definisati na sledeći način:

Za podskup 𝑆 ⊆ 1, 2, … , 𝑛 koji sadrži 1 i 𝑗, neka je 𝐶(𝑆, 𝑗) dužina


najkraćeg puta koji kreće iz 1, završava se u 𝑗, i posećuje svaki
čvor iz skupa 𝑆 tačno jednom.

Kada je 𝑆 > 1, tada definišemo 𝐶 𝑆, 1 = ∞, jer put ne može istovremeno


započeti i završiti se u 1.
Ako je 𝑖 pretposlednji grad na putu od 1 do 𝑗, tada je ukupno pređeni put jednak
rastojanju 𝐶 𝑆 ∖ 𝑗 , 𝑖 od 1 do 𝑖, plus 𝑑𝑖𝑗 – dužina poslednje grane na tom putu
Potom tražimo najbolje takvo 𝑖, tj.

𝐶 𝑆, 𝑗 = min 𝐶 𝑆 ∖ 𝑗 , 𝑖 + 𝑑𝑖𝑗
𝑖∈𝑆 ∖ 𝑗
Problem trgovačkog putnika
Podproblemi su uređeni po |𝑆| – broju elemenata skupa 𝑆

I sada evo koda

Postoji najviše 2𝑛 ∙ 𝑛 podproblema, i svaki se rešava u linearnom vremenu. To


znači da je ukupno vreme realizacije 𝑂(𝑛2 2𝑛 ).
Vreme realizacije i potrebna memorija
Vreme potrebno za realizaciju algoritma dinamičkog programiranja može se
uočiti iz DAGa podproblema.
U mnogim slučajevima to je prosto ukupan broj grana u DAGu.
Sve što zapravo radimo je da posećujemo čvorove u linearizovanom redosledu,
ispitujemo sve ulazne grane za taj čvor, i, najčešće, trošimo konstantno vreme
po grani.
Konačno, svaka grana DAGa se ispituje jednom.
Međutim, koliko memorije je potrebno?
Ne postoji jednostavan parametar DAGa za koji se to može vezati.
Sigurno je moguće odraditi posao sa memorijom proporcionalnom broju čvoro-
va (podproblema), ali nam obično treba mnogo manje.
Naime, vrednost određenog podproblema treba da se zapamti samo dok se ne
reše veći problemi koji zavise od njega, posle toga memorija se može osloboditi
za ponovno korišćenje.
Vreme realizacije i potrebna memorija
Na primer, u Flojd-Voršolovom algoritmu vrednost za dist(𝑖, 𝑗, 𝑘) nam treba
sve dok se izračunavaju vrednosti dist(∙,∙, 𝑘 + 1)

Prema tome, potrebne su nam samo dva |𝑉| × |𝑉| niza da smestimo dist vred-
nosti – jedan za neparne vrednosti 𝑘, a drugi za parne vrednosti.

Kada izračunavamo dist(𝑖, 𝑗, 𝑘) vrednosti beležimo preko dist(𝑖, 𝑗, 𝑘 − 2)


Nezavisni skupovi u stablima
U grafu 𝐺 = (𝑉, 𝐸), skup čvorova S ⊂ 𝑉 se naziva nezavisan skup ako ne posto-
je grane između njih.
Na primer, na donjoj slici 1, 5 je nezavisan skup, ali 1, 4, 5 to nije, jer postoji
grana između 4 i 5. Najveći nezavisni skup je 2, 3, 6 .

Kao i za neke druge probleme koji se rešavaju dinamičkim programiranjem


(problem ranca, problem trgovačkog putnika), i za probem nalaženja najvećeg
nezavisnog skupa u grafu se veruje da je računarski težak.
Međutim, kada je taj graf stablo, problem se može rešiti u linearnom vremenu,
dinamičkim programiranjem.
Nezavisni skupovi u stablima
Podprobleme bi smo mogli definisati pomoću podstabala sa korenima u svim
čvorovima stabla:
𝐼 𝑢 = veličina najvećeg nezavisnog skupa u podstablu sa korenom 𝑢

Jasno, naš krajnji cilj je 𝐼 𝑟 , gde je 𝑟 koren celog stabla.


Pretpostavimo da znamo najveće nezavisne skupove za sva podstabla ispod ne-
kog čvora 𝑢, tj. da znamo 𝐼 𝑤 za sve potomke čvora 𝑢. Kako izračunati 𝑰 𝒖 ?
Ako nezavisni skup sadrži 𝑢, tada ga ubrajamo, i kako taj nezavisni skup ne
može sadržati decu od 𝑢 , mi onda prelazimo na istraživanje unuka od 𝑢 .
Sa druge strane, ako nezavisni skup ne sadrži 𝑢, onda ga ne ubrajamo, ali može-
mo preći na istraživanje njegove dece. Dakle

𝐼 𝑢 = max 1 + ෍ 𝐼(𝑤) , ෍ 𝐼(𝑤)


unuci 𝑤 od 𝑢 deca 𝑤 od 𝑢

Broj podproblema je broj čvorova, a vreme realizacije je linearno – 𝑂( 𝑉 + |𝐸|)

You might also like