16
Univerzitet u Nišu Prirodno matematički fakultet Seminarski rad Dinamičko programiranje Autor: Mentor: Nemanja Petrović Prof. dr Predrag Stanimirović Februar 2014. Niš

Dinamicko programiranje

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];

    }

    }