Upload
nemanja-petrovic
View
189
Download
9
Embed Size (px)
DESCRIPTION
Semiraski radi iz dinamickog programiranja
Citation preview
Univerzitet u Niu Prirodno matematiki fakultet
Seminarski rad
Dinamiko programiranje
Autor: Mentor:
Nemanja Petrovi Prof. dr Predrag Stanimirovi
Februar 2014.
Ni
2
Sadraj ta je dinamiko programiranje .3
Rekurzija .6
BackTrack algoritam ..6
Memoizacija 7
Formulacija dinamikog programiranja 7
Raunanje n-tog lana Fibonaijevog niza ..9
Probem ranca 10
Problem najdueg podniza karaktera ..12
Red za karte 13
Najdui neopadajudi podniz ...................................................................................................................14
3
ta je dinamiko programiranje
Programiranje kao oblast matematike je veoma opirna oblast koja se razvila tek sredinom ovog
veka, nalazei veliku primenu na kompjuterima i kompjuterskoj tehnologiji. Programiranje kao
metod moe se definisati kao niz postupaka ili procedura koje vode do eljenog rezultata. Pojam
algoritma moemo isto ovako definisati. To je spisakuputstava koji opisuje izvravanje nekog
problema, tako da svako uputstvo iz spiska, koje nazivamo i korak algoritma, mora da bude neka
dobro poznata operacija. Algoritam moe biti bilo koja radnja koju ovek svakodnevno izvrava.
To na primer moe biti: kuvanje ruka, popravljanje kola, pakovanje stvari itd.
Primer algoritma za zamenu toka na automobilu:
1.podigni auto
2.skini toak
3.ako je rezervni toak pripremljen idi na korak 5.
4.pripremi rezervni toak
5.namesti rezervni toak
6.spusti auto
to se tie dinamikog programiranja njega moemo definisati kao skup svih algoritama tj.
reenja problema koji imaju jednu zajedniku osobinu. Ta osobina je da do krajnjeg (eljenog)
rezultata dolazimo pomou pomonih koja imaju zajednike podprobleme, ije rezultate upisuj
emo u memoriju, izbegavajui reavanje podproblema vie puta. Da bi dobili reenje naeg
problema potrebno je nai reenja svih podproblema. Naravno da i podproblemi imaju svoje
podprobleme koji se mogu preklapati. Na primer podproblemi X i Y mogu imati za
jedniki podproblem Z, tako da su podproblemi X i Y uslovljeni podproblemom Z, tj. da bi reili
podprobleme X i Y neophodno je prethodno reiti podproblem Z.
Sve ovo moemo ilustrovati primerom. Potrebno je sagraditi zid u obliku trougla. Zid gradimo iz
kocki jednake veliine tako to u prvi red (najnii red na samoj zemlji) naslaemo neki broj
kocki jednu pored druge u jednoj liniji, tako da se svake dve susedne kocke dodiruju svojim
bonim stranama. U svaki sledei red stavljamo jednu kocku manje, tako da se svake dve u tom
redu takoe dodiruju i da svaka kocka stoji na dve iz prethodnog reda. Zavravamo sa zidanjem
kada u poslednji red tj. najvii red stavimo poslednju kocku. Na zid bi onda izgledao ovako:
4
Neka se na problem sastoji u tome da izgradimo zid odnosno da moemo postaviti poslednju
kocku na vrh. Uslovi koji se podrazumevaju jesu da je sila gravitacije prisutna, odnosno da za
svaku kocku moramo imati po dve donje na kojoj ona stoji.
Analogija izmeu ovog zida i dinamikog programiranja je u sledeem. Neka se na problem
sastoji u tome da postavimo poslednju kocku na zid, to jest da sagradimo ceo zid. Da bi mogli da
stavimo poslednju kocku neophodno je staviti one dve kocke koje su neposredno ispod nje. Da bi
stavili te dve kocke potrebno je staviti one tri kocke koje su ispod njih itd. Moemo zakljuiti da
za svaku kocku vai pravilo da njeno ugraivanje u zid zahteva prethodno stavljanje kocki koje
su neposredno ispod nje, a poto svaka kocka koja je neposredno ispod, zahteva to isto od onih u
niem redu, itd, zakljuujemo da postavljanje odreene kocke u zid zahteva stavljanje itavog
niza kocki. To naravno ne vai za one kocke koje su pri samom dnu.
Ovo bi se moglo nazvati analizom problema. Posle ovoga moemo prei na implementaciju. Da
bi napravili zid, zakljuujemo prema prethodnoj analizi, da je to ostvarivo ako budemo reali red
po red poev od najnieg do najvieg. Svaki red je uslovljen prethodnim redom. Poto prvi red
nije uslovljen ni jednim, kreemo od njega. Posle njega postavljamo red koji je neposredno iznad
njega, zatim onaj sledei i tako dalje dok ne stavimo najvii red odnosno poslednju kocku na zid.
Analogno sa stavljanjem kocki na zid jeste reavanje podproblema u dinamikom
programiranju. Svakoj kocki odgovara jedan podproblem ili ceo problem ako je u pitanju kocka
na samom vrhu. Analogno prvom redu kocki jeste skup onih podproblema koji nisu uslovljeni ni
jednim podproblemom odnosno oni podproblemi koji ne zahtevaju nikakve prethodne
podprobleme. Posle njih reavamo one podprobleme koji su uslovljeni ovim prvim
podproblemima. Posle njih reavamo one probleme koji su uslovljeni ovim prethodnim i tako
dalje dok ne doemo do reenja krajnjeg problema. To je analogno slaganju red po red kocki na
zid dok ne doemo do one poslednje. Zbog ovoga se reavanje problema dinamikim
programiranjem zove i bottom-up (odozdo-nagore), poto kreemo da reavamo prvo one najnie
probleme, pa onda one sve vie (ako naravno zamislimo hijerarhiju problema kao to je kod zida
od kocki).
5
ta ovo praktino znai? Ilustrujmo to na sledeem primeru. Neka na problem bude da
naemo lan A(1,1) kvadratne matrice dimenzije N, ako su nam dati lanovi A(N,I) (za svako
1IN, a N je veliina matrice), i zakonitost F tako da je A(I,K)=F(A(I+1,K),A(I+1,K+1)),
(1KI
6
Rekurzija
Rekurzija je metod u kome programske procedure ili funkcije pozivaju same sebe. Odmah se
namee pitanje: Zar to nije beskonana petlja? (petlja koja se izvrava beskonano dugo i iz koje
nema izlaza). Meutim, to ovde nije sluaj, zato to u samoj proceduri, odnosno funkciji
postavljamo ogranienje tj. uslov koji ako je ispunjen, procedura poziva samu sebe.
Rekurzivne procedure i funkcije zaista nije teko napisati, ali one imaju jedan veliki nedostatak.
To je vreme izvravanja programa. Uzmimo za primer ovaj program za nalaenje N -tog
Fibonaijevog broja. Za nal aenje prva dva elementa niza troimo minimalno vreme, vreme
potrebno da se broj upie na dato mesto. Za nalaenje treeg lana takoe troimo minimalno
vremena zato to je to najmanje mogue vreme za izraunavanje tog broja, to je sumiranje prva
dva elementa niza. Ve kod nalaenja etvrtog elementa nastaje viak raunanja.
Kako?
Da bi izraunao etvrti element, program poziva prvo funkciju Fib(4), a ona poziva Fib(2) i
Fib(3) . Broj Fib(2) se odmah nalazi jer je on inicijalno dat, dok sam poziv Fib(3) poziva Fib(2) i
Fib(1). Vidimo da ovde ve dolazi do preklapanja poziva to dovodi do usporavanja rada
programa. Ako bi razmatrali pozive Fib(5) i Fib(6) videli bi da ima sve vie i vie preklapanja
poziva to dovodi do sve veeg usporavanja rada programa. Vreme potrebno da se izrauna N-ti
Fibonaijev broj, ovim postupkom, proporcionalno je jednako broju K^N, gde je K priblino
jednako Fibonaijevom broju F=1.618... To jest da bi program doao do ovog broja potrebno je
priblino K^N poziva funkcije Fib. Da bi se definitivno ubedili da je ovo veoma sporo, moemo
izraunati vreme potrebno da se izrauna 100 -ti Fibonaijev broj. Potrebno je, po prethodno
datoj formuli K^10 poziva. Uzmimo da je K=F, i dobija se da je broj poziva funkcije priblino
jednak 10^20 . Poto u jednoj sekundi imamo odprilike oko 10^6 poziva, dobijamo da je vreme
priblino jednako 10^14 sekundi to je nekoliko miliona godina.
BackTrack algoritam
Poto smo se upoznali sa rekurzijom moemo objasniti BackTrack algoritam. To je takozvano
pretraivanje sa vraanjem. Kod ovog algoritma ispitujemo sve mogue kombinacije (sva
mogua reenja problema) i odabiramo ono pravo, tj. optimalno reenje. BackTrack-om moemo
reiti skoro svaki problem, ali on ima jedan veliki nedostatak, a to je vreme izvravanja
programa. Vreme izvravanja algoritma koji se bazira na BackTrack-u se smatra
eksponencijalnim, a to nije dobro zato to je vreme izvravanja takvih algoritama
eksponencijalno veliko za neke vee test primere.Na primer, BackTrack-om moemo reavati
problem lavirinta, tako to emo traiti sve mogue puteve i od njih odabrati onaj pravi ili
najkrai, u zavisnosti ta traimo. To radimo tako to od svakog polja lavirinta isprobavamo
sva susedna polja koja su prazna, na koja moemo stati. Ako je polje prazno, idemo na njega, i
na njemu primenjujemo isti postupak. Moramo postaviti jedno ogranienje, a to je da ne idemo
7
na ona polja na kojima smo ve bili. To moemo uraditi jednostavno markiranjem polja na
kojima smo bili. Znai svako polje koje posetimo obojimo (na primer u crno ako su sva ostala
neposeena polja bela).
Memoizacija
Memoizacija je sklop dinamikog programiranja i rekurzije. Sama tehnika je top-down, kao kod
rekurzije, ali sva reenja pamtimo u memoriji, tako da ne dolazi do reavanja istog problema vie
puta. Dinamiku matricu, tj. deo memorije u koji smetamo reenja problema, na poetku
inicijalizujemo sa nekim nemoguim brojem za reenje (na primer sa 1, ako su reenja svih
problema pozitivna). Kada dolazimo do nekog problema, prvo pogledamo da li smo ga reili
(ako na njemu nije inicijalni broj). Ako je ve reen, ne raunamo ga ponovo, ve samo uzimamo
reenje za dalje reavanje problema, a ako jo nije reen, reavamo ga. Metod memoizacije se
pokazao kao veoma povoljan, a u nekim sluajevima je i bri od dinamikog programiranja. To
je zbog toga to dinamikim programiranjem reavamo sve mogue podprobleme, a
memoizacijom samo one koji su nam potrebni
Formulacija dinamikog programiranja
Jedino to preostaje da se kae jeste kako formulisati problem koji se reava dinamikim
programiranjem, to jest, reiemo neto o formulaciji samog problema. Svakom problemu koga
moemo reiti dinamikim programiranjem moemo pridruiti odreene parametre, kao i
svakom njegovom podproblemu. Na primer, za odreivanje N-tog Fibanaijevog broja
Fib(N), jedini parametar potreban da naemo reenje je N, to jest njegov redni broj u nizu.
Naravno nije uvek sluaj da problem ima samo jedan parametar, to emo videti iz narednih
primera. Znai u optem sluaju dinamikog programiranja svaki problem moemo formulistai
kaoP(p1,p2...pk), gde je P problem, a p1,p2...pk parametri problema. U najveem broju sluajeva
k nije vee od 3, to jest problem ima najvie tri parametra. Broj parametara vai ne samo za
glavni problem, nego ve i za sve ostale podprobleme, tako da svaki podproblem, kao i sam prob
lem imajedinstvenu k-torku parametara koja jednoznano oznaava taj problem. Moemo uvideti
da smisao dinamikog programiranja gubi smisao za veliko k(zbog manjka memorije), pa se
onda uprkos maloj brzini algoritma pribegavametodi top-down
Znai, prvi korak pri reavanju problema jeste odreivanje parametara problema i nalaenje one
k-torke parametara koja odgovara glavnom problemu. Za Fibonaijev niz to bi bio samo broj N.
Potrebno je jo neto istai, a to je da je potrebno pri implementaciji programa formirati k-
dimenzionalnu strukturu, tj. matricu u koju emo smetati reenja podproblema. Za svaki
parametar problema potrebna je jedna dimenzija te matrice, a ogranienja dimenzija matrice su
jednaka maksimalnim i minimalnim vrednostima parametra koji odgovara datoj dimenziji
8
matrice. Za Fibonaijev niz pravimo niz, poto ima samo jedan parametar sa ogranienjima 1 i
maksimalne vrednosti n:
int a[maxn];
Drugi korak bi bio nalaenje onih podproblema tj. k-torki parametara koji nisu uslovljeni
reavanjem ni jednog drugog podproblema i njihovo reavanje. To su takozvani trivijalni
problemi. Bez njih ne bi mogli poeti reavanje ostalih problema. Trei korak bi bio nalaenje
zakonitosti izmeu pojedinih podproblema. Svaki problem u dinamikom reavanju zadatka,
osim onih trivijalnih, uslovljenje odrenim podproblemima. To jest reenje svakog problema
moemo napisatiu funkciji reenja nekih podproblema. Ako je R(p1,p2...pk) reenje problema
P(p1,p2...pk) onda je:
R(p1,p2...pk) = f(R1(p1,1,p1,2...p1,k),R2(p2,1,p2,2...p2,k)... Rt(pt,1,pt,2...pt,k))
gde je f funkcija zavisnosti pojedinih reenja, a t broj problema od kojih je problem P zavisan.
Potrebno je, znai, nai onakvu funkciju f koja vai za sve probleme, ukljuujui i glavni
problem i naravno ne mora da vai za trivijalne probleme.
etvrti korak u svemu ovome bio bi nalaenje hijerarhije svih problema, tako da za svaka dva
problema P i Q vai da ako je problem P u hijerarhiji direktno iznad problema Q onda je problem
P direktno zavistan od problema Q , t j. da reenje problema P zavisi od reenja problema Q.
Moe se lako uoiti da u ovakvoj hijerarhiji glavni problem stoji na samom vrhu, a trivijalni
problemi stoje na samom dnu hijerarhijskog drveta. Neto to se podrazumeva je da ne postoje
ciklusi zavisnosti. To je formalno reeno niz problema P1, P2...Pm tako da Pi zavisi od Pi+1 za
1im-1 i Pm zavisi od P1. Ako bi ovakvi ciklusi postojali ne bi mogli da naemo reenje
glavnog problema. Zato treba nai onakvu hijerarhiju u kojoj nema ciklusa zavisnosti. Poto smo
rekli kako bi trebalo analizirati opti problem koji se reava dinamikim programiranjem,
pokazaemo kako bi izgledao opti program koji je raen dinamikim programiranjem.
Da bi doli do reenja glavnog problema potrebno je, kao to je ve reeno reiti itavu
hijerarhiju podproblema. Jedini problemi koji nisu zavisni ni od jednog drugog podproblema su
trivijalni podproblemi, pa je logino da program prvo nalazi reenja tih problema. Posle toga,
program moe krenuti na reavanje svih ostalih podproblema idui po hijerarhiskim stepenima,
sve do konanog glavnog problema..
9
Raunanje n-tog lana Fiboaijevog niza
Klasino raunanje n-tog lana Fibonaijebog niza podrazumeva korienje rekurzivne
funkcije:
int fib(int n)
{
if(n==0) return 0;
else if(n==1) return 1;
else return fib(n-1)+fib(n-2);
}
Meutim ova metoda je dosta spora, na primer za raunanje petog lana funkcija mora da
izrauna:
fib(5)
fib(4)+fib(3)
fib(3)+fib(2)+fib(2)+fib(1)
fib(2)+fib(1)+fib(1)+fib(0)+fib(1)+fib(0)+fib(1)
fib(1) + fib(0) + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + fib(1)
U k-tom koraku nama je potrebno da znamo vrednosti fib(k-1) i fib(k-2) ali ipak pozivamo iste
vrednosti nekoliko puta. Ukoliko krenemo sa reavanjem problema odozdo-nagore moemo da
izraunamo samo ono to nam treba za sledei korak pritom eleminiui suvino raunanje:
int fib(int n)
{
/* Pravimo niz u koji smestamo Fibonacijeve brojeve */
int f[n+1];
int i;
/* Nulti i prvi Fibonacijev broj su 0 i 1 */
f[0] = 0;
f[1] = 1;
10
for (i = 2; i
11
#include
int R[2000][2000];
int max(int a, int b)
{
if(a>b) return a;
else return b;
}
int knap(int V[], int M[], int C, int n)
{
for(int i = 1; i
12
Problem najdueg podniza karaktera
Data su nam dva stringa s i t, potrebno je pronai duinu najdueg podniza karaktera(susednih
karaktera) koji se nalazi u oba niza. Ovaj algoritam je u praksi esto korien da bi se nala
slinost izmeu dva razliita gena.
Na primer, dat nam je string s=matematika i string t=informatika Najdui podniz u ovom
sluaju ima duinu 6 jer se matika nalazi u oba stringa.
Reenje:
#include
#include
int D[100][100];
int najduzi(char s[], char t[])
{
int i,j;
for(i=0; i
13
if(D[i][j]>max)
max = D[i][j];
}
}
return max;
}
Prvo postavimo elemente matrice na pozicijama D[i][0] i D[0][j] na 0. Kreui se kroz matricu
proveravamo da li je karatker t[i] jednak karakteru s[j], ukoliko je taj uslov ispunjen, tom
elementu u matrici dodeljujemo vrednost prethodnog elementa poveanu za 1, ako uslov nije
ispunjen taj element dobija vrednost 0. Na kraju nam ostaje samo da pronaemo maksimalni
element matrice i taj element vratimo kao rezultat.
Red za karte
U redu pred blagajnom se nalazi n (n200) ljudi koji ekaju da kupe kartu. Svako kupuje samo
jednu kartu za sebe. Dat je niz t od 1 do n. Broj t[i] oznaava vreme potrebno i-tom oveku u
redu da kupi jednu kartu za sebe.
Kupci u redu su se organizovali tako da pojedini kupci mogu kupiti kartu sebi i onom iza sebe u
redu za vreme r[i], ako se radi o osobi i i i+1. Dati su broj n i nizovi t i r. Potrebno je
minimozovati vreme potrebno da svi kupci dobiju karte, odnosno potrebno je odrediti minimalno
vreme koje je potrebno da svi kupci dobiju kartu.
Reenje:
Oznaimo na problem sa P(n), poto trebamo odrediti minimalno vreme da prvih n kupaca kupe
kartu. Ako imamo reenje problema P(n-1) i P(n-2) onda je reenje problema veoma lako
pronai. To je onda: R(n)=min((R(n-1)+t[n]),(R(n-2)+r[n]-1))
Moemo lako uoiti da ovo vai i za svaki podproblem P(k) za 3kn.
Probleme P(1) i P(2) ne moemo reiti na ovaj nain jer bi onda morao postojati problem P(0).
To su trivijalni problemi i njih reavamo na sledei nain: R(1)=t[1]-to jest vreme da se prvi
kupac snadbe kartom je t[1]
14
R(2)=min((t[1]+ t[2]),r[1])-vreme potrebno da se prva dva kupca snadbeju
kartom jednako je minimumu sumi vremena za prvog i drugog da kupe kartu i
vremenu da prvi kupi kartu za obojicu.
Sva reenja stavljamo u niz din[1..maxn].
#include
int din[200];
int min(int a, int b)
{
if(a
15
Tako smo deo niz akoji ini prvih i elemenata odredili duinu I poslednji element najdueg
neopadajueg podniza. Sledei element niza poredimo sa poslednjim elementom najdueg
neopadajueg podniza. Ako je vei ili jednak od poslednjeg elementa onda se taj element moe
dodati na kraj I tako produavamo najdui neopadajui podniz.
Ako se desi da je sledei element manji od poslednjeg elementa najdueg neopadajueg podniza
, onda taj niz ne moemo produiti. Ipak moe se desiti da je i-ti element vei od pretposlednjeg,
onda poslednji element najdueg neopadajueg podniza menjamo sa i-tim I tako dobijamo
najdui neopadajui podniz sa manjom vrednou poslednjeg elementa, a kako je poslednji
element smanjen vee su anse da ni bude produen.
U toku izraunavanja moramo pamtiti sledee informacije:
Duinu najdueg neopadajueg podniza dela niza koji smo obradili(neka to bude broj l)
Za sve cele brojeve j izmeu 1 I l (1 j l) vrednost poslednjeg elementa neopadajueg
podniza duine j(neka je to y[j-1])
Ako analiziramo i-ti element niza x(xi) tada mogu nastupiti sledei sluajevi:
1. xi yl-1 , tada se najdui neopadajui podniz produava (l=l+1) i poslednji element
podniza duine l se izjednaava sa xi
2. postoji neki broj j(0 j < l-1) tako da je yj xi < yj+1 tada se poslednji element
neopadajueg podniza duine j+2 moe izjednaiti sa brojem xi
3. xi < y0 tada se poslednji element podniza duzine l izjednaava sa xi
Kada obradimo sve elemente podniza vrednost promenljive l je duina najdueg neopadajueg
podniza, a sam podniz odreujemo tako to pamtimo koje smo elemente neopadajuih podnizova
menjali u toku obrade polaznog niza, tj za svaku vrednost broja i pamtimo redni broj elementa
niza y koji smo promenili u toku obrade elementa xi(neka to bude ipi).
Kreemo od poslednjih elemenata nizova x i ip I odreujemo jedan po jedan element najdueg
neopadajueg podniza. Ako treba da odredimo j-ti element podniza, onda pronalazimo poslednji
element niza ip iji je indeks manji ili jednak trenutnoj vrednosti promenljive i, koji je jednak
broju j. j-ti element najdueg neopadajueg podniza e biti jednak sa xk. Istovremeno emo
izjednaiti broj i sa brojem k I umanjiti j za 1 da bismo odredili prethodni element najdueg
neopadajueg podniza.
16
Funkcija izgleda ovako:
int lnd, nd[MAXD], ip[MAXD], ipp[MAXD+1];
void nrp(int an, int *a)
{
int ind = 1, jnd;
ip[0] = -1;
ip[0] = 0
ipp[0] = 0;
for(ind = 1; ind < an; ind++)
{
if(a[ind] >= a[ipp[lnd-1]])
{
ipp[lnd] = ind;
lnd++;
ip[ind] = lnd-1;
}
else
{
for(jnd = lnd-1; jnd>0; jnd--)
if((a[ipp[jnd]] > a[ind]) && (a[ipp[jnd-1]] a[ind])
ipp[0] = ind;
else
jnd--;
ip[ind] = jnd;
}
}
for(ind = lnd, jnd = an -1; ind>0; ind--)
{
while(ip[jnd] != ind-1)
jnd--;
nd[ind-1] = a[jnd];
}
}