Documente Academic
Documente Profesional
Documente Cultură
Laborator 6
Descriere: Programare dinamica - 1
1. Introducere
Problemele rezolvabile prin programare dinamica sunt in general probleme de optimizare, care se
pot descompune in subprobleme de aceeasi natura, dar de dimensiuni mai mici. Aceste
subprobleme nu sunt insa independente, ele avand “sub-subprobleme” comune.
O problema este abordabila folosind tehnica programarii dinamice daca satisface principiul de
optimalitate enuntat in continuare.
Fie secventa de stari S0, S1, ..., Sn ale sistemului. Daca d1, d2, ..., dn este un sir optim de decizii care
duc la trecerea sistemului din starea S0 ın starea Sn, atunci pentru orice i (1 ≤ i ≤ n) si di+1, di+2, ..., dn
este un sir optim de decizii care duc la trecerea sistemului din starea Si ın starea Sn.
Mai exact, conditiile de aplicare a programarii dinamice pentru o problema data sunt:
• Problema sa aiba o substructura optimala, adica solutia optima sa includa solutiile optime
ale subproblemelor rezultate din descompunerea problemei initiale.
Metoda programarii dinamice este de multe ori o alternativa mai eficienta decat un algoritm de tip
Backtracking sau de tip Divide et Impera pentru aceeasi problema.
Matricea folosita in programarea dinamica este o matrice de costuri optime pentru subproblemele
generate, iar costul optim al problemei initiale se afla in ultimul element din matrice calculat, care
se afla de obicei pe ultima coloana, ultima linie sau ultima coloana, prima linie.
Foarte importanta este ordinea de calcul a elementelor matricei de costuri unele din altele,
incepandu-se cu prima linie sau cu prima coloana sau cu diagonala matricei; aceste elemente
reprezinta costul (exact) al unor subprobleme banale, care nu se mai pot descompune in altele mai
simple. De altfel cuvatul “programare” din expresia “programare dinamica” are sensul de
planificare a calculelor in procesul de rezolvare a problemei si subliniaza importanta pe care o are
ordinea efectuarii calculelor in matricea de costuri.
Programarea dinamica se aplica problemelor care au mai multe solutii, fiecare din ele cu cate o
valoare, si la care se doreste obtinerea unei solutii optime (minime sau maxime).
ASD
Algoritmul pentru rezolvarea unei probleme folosind programarea dinamica se dezvolta in 4 etape:
• caracterizarea unei solutii optime (identificarea unei modalitati optime de rezolvare, care
satisface principiului optimalitatii)
• definirea recursiva a valorii unei solutii optime
• calculul efectiv al valorii unei solutii optime folosind o structura de date (de obicei tablou)
• reconstituirea unei solutii pe baza informatiei calculate.
Problema propusa nu este o problema de optimizare, dar permite ilustrarea modului de abordare a
metodei programarii dinamice pe un exemplu simplu.
Calculul combinarilor de n obiecte luate cate k se poate face folosind relatia de recurenta:
Aceasta relatie de recurenta exprima descompunerea problemei C(n,k) in subprobleme mai simple
(cu valori mai mici pentru n si k).
Metoda Divide et Impera transpune direct relatia intr-o functie recursiva, reducand problema
initiala la probleme tot mai mici.
//cazuri de baza
if (n == k) return 1; //C(n,n) = 1
if (k == 0) return 1; //C(n,0) = 1
if (k > n) return 0; //C(n,k) = 0
Dezavantajul acestei abordari rezulta din numarul mare de apeluri recursive, dintre care o parte nu
fac decat sa recalculeze aceleasi valori (metoda combinari se apeleaza de mai multe ori cu aceleasi
valori pentru parametrii n si k). Arborele acestor apeluri pentru n=5 si k=3 este urmatorul:
C(5,3)
C(4,3) C(4,2)
Metoda programarii dinamice creaza o matrice cu valorile C(i,j) a carei completare incepe cu prima
coloana ( C(i,0)=1 ) si continua cu linia a doua ( C(i,1) ), linia a treia ,s.a.m.d. Matricea creata este
cunoscuta si sub numele de triunghiul lui Pascal :
k
0 1 2 3
0 1 0 0 0
n 1 1 1 0 0
2 1 2 1 0
3 1 3 3 1
Obs. Spre deosebire de metoda Divide et Impera, care rezolva subproblemele de sus in jos, metoda
programarii dinamice rezolva subproblemele de jos in sus, adica incepe cu cele de dimensiune mai
mica si continua pana la problema de dimensiune maxima, care este problema initiala.
Consideram n matrice A1,A2, ...,An, de dimensiuni d0 × d1, d1 × d2, ..., dn−1×dn. Produsul
A1×A2×...×An se poate calcula in diverse moduri, aplicand asociativitatea operatiei de ınmultire a
matricelor. Numim ınmultire elementara ınmultirea a doua elemente. In functie de modul de
utilizare al parantezelor difera numarul de ınmultiri elementare necesare pentru calculul produsului
A1 × A2 × ... × An. Se cere o solutie optima astfel incat costul, adica numarul total de ınmultiri
elementare pentru calcularea produsului A1 × A2 × ... × An, sa fie minim.
Exemplu
Pentru 3 matrici, cu dimensiunile (10, 1000), (1000, 10) si (10, 100), produsul A1 × A2 × A3 se
poate calcula ın doua moduri:
Obs. Numarul de ınmultiri elementare necesare pentru a ınmulti o matrice A, avand n linii si m
coloane, cu o matrice B, avand m linii si p coloane, este n ∗ m∗ p.
1. Pentru a calcula A1×A2×...×An, ın final trebuie sa ınmultim doua matrici, deci vom diviza
produsul astfel: (A1 ×A2 ×...×Ak)×(Ak+1 ×...×An). Aceasta observatie se aplica si produselor
dintre paranteze. Prin urmare, subproblemele problemei initiale constau ın determinarea
parantezarii optimale a produselor de matrice de forma Ai ×Ai+1 ×... ×Aj , 1 ≤ i ≤ j ≤ n.
Observam ca numai jumatatea de deasupra diagonalei principale din M este utilizata. Pentru
a construi solutia optima este utila si retinerea indicelui k, pentru care se obtine minimul. Nu
vom considera un alt tablou, ci il vom retine, pe pozitia simetrica fata de diagonala
principala (M[j][i]).
Rezolvare
import java.io.*;
class InmOptimalaMatrice{
if(i<j){
k=m[j][i];
if(i!=k){
System.out.print("(");
paranteze(i,k);
System.out.print(")");
}
else paranteze(i,k);
System.out.print(" * ");
if(k+1!=j){
System.out.print("(");
paranteze(k+1,j);
System.out.print(")");
}
else paranteze(k+1,j);
}
else
System.out.print("A"+i);
}//paranteze
System.out.println("Dimensiunile matricelor:");
for(i=1;i<=n+1;i++){
System.out.print("d["+i+"]=");
d[i]=Integer.parseInt(br.readLine());
}
//recurenta
for(i=n;i>=1;i--)
for(j=i+1;j<=n;j++){
min=m[i][i]+m[i+1][j]+d[i]*d[i+1]*d[j+1];
imin=i;
for(k=i+1;k<=j-1;k++){
v=m[i][k]+m[k+1][j]+d[i]*d[k+1]*d[j+1];
if(min>v){
min=v;
imin=k;
}
}
m[i][j]=min;
m[j][i]=imin;
}//for i,j
System.out.println();
}//main
}//class
ASD
Fie un sir A = (a1, a2, ..., an). Numim subsir al sirului A o succesiune de elemente din A, ın ordinea
ın care acestea apar ın A: ai1, ai2 , ..., aik , unde 1 ≤ i1 < i2 < ... < ik ≤ n. Se cere determinarea unui
sub sir crescator al sirului A, de lungime maxima. De exemplu, pentru
o solutie poate fi
Fie Ai1 = (ai1 ≤ ai2 ≤ ... ≤ aik ) cel mai lung subsir crescator al sirului A. Observam ca el
coincide cu cel mai lung subsir crescator al sirului (ai1, ai1+1, ..., an). Evident Ai2 = (ai2 ≤ ai3
≤ ... ≤ aik ) este cel mai lung subsir crescator al lui (ai2, ai2+1, ..., an), etc. Prin urmare, o
subproblema a problemei initiale consta in determinarea celui mai lung subsir crescator care
ıncepe cu ai, i = {1, .., n}.
Subproblemele nu sunt independente: pentru a determina cel mai lung subsir crescator care
ıncepe cu ai, este necesar sa determinam cele mai lungi subsiruri crescatoare care ıncep cu
aj , ai ≤ aj , j = {i+ 1, .., n}.
Pentru a retine solutiile subproblemelor vom considera doi vectori lg si poz, fiecare cu n
componente, avand semnificatia:
lg[i] =lungimea celui mai lung subsir crescator care ıncepe cu a[i];
poz[i] =pozitia elementului care urmeaza dupa a[i] ın cel mai lung subsircrescator
care ıncepe cu a[i], daca un astfel de element exista, sau −1 daca uastfel de
element nu exista.
a= {2,5,3,4,1}
Vom nota cu lg[i] lungimea subsirului crescator maximal dintre a[1] si a[i] (terminat in pozitia i),
deci:
Ideea programarii dinamice este ca aceste lungimi pot fi calculate unele din altele, cu o relatie de
recurenta, in loc de a calcula separat fiecare lg[i]. Lungimea unui subsir crescator terminat in
pozitia i poate fi cu 1 mai mare ca lungimea unui subsir crescator terminat in pozitia j ( cu j<i ),
daca a[i] >= a[j], deoarece a[i] se adauga subsirului terminat in pozitia j. Pentru calculul lui lg[i]
ASD
vom cauta printre lg[1],lg[2],..lg[i-1] valoarea maxima dintre cele care satisfac conditia a[j] <= a[i].
Relatia de recurenta poate fi exprimata astfel:
Aplicarea acestei relatii se face prin doua cicluri suprapuse: unul pentru fiecare i=2,n si altul pentru
fiecare j=1,i-1. Evolutia principalelor variabile pentru exemplul numeric:
este urmatoarea
i j a[j] a[i] lg[j] max lg[i] a[j]<=a[i] lg[1] lg[2] lg[3] lg[4] lg[5]
2 1 2 5 1 1 2 2<5 1 2
3 1 2 3 1 1 2 2<3
3 2 5 3 2 1 2 5>3 1 2 2
4 1 2 4 1 1 2 2<4
4 2 5 4 2 1 2 5>4
4 3 3 4 2 2 3 3<4 1 2 2 3
5 1 2 1 1 0 1 2>1
5 2 5 1 2 0 1 5>1
5 3 3 1 2 0 1 3>1
5 4 4 1 3 0 1 4>1 1 2 2 3 1
Determinarea subsirului crescator cu lungime maxima se face intr-o etapa ulterioara, examinand
vectorul lg de la stanga la dreapta pentru valori crescatoare, pana la valoarea maxima.
Rezolvare
import java.io.*;
class SubsirCrescatorMaximal{
static int n;
static int[] a;
static int[] lg;
static int[] poz;
int i,j;
st.nextToken(); n=(int)st.nval;
a=new int[n+1];
lg=new int[n+1];
poz=new int[n+1];
ASD
for(i=1;i<=n;i++) {
st.nextToken();
a[i]=(int)st.nval;
}
int max,jmax;
if(max!=0) {
lg[i]=1+max;
poz[i]=jmax;
}
else lg[i]=1;
}
max=0;
jmax=0;
for(j=1;j<=n;j++)
if(max<lg[j]){
max=lg[j];
jmax=j;
}
out.println(max);
int k;
j=jmax;
for(k=1;k<=max;k++) {
out.print(a[j]+" ");
j=poz[j];
}
out.close();
}//main
}//class
Probleme
1. Modificati programul pentru un subsir crescator maximal pentru a calcula cel mai lung sir
crescator (in ordine lexicografica) a unui sir de litere.
2. Realizati un program care calculeaza primele n linii din triunghiul lui Pascal folosind
metoda programarii dinamice.