Professional Documents
Culture Documents
Dinamičko
programiranje
Marko Mišić
marko.misic@etf.bg.ac.rs
Vladislav Guberinić
neosisani@gmail.com
Aleksandar Ivanović
aleksandar.ivanovic.94@gmail.com
Uvod u dinamičko programiranje (1)
Dinamičko programiranje je
metod rešavanja kompleksnih problema
koji se mogu razbiti
na potprobleme manje složenosti
Koristi se najčešće kod problema optimizacije
Problemi moraju imati
optimalnu substrukturu
Pritom, potproblemi se preklapaju
Za razliku od problema koji se rešavaju
npr. divide and conquer algoritmima
Uvod u dinamičko programiranje (2)
Rešenje jednog potproblema se
može iskoristiti u rešavanju drugog,
složenijeg potproblema
Svaki potproblem se tipično rešava samo jednom
Koristi se tehnika
memo(r)izacije za skladištenje rešenja potproblema
Nakon što je rešenja potproblema izračunato,
svaki sledeći put se koristi
izračunata vrednost prostim pristupom tabeli
Tzv. „tablični“ metod
Posebno efikasno ukoliko
broj ponavljenih potproblema raste
eksponencijalno sa brojem ulaznih podataka
Osobine DP problema
Optimalna podstruktura problema znači
da se rešenje problema može dobiti
kombinovanjem optimalnih rešenja
potproblema
Optimalna podstruktura se
obično opisuje rekurzivno
Identifikacija rekurzije je često
prvi korak u rešavanju problema
Preklapanje potproblema podrazumeva
da je prostor (broj) potproblema relativno mali
Algoritam tipično ne generiše nove potprobleme,
već se stari rešavaju iznova i iznova
Koraci u rešavanju problema
Rešavanje problema korišćenjem
dinamičkog programiranja se obično može podeliti
na sledeća četiri koraka:
Karakterizacija strukture optimalnog rešenja
Rekurzivno definisanje rešenja optimalnog rešenja
Računanje vrednosti optimalnog rešenja
top-down ili bottom-up pristupom
Konstruisanje optimalnog rešenja
na osnovu sračunatih informacija
Koraci 1-3 su osnova dinamičkog programiranja,
dok se korak 4 može izostaviti ukoliko se traži
samo određena vrednost koja karakteriše rešenje,
ne i samo rešenje
Npr. dužina najduže zajedničke podsekvence i sl.
Pristupi u rešavanju problema
Top-down pristup (odozgo na dole)
Problem se rastavlja na potprobleme
Potproblemi se reše i pamte se njihova rešenja
za kasniju upotrebe
Ovaj pristup predstavlja kombinovanje
rekurzije i memoizacije
Bottom-up pristup (odozdo na gore)
Svi potproblemi se redom rešavaju i
koriste za nalaženje većih
Ovaj pristup je bolji zbog štednje
memorijskog prostora
Ponekad teško odrediti
koji su sve potproblemi potrebni za traženje datog
Potrebno je odrediti tzv. graf zavisnosti
Primena dinamičkog programiranja
Izračunavanje Fibonačijevih brojeva
“Matrični” problemi
Pronalaženje najduže
zajedničke podsekvence
Longest common subsequence
Longest increasing subsequence
Prebrojavanje novca
Problem ranca
Fibonačijevi brojevi (1)
Naivna implementacija:
function fib(n)
if n = 0 or n = 1
return n
else
return fib(n − 1) + fib(n − 2)
Događa se kombinatorna eksplozija
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))
Fibonačijevi brojevi (2)
Pristup odozgo na dole koristi memoizaciju
Potproblemi se rešavaju tačno jednom,
a njihove vrednosti se pamte
var m := array[0..n]
m[0] := 1; m[1] := 1;
m[2 : n ] := SENTINEL; {npr. -1}
function fib(n)
if n not in m
m[n] := fib(n − 1) + fib(n − 2)
return m[n]
Fibonačijevi brojevi (3)
Pristupom odozdo na gore, linearna prostorna složenost
se može preobraziti u konstantnu
function fib(n)
var previousFib := 1, currentFib := 1
repeat n − 1 times
var newFib := previousFib + currentFib
previousFib := currentFib
currentFib := newFib
return currentFib
Alternativno:
a := 1; b := 1; c := 0;
for i := 3 to n do
begin
c := a + b;
a := b;
b := c;
end;
Fibonačijevi brojevi –
dodatne optimizacije (1)
Kod za pristup odozdo nagore se može
još dodatno optimizovati tehnikom
razmotavanja (uklanjanja) petlji
(loop unroll)
Štedi se polovina skokova
a := 1; b := 1; c := 0;
for i := 3 to n step 2 do
begin
c := a + b; a := b; b := c;
c := a + b; a := b; b := c;
end;
Fibonačijevi brojevi –
dodatne optimizacije (2)
Konačno, mogu se uštedeti memorijski
transferi izbacivanjem promenljive c:
a := 1; b := 1;
for i := 3 to n step 2 do
begin
a := a + b;
b := a + b;
end;
Svi međurezultati koji se čuvaju
sada staju u registre procesora,
uklonjen je deo skokova i
memorijskih transfera
Fibonačijevi brojevi –
dodatne optimizacije (3)
Ovakve optimizacije uvek smanjuju
čitljivost koda i treba ih retko koristiti!
Mana optimizacije - gde je rezultat?
if (odd(n))
then result := a
else result := b
Maksimalna suma
nesusednih elemenata u nizu (1)
Postavka problema
Dat je niz a prirodnih brojeva dužine n. Odrediti
podniz datog niza čiji je zbir elemenata
maksimalan, a u kome nema susednih elemenata.
Kako definisati potproblem?
Nalaženje traženog podniza
na nekom delu polaznog niza a
Definišemo dodatni niz d[k] koji će u svakom
trenutku čuvati maksimalan zbir nesusednih
elemenata niza (a1,a2,...,ak), za k ∈[1,...,n]
Konačno rešenje će biti smešteno u d[n]
Maksimalna suma
nesusednih elemenata u nizu (2)
Ukoliko imamo sračunat niz d
za prvih k-1 elemenata,
tada element ak možemo dodati u sumu
ukoliko je povećava ili
uzeti vrednost d[k−1]
kao do tada najveću sračunatu sumu
d[k] = max{d[k−1], ak +d[k−2]}, za k ≥3
Početni uslovi će pritom biti:
d[1] = max{0, a1}
d[2] = max{0, a1, a2}
Maksimalna suma
nesusednih elemenata u nizu (3)
Računanje niza d:
d[1] = max{0,a1};
d[1] = max{0,a1,a2};
for k ←3 to n do
if d[k−1] > d[k−2]+a[k] then
d[k] = d[k−1];
else9
d[k] = d[k−2]+a[k];
end
Maksimalna suma
nesusednih elemenata u nizu (4)
Rekonstrukcija podsekvence (u obrnutom poretku)
if n = 1 then
return subsequence = a;
end
subsequence =∅; ∅
currentIndex = n;
while (currentIndex > 0) do
if d[currentIndex] = d[currentIndex−1] then
add a[currentIndex] to subsequence;
currentIndex = currentIndex−2;
else
currentIndex = currentIndex−1;
end
End
return subsequence;
Problem penjanja uz veštačku stenu (1)
Zamislite da treba da se uspnete uz veštačku stenu u
nekom zabavnom parku. Na steni se nalaze ručke za
penjanje koje su raspoređene u kvadratnu mrežu (grid).
Zbog nagiba stene, penjanje uz neke ručke je znatno
opasnije nego uz druge. Sa svake ručke, penjač može da
dohvati i pređe na neku od tri ručke iznad njega – ručku
iznad, ručku iznad desno i ručku iznad levo (osim ukoliko
jedna od ručki levo ili desno nije dostupna, jer je penjač
došao do kraja zida).
Potrebno je pronaći najmanje opasan put od podnožja do
vrha stene. Smatrati da je svakoj ručki dodeljen ceo broj
koji označava težinu penjanja uz njih, a ukupna težina za
neki put će biti jednak sumi težina svih ručki koje su
korišćene na putu do vrha.
Problem penjanja uz veštačku stenu (2)
Problem se može modelirati
matricom dimenzije nxm
Vrednost svakoj polja matrice (i,j) će biti
jednaka težini (ceni) prelaska
preko date ručke (polja matrice)
Sa polja (i,j) se u jednom koraku može preći
na neko od polja:
(i+1,j−1), (i+1,j) ili (i+1,j+1),
za 1 < j < m
(i+1,j), (i+1,j+1)
za j = 1
(i+1,j−1), (i+1,j)
za j = m
Problem penjanja uz veštačku stenu (3)
Primer jedne ulazne matrice (stene)
Minimalna cena penjanja najlakšom putnjom je
12 i počinje od polja (1,4)
Pohlepni pristup bi krenuo od polja (1,2) i
uzimao lokalno najbolje rešenje u svakom
koraku što bi proizvelo put sa cenom 13
1 2 3 4 5
4 2 8 9 5 8
3 4 4 6 2 3
2 5 7 5 6 1
1 3 2 5 4 8
Dno stene
Problem penjanja uz veštačku stenu (4)
Očigledno, problem je rekurzivan,
pa je potrebno definisati strukturu podataka
u kojoj će se čuvati međurezultati
Potrebno je definisati matricu A(i,j) dimenzija nxm
Svaki element matrice A(i,j) će sadržati
cenu (težinu) najmanje opasnog puta
od dna matrice do polja A(i,j) uključujući i to polje
k (n − k )!k!
n
Rešenje:
popuni matricu jedinicama
for i := 1 to n
for j := 1 to i
pas[i,j] = pas[i-1,j] + pas[i-1,j-1];
Ranac - postavka
Mali Andrija je dobio jednodnevnu kartu do
Beograda da bi posetio Narodnu biblioteku
Srbije. U biblioteci postoji kolekcija od n
naučnih radova, svaki sa po s[i] stranica i
naučnom vrednošću v[i]. Andrija ima svoj
ranac u koji staje najviše S strana i želi da
iznese neke radove iz bibloteke. Kako je
Andrija Piroćanac on želi da vrednost radova
koje će izneti bude što veća. Postoji
beskonačno mnogo kopija svakog rada.
Pomozite Andriji da odabere najvrednije
radove.
Ranac - analiza (1)
Gramzivo rešenje:
Naći rad koji ima najvecu vrednost po strani
Trpati u ranac dok ima mesta
Dopuniti na sličan način do kraja
Problem:
S = 18, n = 2
s[0] = 10, v[0] = 100 (10 $ / strani)
s[1] = 9, v[1] = 81 (9 $ / strani)
Gramzivo rešenje: Ubaciti rad br. 0
Optimalno rešenje: Ubaiciti 2 rada br. 1
Ranac - analiza (2)
Iscrpljivanje grubom silom (brute force):
Isprobamo sve moguće kombinacije
Svaka stvar može da se nađe najviše k puta
k = S / min(s[i])
O(kn) -> eksponencijalno!!!
Ranac - analiza (3)
Dinamičko rešenje
Oktrijemo optimalni način za ranac veličine 1
Na osnovu njega napravimo optimalno rešenje
za ranac veličine 2
Na osnovu njega napravimo optimalno rešenje
za ranac veličine 3
???
Profit
Ranac - pseudokod (1)
Potrebne strukture podataka:
m - veličina ranca
n - broj radova
s[i] - broj strana i-tog rada
v[i] - vrednost i-tog rada
ranac[i] - maksimalna vrednost ranca
veličine i
Ranac - pseudokod (2)
#Inicijalizujemo niz
for i := 1 to n
if (ranac[s[i]] < v[i]) then
ranac[s[i]] := v[i];
#Gradimo iterativno rešenje
for i := 1 to m
for j := 1 to n
if (ranac[i] > (ranac[i-s[j]] + v[j])) then
ranac[i] := ranac[i-s[j]] + v[j])
Ranac - varijacija - sadržaj ranca (1)
Potrebno je znati i sadržaj optimalnog
ranca, ne samo njegovu vrednost.
for i := 1 to m
for j := 1 to n
if (ranac[i] > (ranac[i-s[j]] + v[j])) then
ranac[i] := ranac[i-s[j]] + v[j])
pret[i] := j;
Ranac - varijacija - sadržaj ranca (3)
Ispis sadržaja – rekurzija:
procedure ispisi (i : integer)
begin
if (i = -1) then
return;
else
ispisi ( i - pret[i]);
print pret[i];
end;
Ranac - varijacija - konačan br radova (1)
Postoji konačan broj kopija svakog rada
Pamtimo u svakom trenutku
stanje svih radova
for i := 1 to n do
for j := 1 to n do
if (mat[i][j] <> -1) then
m = max (mat[i-1, j-1], mat[i-1, j],
mat[i, j-1]) + 1;
Sumarum (1)
Đurica je pronašao N karata poređanih u niz. Na kartama su
zapisani celi brojevi.
Đurica datom nizu karata A, dodeljuje vrednost f(A) koja je
jednaka sumi razlika vrednosti na uzastopnim kartama.
Đurica želi da izbaci najviše K karata, tako da ne naruši
prvobitan poredak karata u nizu u cilju da maksimizuje
vrednost niza f(A).
Za zadato N i K, kao i početan niz karata A pomozite Đurici da
pronađe maksimalnu vrednost f(A) koju može postići.
Ograničenja:
2 ≤ N ≤ 500.000, 0 ≤ K ≤ N – 2
-1.000.000.000 ≤ A[i] <= 1.000.000.000
Sumarum (2)
Analizirajmo funkciju f(A):
Neka je A =[a1,a2,…an]
f(A) = a2-a1+a3-a2+…+an-an-1 = an-a1
Funkcija f(A) je u stvari jednaka razlici
poslednjeg i prvog elementa niza A
Iz ove činjenice sledi ključna opservacija
potrebna za rešavanje ovog problema:
Za dati niz A brisanje elementa koji nije ni prvi
ni poslednji ne utiče na vrednost f(A)
Sumarum (3)
Na osnovu toga možemo zaključiti
da iz prvobitnog niza treba brisati
samo nekoliko prvih i/ili
nekoliko poslednjih elemenata
Formalno rečeno,
na vrednost f(A) možemo uticati
tako što ćemo obrisati L elemenata
sa početka niza i R elemenata sa kraja niza
tako da važi 0 ≤ L, R i L + R ≤ K
Sumarum (4)
Vrednosti L i R možemo fiksirati
pomoću dve petlje i
pronaći optimalno rešenje
Složenost ovog pristupa je kvadratna u odnosu na K,
što je u ovom problemu previše sporo
Bolje rešenje možemo postići
tako što ćemo malo promeniti
način razmišljanja:
Umesto da fiksiramo L i R koji predstavljaju koliko tačno
brojeva treba obrisati sa početka, odnosno sa kraja niza,
fiksiraćemo L i R koji predstavljaju koliko najviše brojeva
treba obrisati sa početka, odnosno, sa kraja niza
Sumarum (5)
Primetimo da je za fiksirano L, R
jednoznačno određeno:
R=K–L
Sada ostaje samo
da za fiksirano L i izračunato R pronađemo
koji element treba ostati na početku niza,
a koji element treba ostati na kraju niza i
rešili smo zadatak
Sumarum (6)
Možemo primetiti da bi smo dobili
optimalno rešenje prvi element niza
mora biti što manji, a poslednji što veći
Dakle za dato L i R rešenje je:
maxR[R] – minL[L]
gde minL[i] predstavlja minimum
prvih i elemenata sa početka niza,
a maxR[i] maksimum prvih i elemenata
sa kraja niza
Ostaje samo da izračunamo
nizove maxL i maxR
Sumarum (7)
Nizove minL i maxR možemo računati dinamički:
minL[0] ← A[1]
maxR[0] ← A[N]; // početne vrednosti
for i =1 to N do
minL[i] ← min ( A[i], minL[i – 1] );
maxR[i] ← max ( A[N + 1 – i], maxR[i – 1] );
Minimum prvih i elemenata je minimum i-tog
elementa i minimuma prvih i – 1 elemenata,
analogno se popunjava niz maksimuma
Sumarum (8)
Kada imamo izračunate nizove minL i maxR
algoritam za rešavanje ovog problema
izgleda ovako: