Sunteți pe pagina 1din 37

3. Metode de rezolvare a problemelor.

3.1. Algoritmi Backtracking (cutare cu revenire)


Problema este de a determina algoritmi pentru a gsi soluii pentru probleme specifice, nu prin
urmarea unei reguli fixe de calcul, ci prin ncercare i eroare (testarea soluiei). Metoda clasic este de a
descompune procesul ncearc-i-eroare n task-uri (probleme) pariale. Adesea aceste task-uri sunt
mult mai natural exprimate n termeni recursivi i constau din exploatarea unui numr finit de
subprobleme. ntregul proces poate fi vzut ca un proces de selecie sau cutare care construiete treptat
i scaneaz un arbore de subprobleme. n multe probleme acest arbore crete foarte rapid, de obicei
exponenial, depinznd de un anumit parametru, dat. n mod corespunztor crete i eforul de cutare.
De multe ori arborele de cutare poate fi redus (retezat) numai prin utilizarea de euristici, reducnd
astfel calculele la o limit acceptabil. Ne vom ocupa, n continuare, de principiul general al
descompunerii task-urilor pentru rezolvarea unei astfel de probleme n subtask-uri i aplicarea
recursivitii i nu de discutarea regulilor euristice.
Vom ncepe prin demonstrarea acestei tehnici utiliznd un exemplu binecunoscut, i anume
turul calului. Se consider o tabl (de ah) cu n*n cmpuri, n cazul general. Un cal (piesa de la jocul
de ah) care se poate muta dup regulile binecunoscute de la ah, este plasat ntr-un cmp cu
coordonatele iniiale (x0, y0). Problema care trebuie rezolvat este de a gsi o acoperire a ntregii table,
dac se poate gsi, deci de a determina un tur (o parcurgere) de n2-1 mutri, astfel nct fiecare cmp al
tablei s fie vizitat numai o singur dat.
Modalitatea evident de a simplifica problema acoperirii celor n2 cmpuri este de a considera
problema fie de realizare a urmtoarei mutri fie de a determina pe cele care nu sunt posibile. Vom
defini un algoritm ce ncearc s realizeze urmtoarea mutare. O prim metod este prezentat n
continuare:
ncearc urmtorea mutare
{iniializarea selecie de mutri;
repet
{
selecteaz urmtorul candidat din lista de mutri urmtoare;
dac (acceptabil)
{memoreaz mutarea;
dac (tabla nu este plin)
{
ncearc urmtorea mutare;
dac (nu este succes)
terge mutarea anterioar;
}
}
} pn cnd (( mutarea a fost cu succes )
|| ( nu mai sunt candidai ) )
}
n continuare trebuie luate anumite decizii pentru reprezentarea datelor, pentru a descrie ntr-un
mod precis acest algoritm. n primul rnd trebuie reprezentat tabla printr-o matrice, pe care o vom
1

nota h. Pentru a memora istoria (a pstra urma - tracking) mutrilor succesive vom folosi urmtorea
convenie:
h[x][y] = 0; dac cmpul (x,y) nu a fost vizitat
h[x][y] = i; dac cmpul (x,y) a fost vizitat la mutarea i, (1<= i <= n2)
S determinm i ceilali parametrii. Trebuie s determinm condiiile de start pentru
urmtoarea mutare i apoi s determinm dac s-a realizat cu succes sau nu. Condiiile de start se pot
preciza prin specificarea coordonatelor (x,y) de la care se realizeaza mutarea i, pentru a putea fi
memorat. Pentru a specifica succesul unei mutri sau nu vom utiliza o variabil ntreag q = 1 pentru
succes i q = 0, pentru insucces. Condiia c tabla nu este plin poate fi exprimat prin i < n2.
Vom intoduce dou noi variabile u i v pentru a reprezenta coordonatele destinaie ale
posibilelor mutri, n concordan cu regulile de mutare ale calului. n acest caz o mutare acceptabil
poate fi rafinat astfel: noul cmp (u,v) va trebui s satisfac condiiile 1<= u <= n i 1<= v <= n i
cmpul respectiv nu fost vizitat anterior, adic h[u][v] = 0.
Operaia de memorare a unei mutri permise se va face prin atribuirea h[u][v] = i, iar anularea
unei mutri prin h[u][v] = 0. Vom introduce o variabil local q1 pentru a fi utilizat ca parametru
rezultat n apelarea recursiv a acestui algoritm; deci q1 poate fi substituit pentru mutarea a fost cu
succes. Dup aceste specificri, nu copmlete, algoritmul devine:
incearca ( int i; int x; int y; int q )
{int u, v, q1;
iniializarea selecie de mutri;
do{
se aleg u, v coordonatele urmatoarei mutari,
conform regulilor sahului;
if (1<= u <= n && 1<= v <= n && h[u][v] == 0)
{
h[u][v] = i;
if (i < pow (n,2))
{
incearca ( i+1 , u , v , q1 );
if ( !q1 )
h[u][v] = 0;
}
else
q1 = 1;
}
} while ( !q1 && ( mai sunt candidai ) );
q = q1;
}
Mai este necesar rafinarea unei condiii pentru ca programul s fie exprimat n notaia de baz
a programrii. Trebuie specificate n limbajul de programare regulile dup care se poate muta calul.
Dintr-un punct dat prin coordonatele sale (x,y) sunt 8 mutri posibile (ca n figura urmtoare, n
care sunt numerotate de la 1 la 8) ale cror coordonate viitoare vor fi notate (u,v). Pentru a obine
coordonatele viitoarelor mutri posibile (u,v) din coordonatele iniiale (x,y) se pot aduna diferenele de
coordonate posibile, memorate ntr-unul sau doi vectori. Vom utiliza doi vectori notai a i b, iniializai
corespunztor.
3
2

1
Cal

8
6

Pentru a specifica candidatul urmtor se poate utiliza o variabil ntreag index. ntregul
program este prezentat n continuare. Funcia recursiv este apelat, iniial, cu coordonatele (x0, y0), ca
paramertii, de unde va ncepe turul calului.
// turcal1s.cpp - program "knight tour": "acoperirea" unei table de sah
// prin mutarile unui cal, astfel incat fiecare casuta a tablei sa fie
// parcursa (atinsa) o singura data; se afiseaza prima solutie gasita
#include <stdio.h>
#include <conio.h>
#define N 5
#define NSQ N*N
#define NrMaxMutCal 8
// N - dimensiunea tablei de sah; NSQ=N^2;
// NrMaxMutCal=8, nr. maxim mutari posibile ale calului, pt. o pozitie data
int i, j, q;
long int nr_incercari;
int h[N+1][N+1];
int a[NrMaxMutCal+1]={0, 1, 1, 2, 2, -1, -1, -2, -2};// poz. relative pe cele doua
int b[NrMaxMutCal+1]={0, 2, -2, 1, -1, 2, -2, 1, -1};// doua coordonate,
// ale celor 8 mutari posibile
void muta (int i, int x, int y, int *pq){// determina mutarea urmatoare
int k, u, v, l, c, q1;
k = 0;
do {
k = k + 1;
// selectie urmatoarea mutare
u = x + a[k]; // noile coordonate
v = y + b[k];
q1 = 0;
// initializare indicator 'succes mutare'
if ((u >= 1 && u <= N) && (v >= 1 && v <= N) && (h[u][v] == 0))
// daca mutarea este valida si casuta nu a fost vizitata
{h[u][v] = i; // memorare mutare 'i'
if (i < NSQ)
{// daca sol. nu e completa se incearca mutarea urm.
muta (i+1, u, v, &q1);
if (!q1) // daca nu este valida se sterge mutarea
{h[u][v] = 0;
nr_incercari++; // se numara ramurile taiate
}
// (infundate)
}
else
3

q1 = 1;// daca s-a acoperit tabla->tipareste solutia


} // se continua daca nu s-a gasit o solutie (q1=0) si daca
} while (!q1 && k < NrMaxMutCal); // mai sunt mutari posibile (k<8)
// din pozitia curenta
*pq = q1;
// se transmite reusita sau nereusita mutarii respective
}
void main (void) {
int i, j, q;
clrscr();
for (i=1; i<=N; i++) // initializare tabla de sah,
for (j=1; j<=N; j++) // pentru a memora mutarile urmatoare
h[i][j] = 0;
h[1][1] = 1;
// pozitia de start de pe tabla de sah
muta( 2, 1, 1, &q); // apel mutare 2, din pozitia (1,1),
if (!q)
// q-indicator de succes mutare, acoperire tabla de sah
printf("Problema nu are solutie !!!\n");
else
{for (i=1; i<=N; i++)
{for (j=1; j<=N; j++)
printf("%4d", h[i][j]);
printf("\n");
}
printf("\nNumarul total de incercari a fost de: %ld\n\n",
nr_incercari);
}
}
Caracteristica acestei metode este c paii ctre soluia final sunt ncercai i memorai i ei pot
fi, mai trziu, luai (parcuri) napoi i teri din nregistrri cnd se determin c pasul conduce ntr-o
fundtur. Aceast aciune poart numele de backtracking. Modelul general, prezentat n continuare,
este derivat din cel anterior presupunnd c numrul de candidai la fiecare pas este finit.
ncearc
{
iniializarea seleciei de candidai;
repet
{
selecteaz urmtorul candidat;
dac (acceptabil)
{
memoreaz candidatul;
dac (soluia este incomplet)
{
ncearc urmtorul pas;
dac (nu este succes)
terge candidatul memorat;
}
}
} pn cnd (( mutarea a fost cu succes )
4

|| ( nu mai sunt candidai ) )


}
Dac la fiecare pas numrul de candidai de investigat este fix, m, atunci se poate aplica
urmtoarea shem:
ncearc ( i )
{k = 0;
repet
{ k = k + 1; selecteaz candidatul k;
dac (acceptabil)
{
memoreaz candidatul;
dac ( i < n )
{
ncearc ( i + 1 );
dac (nu este succes)
terge candidatul memorat;
}
}
} pn cnd ( mutarea a fost cu succes )|| ( k == m )
}
O alt problem clasic de backtracking (sau altfel spus ncearc-i-eroare) este cea a celor
opt regine, investigat de Gauss n 1850, dar pe care nu a rezolvat-o complet. Problema este
urmtoarea: opt regine trebuie plasate pe tabla de ah, astfel nct ca nici una dintre ele s nu o atace pe
oricare alta.
tim din regulile ahului c o regin amenin orice alt figur care se afl pe aceeai coloan,
linie sau pe diagonalele duse prin punctul respectiv. De aici deducem c fiecare coloan poate conine
numai o singur regin (de altfel acelai lucru este valabil i pentru fiecare linie) i deci alegerea
poziiei reginei i se poate face numai pe coloana i. Astfel parametrul i devine indicele coloanei iar
procesul de selecie pentru poziii va parcurge cele 8 valori posibile pentru indexul liniei j. Pentru
reprezentarea poziiilor celor 8 regine pe tabla de ah am putea alege, ca i n exemplul anterior, o
matrice ptrat, dar aceast reprezentare ar conduce la operaii greoaie pentru verificarea corectitudinii
poziiilor. Vom alege o reprezentare care conduce la verificri ct mai comode i mai simple. n cazul
nostru aceast reprezentare nu este poziia reginei, dar reprezentarea trebuie s precizeze care linii i
diagonale sunt acoperite de o regin, ntruct tim deja c fiecare coloan, k, conine o regin
(1<=k<=i). Din aceste motive vom utiliza urmtoarele variabile:
x[i], specific poziia reginei n coloana i;
a[j], nu este nici o regin n linia j;
b[k], nici o regin nu este n diagonala k, diagonala de la dreapta la stnga;
c[k], nici o regin nu este n diagonala k, diagonala de la stnga la dreapta;
Limitele indicilor sunt determinai de modul n care b i c sunt calculai; n cazul diagonalei de la
dreapta la stnga toate cmpurile unei diagonale au aceeai sum pentru coordonatele i i j , n timp ce
pentru diagonalele de la stnga la dreapta diferena coordonatelor i j este constant. Cu aceste date
selecia unui candidat (regina), din algoritmul generalizat, va fi:
x[i] = j; a[j] = 0; b[i+j] = 0; c[i-j] = 0;
iar declaraia de tergere a unui candidat (regina) va fi:
x[i] = 0; a[j] = 1; b[i+j] = 1; c[i-j] = 1;
5

Condiia acceptabil va fi ndeplinit dac cmpul (i, j) se afl pe o linie i pe diagonale care sunt nc
libere (au valoarea 1), care, deci, poate fi exprimat prin condiia:
a[j] && b[i-j] && c[i-j]
Deoarece vectorul c[i-j] ar urma s aib indicii de la 7 la +7 i ntruct acest lucru n C nu este posibil
vom translata acest indice n intervalul 0-14, adunndu-i 7. Vectorul b[i+j] va avea indicii n intervalul
2-16. n concluzie ntreg programul este urmtorul:
/* regine1s.c - programul furnizeaza o solutie (prima) pentru problema
asezarii a 8 regine pe tabla de sah, astfel incat nici una dintre ele
sa nu o atace pe alta (prima regina este amplasata in pozitia (1,1) */
#include <stdio.h>
#include <conio.h>
int a[9] = {0, 1, 1, 1, 1, 1, 1, 1, 1}; // indice a = linia 'k'
// initializarea pozitiilor, pe linii, de la 1 la 8 (0 nu este utilizata)
int b[17] = {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// initializarea pozitiilor pentru diagonala dr-st, de la 2 la 16,
// pozitiile 0 si 1 nu sunt utilizate
int c[15] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// initializarea pozitiilor de la 0 la 14, pentru diagonala dr-st.
int x[9]; // vectorul pozitiilor pe coloane, indice x=linia, x[]=coloana
void incearca (int i, int *pq)
{int j;
j=0;
do
{
j++; *pq=0; // 0 inseamna ca nu este solutie buna
if (a[j] && b[i+j] && c[i-j+7]) // plasare corecta ?
{
x[i]=j; // memorare pozitie (linie, coloana), si
a[j]=0; b[i+j]=0; c[i-j+7]=0; // "activare" directii
if (i < 8)
// de "atac" ale reginei respective
{
incearca (i+1, pq); // plasare regina urmatoare
if ( !(*pq) )
{// daca pozitionarea nu a fost buna
a[j]=1; b[i+j]=1; c[i-j+7]=1;// se sterg
} //"directiile" atacate de regina respectiva
}
else
*pq=1; // solutie completa
}
}while (!(*pq) && j !=8); // se reia cat timp solutia nu este buna
}
// si nu s-au asezat 8 regine pe tabla de sah
void main (void)
{
int i, q;
clrscr();
incearca (1, &q);
for (i=1; i<= 8; i++)
6

printf("%4d", x[i]);
printf("\n");
}
Metoda prezentat furnizeaz o sigur soluie. S extindem algorimul, n termeni generali,
pentru a determina toate soluiile. Pentru aceasta va trebui s generm candidaii n mod progresiv, ntro manier sistematic care s garanteze c nici un candidat nu este generat dect o singur dat.
Aceast proprietate a algoritmului corespunde unei cutri n arborele candidailor ntr-un mod
sistematic n care fiecare nod este vizitat o singur dat. Aceasta pemite, odat soluia gasit i
memorat, s se treac la urmtorul candidat, furnizat de procesul sistematic de selecie. Schema
general, derivat din ultima, este urmtoarea:
ncearc ( i )
{
for ( k = 0; k<= m; k++)
{
selecteaz candidatul k;
dac (acceptabil)
{
memoreaz candidatul;
dac ( i < n )
ncearc ( i + 1 );
altfel
tiparete soluia;
terge candidatul memorat;
}
}
}
Procesul de cutare pentru toate soluiile este realizat de un program simplu, spre deosebire de
cutarea unei singure soluii.
Programul pentru problema celor 8 regine care furnizeaz toate soluiile (fr a recunoate
simetriile) este prezentat n continuare.
/* reginets.c - programul furnizeaza toate solutiile pentru problema
asezarii a 8 regine pe tabla de sah, astfel incat nici una dintre ele
sa nu o atace pe alta (prima regina este amplasata in pozitia (1,1) */
#include <stdio.h>
#include <conio.h>
int a[9] = {0, 1, 1, 1, 1, 1, 1, 1, 1}; // indice a = linia 'k'
// initializarea pozitiilor, pe linii, de la 1 la 8 (0 nu este utilizata)
int b[17] = {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// initializarea pozitiilor pentru diagonala dr-st, de la 2 la 16,
// pozitiile 0 si 1 nu sunt utilizate
int c[15] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// initializarea pozitiilor de la 0 la 14, pentru diagonala dr-st.
int x[9]; // vectorul pozitiilor pe coloane, indice x=linia, x[]=colaona
int ntotsol=0; // numarul total de solutii
void afiseaza (void)
{
7

int k;
printf("Sol. %2d: ", ntotsol+1);
for ( k = 1; k <= 8; k++)
printf("%4d", x[k]);
printf("\n");
ntotsol++; // contorizez numarul total de solutii
if (ntotsol%20 == 0){
printf("\nProgramul (si afisarea) continua! Apasati o tasta!\n");
getch();
}
}
void incearca (int i)
{
int j;
for (j=1; j<=8; j++)
if (a[j] && b[i+j] && c[i-j+7])
{x[i]=j; // memorare pozitie (linie, coloana), si
a[j]=0; b[i+j]=0; c[i-j+7]=0; // "activare" directii
if ( i < 8 )
// de "atac" ale reginei respective
incearca (i+1);
else
afiseaza (); // afiseaza solutia gasita
a[j]=1; b[i+j]=1; c[i-j+7]=1;// se sterg
} // "directiile" atacate de regina respectiva
}
void main (void)
{
clrscr();
incearca (1);
printf("\nNumarul tota de solutii gasite este %4d.\n", ntotsol);
printf("\nProgramul s-a terminat! Apasati o tasta!\n");
getch();
}
Iat i varianta care ofer toate soluiile pentru problema anterioar (acoperirea tablei de ah cu
mutrile calului).
// turcalts.cpp - program "knight tour" : "acoperirea" unei table de sah
// prin mutarile unui cal, astfel incat fiecare casuta a tablei sa fie
// parcursa (atinsa) o singura data, programul furmizeaza toate solutiile
// posibile, pornind din coltul din stanga sus, pozitia de coordonate (1,1)
#include <stdio.h>
#include <conio.h>
#define N 5
#define NSQ N*N
#define NrMaxMutCal 8
// N - dimensiunea tablei de sah; NSQ=N^2;
// NrMaxMutCal = 8, numarul maxim de mutari posibile ale calului,
// dintr-o pozitie data
8

int nsl, nsv, nse; // numar de solutii pe linie/ pe veritcala/ si pe ecran


int h[N+1][N+1];
int a[NrMaxMutCal+1] = {0, 1, 1, 2, 2, -1, -1, -2, -2};
// pozitiile relative pe cele doua
int b[NrMaxMutCal+1] = {0, 2, -2, 1, -1, 2, -2, 1, -1};
// coordonate, ale celor 8 mutari posibile
long int nr_sol=0, nr_ramuri=0;
// numar total de solutii, numar de ramuri parcurse pt. a gasi solutiile
void afiseaza_solutie (void) {
int xe, ye, l, c;
xe = nr_sol % nsl; // pozitia pe axa x (orizontala) a solutiei de afisat
ye = (nr_sol / nsl) % nsv; // pozitia pe verticala (y) a solutiei de afisat
for (l=1; l<=N; l++)
for (c=1; c<=N; c++)
{gotoxy(xe*(3*N+2)+c*3, ye*(N+1) + l); // coordonatele de
printf("%2d", h[l][c]);
// afisare a solutiei
}
nr_sol++; // contorizare numar de solutii
if (nr_sol % nse == 0) // continua afisarea urmatorului ecran de solutii
{printf("\n"); // dupa afisarea a 'nse' solutii, apasa tasta 'Enter'
printf("Apasati ENTER pentru a continua afisarea");
getch();
clrscr();
}
}
void muta ( int m, int x, int y){// determina mutarea urmatoare
int k, u, v;
for (k=1; k<=NrMaxMutCal; k++)
{u = x + a[k]; // noile coordonate ale urmatoarei mutari (u,v)
v = y + b[k]; // pornind din pozitia (x,y)
if ((u >= 1 && u <= N) && (v >= 1 && v <= N)) // mutare valida ?
if (h[u][v] == 0) // si care nu a fost acoperita anterior
// daca mutarea este valida si casuta nu a fost vizitata
{h[u][v] = m; // se memoreaza mutarea
if (m < NSQ) // daca solutia nu este completa incearca
muta (m+1, u, v); // mutarea urmatoare, din poz (u,v)
else
// daca este completa se afiseaza
afiseaza_solutie();
h[u][v] = 0; // dupa afisarea mutarii se sterge mutarea,
}
// pentru a continua cu alta varianta posibila
else
nr_ramuri++;// se numara ramurile taiate (fundaturi)
}
// adica cele parcurse anterior
};
void main (void) {
int il, ic;
clrscr();
nsv = 2;
// initializare pentru N=8
9

nsl = 2;
// dupa care vor fi actualizate pentru N-ul curent
if (N<6)
{nsl = 4;
// nsl = numar de solutii afisate pe linie
nsv = 4;
// nsv = numar de solutii afisate pe verticala
}
else if (N<8)
{nsl = 3;
if (N == 6)
nsv = 3;
}
nse = nsl*nsv;
// numarul de solutii afisate pe ecran
for (il=1; il<=N; il++) // initializare tabla sah, care va memora mutarile
for (ic=1; ic<=N; ic++)
h[il][ic] = 0;
h[1][1] = 1;
muta( 2, 1, 1);
printf("\nNumar total de solutii: %li\n", nr_sol);
printf("\nNumarul total de incercari (ramuri arbore incercari): %li\n",
nr_ramuri);
}
O alt problem rezolvat cu ajutorul metodei backtracking este problema determinrii soluiei
pentru jocul-minune, Sudoku.
// sudoku1s.cpp - program pentru "acoperirea" unui careu de 81 casute (9*9)
// din care o parte sunt fixate, dupa urmatoarea regula:
// orice linie, coloana sau patrat de 3*3 casute sa contina o singura data
// fiecare cifra cuprinsa intre 1 si 9. Verificarea incrucisata a liniilor
// coloanelor si patratelor mici de 3*3 ofera indicii pentru gasirea solutiei
// se determina si afiseaza prima solutie gasita
#include <stdio.h>
#include <conio.h>
#define N 9
#define NSQ N*N
// N - dimensiunea careului de joc; NSQ=N^2;
int q;
long int nr_incercari;
int si[N][N]={{8,0,3,0,0,9,1,0,0},
{0,0,9,0,0,1,0,0,0},
{6,0,0,0,4,0,0,0,8},
{0,0,0,6,0,7,0,5,0},
{3,0,6,0,0,0,2,0,9},
{0,7,0,9,0,3,0,0,0},
{5,0,0,0,7,0,0,0,2},
{0,0,0,1,0,0,7,0,0},
{0,0,1,4,0,0,8,0,3}
};
int s[N][N];
10

int vp[NSQ][N+1]; // matricea de valori permise pentru fiecare casuta


// din careu, construita in concordanta cu regulile jocului
// matricea are o coloana in plus (N+1), care este de fapt
// prima (0) si in care vom pune -1 pentru casutele ce au
// valori impuse (sau pentru cele care nu mai au decat o
// singura alegere, pentru a reduce numarul de incercari
void init_joc(){
int i, j, l, c, il, ic, nr1;
clrscr();
printf("\nInitializare careu de joc:\n");
/*for (i=0; i<N; i++){// initializare careu de joc
printf("Valorile pentru linia %d (separate prin spatii): ", i+1);
for (j=0; j<N; j++)
scanf("%d", &si[i][j]);
}
clrscr();*/
for (i=0; i<N; i++)
{for (j=0; j<N; j++)
printf("%4d", si[i][j]);
printf("\n");
}
for (i=0; i<NSQ; i++)
// i-casuta pentru care determin valorile posibile
{vp[i][0]=0; // pozitia curenta (initiala) in cadrul vectorului i
for (j=1; j<=N; j++)
vp[i][j]=1; // initial sunt permise toate valorile (1-9)
l=i/N; c=i%N;
// linia si coloana elementului 'i' din careul 9*9
if (si[l][c]==0) // daca casuta nu are valoare initiala
{
// se cauta valorile 'impuse' pe coloana respectiva
for (il=0; il<N; il++) // si pentru acestea (diferite de 0)
if (si[il][c]) // se elimina din lista de valori
vp[i][si[il][c]]=0; // posibile casutei resp.
for (ic=0; ic<N; ic++) // se repeta aceeasi operatie si
if (si[l][ic]) // pentru linia respectiva
vp[i][si[l][ic]]=0;
l=(i/N/3)*3; c=(i%N/3)*3;// l si c elem 'i' din careul 3*3
for (il=l; il<l+3; il++) // se aplica aceeasi regula si
for (ic=c; ic<c+3; ic++) // pentru careul 3*3 in care
if (si[il][ic]) // se afla casuta i, (l,c)
vp[i][si[il][ic]]=0; // elimina val
} // valorile eliminate sunt cele reprezentate de indicii
// pentru care in matrice punem 0; 1 - indici=val permise
else // daca casuta are valoare initiala, atunci pun 0 peste tot
{for (ic=1; ic<=N; ic++) // cu exceptia casutei cu val impusa
vp[i][ic]=0; // si pun -1 in prima coloana (0)
vp[i][0]=-1; // pozitii ce contin valori initiale
vp[i][si[l][c]]=1;
}
nr1=0;// verific daca pentru pozitia i, exista doar 1 nr posibil
11

for (j=1; j<=N; j++) // pentru a pune in prima coloana (0) din vp
if (vp[i][j]) // valoarea -1, care specifica ca nu avem
nr1++; // decat o singura solutie (alegere)
if (nr1==1)
vp[i][0]=-1;
}
for (i=0; i<N; i++) // se transfera matricea initiala in matricea finala
for (j=0; j<N; j++) // cea care va contine solutia jocului
s[i][j]=si[i][j];
/* clrscr(); // afisarea matricii de valori permise pentru fiecare casuta
printf("Matricea numerelor permise pentru fiecare pozitie din careu:\n");
for (i=0; i<NSQ; i++){
if (i%18==0) // se continua afisarea dupa un numar de linii afisate
{printf("\nContinua afisarea dupa apasarea unei taste\n");
getch();
clrscr();
}
printf("Pozitia [%d,%d] : ", i/N+1, i%N+1);
printf("% d ", vp[i][0]);
for (j=1; j<=N; j++)
if (vp[i][j])
printf("%d ", j);
else
printf("%d ", 0);
printf("\n");
} */
}
int valid(int ip, int k){// fct verifica daca valoarea k poate fi pe poz ip
int l, c, i, j;
l=ip/N; c=ip%N;
// linia 'l' si coloana 'c' a elementului 'ip' din careul 9*9
for (i=0; i<N; i++) // daca valoarea exista pe coloana respectiva (c)
if (k==s[i][c]) // nu mai poate fi incercata (valida) pentru casuta
return 0; // curenta (ip) si returneaza invalid (0)
for (j=0; j<N; j++) // aceeasi conditie se testeaza si pentru linia 'l'
if (k==s[l][j])
return 0; // valoarea k nu poate fi pusa in casuta 'ip'
l=(ip/N/3)*3; c=(ip%N/3)*3;// l si c elem 'ip' din careul 3*3 corespunzator
// in care este inclusa casuta 'ip', deci 'l' si 'c'
// reprezinta coord. stanga-sus al careului 3*3
for (i=l; i<l+3; i++) // se verifica conditia de validitate si pentru careul
for (j=c; j<c+3; j++) // 3*3 in care se afla casuta 'ip'
if(k==s[i][j])
return 0;
return 1; // daca toate conditiile (linie/coloana/careu 3*3) sunt
}
// indeplinite, functia returneaza 1 (valoare valida)
void incearca (int i, int *pq){// determina valoarea urmatoare
int k, x, y, q1=0;
// pentru casuta 'i'
k = 0;
12

x=i/N; y=i%N; // coordonatele casutei 'i' in careul 9*9 -> (x,y)


do {k++;
// daca vp[i][0]=-1 inseamna ca este o valoare impusa/ unica
if (k<=9 && vp[i][0]==-1){// determin solutia unica pentru pozitia 'i'
if(vp[i][k])
{s[x][y]=k; // memorez solutia
q1=1; // solutie corecta (si unica)
if (i<NSQ-1){ // daca careul nu a fost completat in intregime
incearca(i+1, &q1); // trece la casuta urmatoare
if (!q1) // daca sol. e valida se sterge acest numar
{if(vp[i][0]!=-1)// sterg nr. k de pe poz. i
s[x][y]=0;
nr_incercari++; // numara ramurile taiate
}
}
else // s-a acoperit careul, solutie completa
q1 = 1;// daca s-a acoperit careul se va tipari sol.
}
// se va continua daca nu s-a gasit o solutie (q1=0)
} //si daca mai sunt numere posibile pentru casuta curenta
else if (k<=9 && vp[i][k]){
// pozitie ce nu are solutie unica
q1 = 0;
// initializare indicator 'succes numar'
if (k<=N && valid(i,k))// nr. valid si nu e deja pe lin/col/patrat
{s[x][y] = k; // memorare numar 'k' in casuta (x,y)
if (i < NSQ-1)
{// daca solutia nu e completa se incearca urm poz
incearca (i+1, &q1);
if (!q1) // daca nu este valida se sterge acest nr.
{if(vp[i][0]!=-1) // se sterge doar daca nu e
s[x][y]=0; // val unica pt casuta 'i'
nr_incercari++; // se numara ramurile taiate
}
// (infundate)
}
else
q1 = 1;// daca s-a acoperit careul tipareste
}// se continua daca nu s-a gasit o solutie (q1=0)
}
// si daca mai sunt
} while (!q1 && k<=N); // numere posibile (k<=N) pentru pozitia curenta
*pq = q1; // se transmite reusita sau nereusita plasarii numarului respectiv
}
void main (void) {
int i, j;
init_joc();
for (i=0; i<N; i++) // initializare careul de joc final (solutia)
for(j=0; j<N; j++)
s[i][j]=si[i][j];
printf("Careul initial este urmatorul:\n");
for (i=0; i<N; i++)
{for (j=0; j<N; j++)
if (s[i][j])
13

printf("%2d", s[i][j]);
else
printf(" ");
printf("\n");
}
incearca(0, &q);// apel pt. i=0, in pozitia (0,0), q-succes joc (solutie)
if (!q)
// acoperire completa careul de joc
printf("Problema nu are solutie !!!\n");
else
{printf("\nSolutia finala este urmatoarea:\n");
for (i=0; i<N; i++)
{for (j=0; j<N; j++)
printf("%2d", s[i][j]);
printf("\n");
}
printf("\nNumarul total de incercari a fost de: %ld\n",nr_incercari);
}
printf("Pentru terminare apasati o tasta!\n");
getch();
}

Pentru exemplul ales matricea si (Sudoku iniial) are urmtoarea configuraie:


8

3
9

9 1
1

6
7

5
2

7
1
1 4

14

8
9
2
7
8

n continuare se construiete, n funcia init_joc() matricea de valori permise (vp) pentru fiecare
csu din careul 9*9, pentru a reduce numrul de ncercri pentru fiecare celula din careu. Aceast
matrice are ca indice de linie (i) numrul csuei din careul iniial (de la 0 la 80, n loc de 1 la 81), iar
indicii coloanelor (j) reprezint valorile ce pot fi ncercate pentru csua (linia) respectiv; dac
valoarea din matricea vp este 1 nseamn c indicele coloanei respective (j) este o valoare ce poate fi
ncercat pentru csua respectiv (i), n caz contrar este o valoare nepermis. Valorile permise sunt n
conformitate cu regulile jocului, valori ce nu apar pe colona respectiv, pe linia respectiv, precum i
valori ce nu apar n careul 3*3 n care este inclus celula respectiv (careuri desenate ngroat n tabela
de mai sus).
Matricea valorilor permise vp are o coloan n plus (N+1) care este chiar prima, coloana indice
0, care va conine valoarea 1 dac csua respectiv are o valoare impus iniial (valorile specificate n
tabela dat mai sus), sau dac n urma verificrii condiiilor de completare a csuei respective rezult
c nu are dect o singur soluie. n celelalte linii de pe coloana 0 se va memora, pe durata ncercrii
(algoritmului), valoarea curent ncercat n celula i.
De exemplu pentru careul anterior, matricea vp (numai primele 20 de linii, din cele 81) are
urmtoarea configuraie:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

0
-1
0
-1
0
0
-1
-1
0
0
0
0
-1
0
0
-1
0
0
0
-1
0

1 2 3 4 5 6 7 8 9
1
1
1 1
1
1
1
1
1
1 1
1
1
1
1
1 1
1 1 1 1
1
1
1
1
1 1
1
1 1
1
1 1
1 1
1 1
1
1
1 1 1 1
1 1 1
1 1
1 1 1 1
1
1
1 1
1

Un ultim exemplu de backtracking const n nsumarea unor valori ntregi dintr-un vector
pentru a obine o anumit valoare dat (programul furnizeaz toate soluiile posibile).
/* sumvalv.c - programul citeste un vector de valori intregi
si determina daca o anumita valoarea (suma dorita) se poate furniza
prin insumarea valorilor din vector, si ofera toate solutiile posibile
15

- exemplu de backtracking */
#include <stdio.h>
#include <conio.h>
#define MAXV 16
typedef struct
{int nval, val[MAXV]; /* numar valori disponibile in vector */
long rest;
/* si vectorul; rest (suma) de realizat */
int nrsol;
/* numar solutii */
unsigned sel; /* marcaj valori selectate, prin setarea
bitului corespunzator pozitiei valorii selectate */
} TDate;
int nle=0; /* numar de linii afisate pe ecran */
void AfiSolutie (TDate *a)
{int i;
unsigned x;
printf ("%2i:", ++a->nrsol);
for (x = a->sel, i = 0; i < a->nval; i++, x >>= 1)
if (x & 1)
printf ("%4i", a->val[i]);
else
printf(" -");
printf("\n");
nle++;
if(nle%23==0)
{printf("Se continua afisarea dupa apasarea unei taste!\n");
getch();
}
}
/* cauta variante de completare a restului din solutia curenta
prin backtracking */
void Cauta (TDate *a, int iv) /* iv - indicele valorii analizate */
{if (a->rest == 0)
/* solutie exacta */
{AfiSolutie (a); return;}
for (; iv < a->nval ; iv++) /* cat timp mai exista valori de analizat */
if (a->rest >= a->val[iv] ) /* valoarea iv este acceptabila */
{a->sel += (1 << iv);/* avans: marcheaza acceptare val[iv],
prin pozitionarea unui bit 1 pe pozitia iv */
a->rest -= a->val[iv]; /* si micsoreaza rest de completat */
Cauta (a, iv+1);
/* continua cautarea */
a->sel -= (1 << iv); /* revenire: renunta la val[iv],
prin stergerea bitului 1 de pe pozitia iv */
a->rest += a->val[iv]; /* si reface rest de completat */
}
}
void main ()
{int i;
long Suma;
16

TDate x;
clrscr();
printf ("Introduceti valori, terminand cu 0 sau caracter nenumeric:\n");
for (i = 0; i < MAXV; i++)
if (scanf("%i", &(x.val[i])) < 1 || x.val[i] == 0)
break;
if (!i)
{ printf ("Lipsa date!!!\n"); exit(1); }
x.nval = i;
printf("\nAti introdus %u valori:\n", x.nval);
for (i = 0; i < x.nval; i++)
printf ("%5i", x.val[i]);
printf ("\n");
for (;;)
{printf ("\nSuma dorita de construit (calculat) cu valori "
"din vector\n\t(oprire program orice caracter nenumeric): ");
fflush(stdin);
if (!scanf ("%ld", &Suma))
break;
x.sel = 0; x.rest = Suma; x.nrsol = 0;
Cauta (&x, 0);
/* Cauta solutii */
if (! x.nrsol)
printf ("Nu exista solutie !\n");
}
}

3.2. Greedy (optim local)


Algoritmii greedy (greedy = lacom) sunt n general simpli i sunt folosii la probleme de
optimizare, cum ar fi gsirea celei mai bune ordini de executare a unor taskuri pe calculator, gsirea
celui mai scurt drum ntr-un graf etc. n astfel de situaii avem:
- o mulime de candidti (taskuri de executat, vrfuri ale grafului etc);
- o funcie care verific dac o mulime de candidai este posibil (adecvat), adic dac este
posibil s completm aceast mulime astfel nct s obinem o soluie posibil, nu neaprat
optim, a problemei;
- o funcie de selecie care indic la orice moment care este cel mai promitor dintre
candidaii nc nefolosii;
- o funcie obiectiv care d valoarea unei soluii (timpul necesar executrii tuturor lucrrilor
ntr-o anumit ordine, lungimea drumului pe care l-am gsit etc); aceasta este funcia pe care
urmrim s o optimizam (minimizm/maximizm).
Aceast tehnic se folosete n situaiile n care este dat o mulime A, cu n elemente i se cere
s se gseasc o submulime B, a sa, care s ndeplineasc anumite condiii (restricii sau un criteriu de
optim).
Algoritmul greedy construiete soluia pas cu pas. Iniial, mulimea candidatilor selectai (B)
este vid. La fiecare pas, ncercm s adugm acestei mulimi cel mai promitor candidat, conform
funciei de selecie. Dac, dup o astfel de adugare, mulimea de candidai selectai nu mai este
fezabil, eliminm ultimul candidat adugat; acesta nu va mai fi niciodata considerat. Dac, dup
17

adugare, mulimea de candidai selectai este adecvat, ultimul candidat adugat va rmne de acum
ncolo n ea. De fiecare dat cnd lrgim mulimea candidailor selectai, verificm dac aceast
mulime nu constituie o soluie posibil a problemei noastre.
Metoda este urmtoarea:
- se alege un element din mulimea A;
- se testeaz dac acesta poate fi adugat unei mulimi B, iniial vid;
- se repet acest procedeu pn au fost alese toate elementele pentru mulimea B (i eventual
parcurse toate cele din mulimea A);
Dac algoritmul greedy funcioneaz corect, prima soluie gsit va fi totodat o soluie optim
a problemei. Soluia optim nu este n mod necesar unic; se poate ca funcia obiectiv s aib aceeai
valoare optim pentru mai multe soluii posibile.
Alegerea unui element din mulimea A, mai ales dac aceasta are un numr foarte mare de
elemente, pentru a determina dac acesta poate fi adugat mulimii B poate fi fcut astfel:
- fie dispunem de anumite criterii de alegere, care conduc la rezultatul dorit;
- fie nu dispunem de astfel de criterii, dar putem face o alegere care conduce la un rezultat
acceptabil (dar nu optim), situaie n care trebuie s apreciem distana fa de soluia
optim.
Descrierea formal a unui algoritm greedy este urmtoarea:
greedy (A)
/* A este mulimea candidailor */
B=0; /* B este mulimea n care construim soluia, iniial vid */
while ( ! soluie (B) && A!=0)
{
x = un element din A care maximizeaz/minimizeaz select(x);
A = A x;
if fezabil(B x})
B=Bx
}
if soluie(B)
return B
else
return nu exist soluie
Este de neles acum de ce un astfel de algoritm se numete lacom (greedy), deoarece la
fiecare pas, funcia alege cel mai bun candidat la momentul respectiv, fr s-i pese de viitor i fr s
revina asupra alegerii. Dac un candidat este inclus n soluie, el rmne n soluie, iar dac un
candidat este exclus din soluie, el nu va mai fi niciodata reconsiderat. O astfel de metod poate da
rezultate foarte bune datorit simplitii ei.
O problem clasic pentru aceast tehnic este problema rucsacului: un rucsac care poate
transporta o greutate G, trebuie ncrcat cu obiecte alese dintre n obiecte, fiecare avnd o anumit
greutate (g) i un anumit profit (importan) (p) sau ctig. Se cere s se determine obiectele ce trebuie
ncrcate n rucsac astfe nct profitul (ctigul) s fie maxim.
Exist dou variante ale acestei probleme, dup cum obiectele pot fi tiate sau nu pentru
includerea lor n rucsac: problema continu, respectiv problema discret a rucsacului. Bineneles c
cele dou probleme se rezolv n mod diferit.
Problema continu a rucsacului.
18

n acest caz exist posibilitatea ca obiectele s fie tiate. n acest fel se poate obine o ncrcare
mai eficient a rucsacului:
- se determin pentru fiecare obiect eficiena de transport rezultat prin mprirea profitului
la greutatea obiectului;
- se sorteaz obiectele n ordinea descresctoare a eficienei de transport i se iau n calcul n
aceast ordine;
- iniial profitul este 0, iar greutatea rmas de ncrcat este egal cu G;
- ct timp nu s-a ajuns la greutatea maxim (G) i nu au fost luate n considerare toate
obiectele se va selecta obiectul cel mai eficient pentru care avem dou variante:
- intr n ntregime n rucsac i atunci se va scdea greutatea lui din cea rmas de ncrcat;
- nu intr, n totalitate, n rucsac i atunci se determin ce procent din obiectul respectiv va
intra n rucsac; n acest caz se va cumula la ctigul total procentul de profit asociat prii
din obiect ncrcat n rucsac, iar greutatea rmas va fi zero.
Deci conform cu acest algoritm programul este urmtorul.
/* rucsacc.c - programul incarca un rucsac, utilizand metoda Greedy
(problema continua a rucsacului)*/
#include <stdio.h>
#include <conio.h>
#define DIM_MAX 20
void main()
{
float p[DIM_MAX], g[DIM_MAX], ef[DIM_MAX];
int n, i, j, inv, ordonat[DIM_MAX];
float gr, castig;
clrscr();
printf("Capacitate rucsac (greutate admisa): ");
scanf("%f", &gr);
printf("Numarul de obiecte ce trebuie incarcate in rucsac (disponibile): ");
scanf("%d", &n);
printf("Specificati pentru fiecare obiect greutatea/profitul:\n");
for(i=0;i<n;i++)
/* citirea elementelor sirului de sortat */
{
printf("Greutate(%d)= ",i+1);
scanf("%f", &g[i]);
printf("Profit(%d) = ",i+1);
scanf("%f", &p[i]);
ordonat[i]=i;
ef[i]=p[i]*g[i];
}
do
/* ordonez indici obiecte in vector ordonat, functie de eficienta */
{
/* utilizand metoda inversiunilor (bulelor)
*/
inv=0;
for (i=0; i < n-1; i++)
if (ef[ordonat[i]] < ef[ordonat[i+1]])
{
j=ordonat[i];
ordonat[i]=ordonat[i+1];
19

ordonat[i+1]=j;
inv=1;
}
} while (inv);
castig=0;
i=0;
printf("\nSe incarca in rucsac:\n");
while (gr > 0 && i < n)
{
if (gr >= g[ordonat[i]])
{
printf("\tObiect %d incarcat in intregime\n", ordonat[i]+1);
gr=gr - g[ordonat[i]];
castig=castig + p[ordonat[i]];
}
else
{printf("Obiect %d incarcat in proportie de"
" %.2f %\n", ordonat[i]+1, gr/g[ordonat[i]]*100);
castig=castig + p[ordonat[i]]*gr/g[ordonat[i]];
gr=0; // rucsacul este plin, greutate disponibila 0
}
i++;
}
printf("\nObiectele incarcate in rucsac sunt urmatoarele:\n");
for (j=0; j<i; j++)
printf("\tObiectul: %d, de greutate: %.2f si profit: %.2f\n",
ordonat[j]+1, g[ordonat[j]], p[ordonat[j]]);
if (gr){
printf("\nSe mai pot incarca obiecte de greutate: %.2f", gr);
printf("\n\t\t rucsacul nu este plin, dar nu mai sunt obiecte.\n");
}
printf("\nProfitul total obtinut pentru aceasta incarcare: %.2f\n", castig);
printf("\nApasati o tasta pentru a termina programul !!!\n");
getch();
}
Problema comis-voiajorului
Se cunosc disantele dintre mai multe orae. Un comis-voiajor pleac dintr-un ora i dorete s
se ntoarc n acelai ora, dup ce a vizitat fiecare din celelalte orae numai o dat. Problema este de a
minimiza lungimea drumului parcurs.
Problema poate fi reprezentat printr-un graf neorientat, n care oricare dou vrfuri diferite ale
grafului sunt unite ntre ele printr-o muchie, de lungime pozitiv. Cutm un ciclu de lungime minim,
care s se nchid n varful iniial i care s treaca prin toate vrfurile grafului.
Conform strategiei greedy, vom construi ciclul pas cu pas, adugnd la fiecare iteraie cea mai scurt
muchie disponibil cu urmtoarele proprieti:
- nu formeaz un ciclu cu muchiile deja selectate (exceptnd pentru ultima muchie aleas, care
completeaz ciclul)
20

- nu exist nc dou muchii deja selectate, astfel nct cele trei muchii s fie incidente n acelai
varf
Matricea distanelor pentru problema comis-voiajorului este urmtoarea (matricea este simetric fa de
diagonala principal, ntruct distana i-j este egal cu distana j-i):
La nodul 2
De la nodul:
1
3
2
3
4
5

10
6

11
12
9

7
8
4
5

25
26
20
15
18

De exemplu, pentru ase orae a caror matrice a disantelor este prezentat, muchiile se aleg n
ordinea: {1, 2}, {3, 5}, {4, 5}, {2, 3}, {4, 6}, {1, 6} i se obine ciclul (1, 2, 3, 5, 4, 6, 1) de lungime
58. Algoritmul greedy nu a gasit ciclul optim, deoarece ciclul (1, 2, 3, 6, 4, 5, 1) are lungimea 56.

3.3 Algoritmi divide et impera


Divide et impera este o tehnic de elaborare a algoritmilor care const n:
- descompunerea problemei ce trebuie rezolvat ntr-un numr de subprobleme, mai mici, ale
aceleiai probleme, ce pot fi rezovate separat;
- rezolvarea succesiv i independent a fiecreia din aceste subprobleme;
- recompunerea subsoluiilor astfel obinute pentru a gsi soluia problemei iniiale.
Cutarea binar este una dintre cele mai simple aplicaii ale metodei divide et impera, i const, n
esen, este metoda dup care se caut un cuvnt ntr-un dicionar, sau un nume n cartea de telefon.
Acesat metod a fost prezentat n cadrul metodelor de sortare.
Tot n capitolul referitor la sortare a fost prezentat metoda Mergesort (sortarea prin interclasare)
pentru sortarea unui vector V. Prin tehnica divide et impera se proceedeaz astfel: separm vectorul V
n dou pri de mrimi ct mai apropiate, sortm aceste pri prin apeluri recursive, apoi interclasam
soluiile pentru fiecare parte, avnd grij s pstrm ordonarea crescatoare a elementelor. De acelai tip
este i algoritmul QuickSort.
Turnurile din Hanoi
Vom prezenta, n continuare, o funcie recursiv folosit pentru rezolvarea unei probleme
clasice aprut la sfritul secolului al XIX-lea, n Europa, cunoscut sub numele de "Turnurile din
Hanoi". n esen problema este urmtoarea: se dau trei tije, denumite stnga (sursa), mijloc (aux) i
dreapta (dest) i 64 de discuri, de dimensiuni (diametre) diferite, aezate unul peste altul n ordine
descresctoare (a diametrelor), pe tija din stnga, ce formeaz un "turn". Se cere s se deplaseze turnul
format din cele n discuri (n=64) din poziia stnga n poziia dreapta, prin intermediul tijei mijloc,
respectnd urmtoarele reguli:
- se mut cte un singur disc;
- nu se poate aeza un disc mai mare, ca diametru, peste unul mai mic.

21

Sursa
Dest. Aux
Sursa
Dest.
Aux
|
|
|
|
|
|
1
===
|
|
==>
|
===
|
2 =====
|
|
|
=====
|
3 =======
|
|
|
=======
|
------------------------------------------------------------------Problema mutrii celor n discuri poate fi descris recursiv astfel:
- dac n=1 , se mut un singur disc;
- dac n >1 se mut primele n-1 discuri n poziia mijloc, prin intermediul poziiei dreapta; se
mut apoi discul rmas n poziia dreapta, dup care se mut cele n-1 discuri din poziia mijloc n
poziia dreapta, prin intermediul poziiei stnga.
De exemplu deplasarea celor n discuri din poziia stnga n poziia dreapta, muta (n, stanga, dreapta)
este echivalent cu urmtoarea secven de sub-probleme :
muta ( n-1 , stanga , mijloc );
deplasez un disc din poziia "stnga" n "dreapta";
muta ( n-1 , mijloc , dreapta );
Am redus problema la a deplasa n-1 discuri, iar acest procedeu poate fi repetat pentru a rezolva
problema. De exemplu operaia muta ( n-1 , stanga , mijloc ) poate fi exprimat astfel:
muta ( n-2 , stanga , dreapta );
deplaseaz un disc din poziia stnga n mijloc;
muta ( n-2 , dreapta , mijloc );
Algoritmul acesta de descompunere a problemei mutrii a k discuri n subprobleme de mutare a
k-1 discuri poate continua pn cnd se ajunge la k-1=1, adic deplasarea unui singur disc. n
descompunerea anterioar funciile definite au ca parametrii poziiile efective ce difer de la un nivel la
altul (k-> k-1). ntruct este vorba de aceeai funcie i pentru a nu fi astfel particularizate vom defini
parametrii pentru cazul general, indiferent de nivelul mutrii (k). n algoritmul general de rezolvare a
acestei probleme va trebui s specificm, pentru mutarea discurilor de la poziia surs la cea destinaie,
i poziia intermediar care este utilizat pentru a realiza un turn provizoriu. Vom nota aceast funcie
astfel :
muta ( n , sursa , mijloc , destinatie );
care nseamn c se mut n discuri de la poziia "surs" n poziia "destinaie", utiliznd poziia
intermediar "mijloc" pentru un turn provizoriu.
Aceast mutare poate fi rezolvat prin descompunerea n subprobleme astfel:
muta ( n - 1 , stanga , dreapta , mijloc );
deplaseaz un disc de la stnga la dreapta;
muta ( n - 1 , mijloc , stanga , dreapta );
Acest algoritm este valabil ct timp n>=1. Se observ c aceast funcie este recursiv,
apelndu-se pe ea nsi ct timp n>=1. ntregul program pentru rezolvarea problemei "Turnurilor din
Hanoi" este urmtorul:
/*t_hanoi.c - Problema 'Turnurilor din Hanoi', rezolvata recursiv */
#include <stdio.h>
#include <conio.h>
enum pozitie { stanga , mijloc , dreapta};
void tipareste_pozitie ( enum pozitie poz )
{
switch ( poz )
{
22

case 0 : printf ( "stanga" );/* se poate case stanga */


break;
case 1 : printf ( "mijloc" );
break;
case 2 : printf ( "dreapta" );
}
}
void deplasare( enum pozitie sursa , enum pozitie dest )
{
printf ( "muta un disc din " );
tipareste_pozitie ( sursa );
printf ( " in " );
tipareste_pozitie ( dest );
printf ( "\n" );
return;
}
void muta ( int n, enum pozitie sursa, enum pozitie inter, enum pozitie dest )
{
if ( n > 0 )
{
muta ( n - 1 , sursa , dest , inter );
deplasare ( sursa , dest );
muta ( n - 1 , inter, sursa , dest );
}
}
void main (void)
{
int i , nd;
clrscr();
printf ( "Nr. de discuri :" );
scanf ("%d", &nd);
while ( getchar() != '\n' );
printf ("Mutarile pt. deplasarea unui turn cu %d discuri sunt :\n", nd);
muta ( nd , stanga , mijloc , dreapta );
printf("\n\n");
}
Alt exemplu.
/*

programul afiseaza o valoare intreaga, pozitiva, in binar si hexa, utilizand, pentru


conversie functii recursive */
#include <stdio.h>
int n;
void binar ( int k ){
if ( k > 1 ) {
binar ( k/2 );
printf ("%i", k % 2 ); // sau printf(%i, k&1);
}
23

else
if ( k )
printf ("1");
}
void afishexa ( int k ){
if ( k < 10 )
printf("%i",k);
else
printf("%c", k-10+'A');
}
void hexa ( int k ) {
if ( k > 15 ){
hexa ( k/16 );
afishexa ( k%16 );
}
else
if ( k )
afishexa ( k );
}
void main (void){
int n;
printf("numar de afisat in binar :");
scanf("%i",&n);
printf("reprezentarea binara este: \n");
binar ( n );
printf("\n");
getch();
printf("reprezentarea hexazecimala este :\n");
hexa ( n );
printf("\n");
getch();
}
Problema tieturilor
Se consider o form dreptunghiular cu lungimea l i limea h, care are pe suprafaa ei n
guri, ce au coordonate ntregi. Se cere s se separe (taie) din aceast form dreptunghiular o bucat,
tot de form dreptunghiular, de arie maxim care nu prezint guri.
Vom considrea c poziiile gurilor sunt reinute n doi vectori xg i yg. Dreptunghiurile iniiale,
precum i cele obinute n procesul tierii sunt memorate prin coordonatele colului din stnga jos i
cele dou dimensiuni (l i h). Pentru un dreptunghi dat, la un moment dat, se caut dac are vreo gaur.;
dac are, atunci problema se descompune n alte patru subprobleme, de acelai tip. n cazul cnd bucata
respectiv nu are guri, atunci se compar aria ei cu aria bucii maxime, gsite pn n acel moment.
Dreptunghiul de arie maxim este memorat tot prin coordonatele stnga jos i dimensiuni, transmise de
la o etap la alta. Dac dreptunghiul iniial avea n guri, am descompus problema n alte patru
subprobleme, cu cel mult n-1 guri.
Pentru ca o gaur de coordonate xg(i) i yg(i) s se afle n interiorul dreptunghiului trebuie ca:
xg(i) > x; xg(i) < x+l; yg(i) > y; yg(i) < y+h;
24

Printr-o tietur vertical se obin dou dreptunghiuri de coordonate i dimensiuni:


(x, y) i dimensiuni xg(i)-x, h;
(xg(i), y) i dimensiuni l+x-xg(i), h;
iar pentru tietura pe orizontal se obin alte dou dreptunghiuri:
(x, y) i dimensiuni l, yg(i)-x;
(x, yg(i)) i dimensiuni l, h+y-yg(i);
n continuare este prezentat programul pentru rezolvarea acestei probleme.
/*taieturi.c - Problema taieturilor, exemplu de algoritm "divide et impera"*/
#include <stdio.h>
#include <conio.h>
#define MAXG 5
int n=4;
void dreptunghi (int x, int y, int l, int h,
int *px, int *py, int *pl, int *ph, int xg[], int yg[]){
int gasit, i;
i=0; gasit=0;
while (i<n && !gasit) // exista gaura in dreptunghiul ((x,y), l, h) ?
if (xg[i] > x && xg[i] < x+l && yg[i] > y && yg[i] < y+h)
gasit=1;
else
i++;
if (gasit) // daca da, avem 2 taieturi -> 4 dreptunghiuri
{dreptunghi(x, y, xg[i]-x, h, px, py, pl, ph, xg, yg);
dreptunghi(xg[i], y, l+x-xg[i], h, px, py, pl, ph, xg, yg);
dreptunghi(x, y, l, yg[i]-y, px, py, pl, ph, xg, yg);
dreptunghi(x, yg[i], l, h+y-yg[i], px, py, pl, ph, xg, yg);
}
else if (l*h > *pl* *ph) // se compara aria drept. cu cea a drept. taiat
{*px=x; *py=y;
// daca acesta nu mai contine gauri (!gasit)
*pl=l; *ph=h;
}
}
void main (void)
{
int xg[MAXG]={1, 2, 3, 4}, yg[MAXG]={1, 2, 3, 4};
int l=5, h=5;
int *px, *py, lmax=0, hmax=0;
clrscr();
dreptunghi(0, 0, l, h, px, py, &lmax, &hmax, xg, yg);
printf("\nDreptunghiul maxim este: (x,y) stanga-jos = (%d, %d)\n\t\t\t"
"lung (dx) = %d, latime (dy sau h) = %d\n", *px, *py, lmax, hmax);
printf("\nApasati o tasta pentru a termina programul !!!\n");
getch();
}

25

Problema labirintului
Se consider un labirint, specificat printr-o matrice cu n linii i m coloane, n care fiecare
element al matricei reprezint o camer din labirint. Pornind dintr-o camer oarecare, de coordonate i, j
se cere s se gseasc toate ieirile din labirint.
Un element al matricii L[i][j], ce are patru vecini (direciile spre partea de sus, dreapta, jos,
stnga ale poziiei curente, considerate n aceast ordine), poate lua valori ntre 0 i 15, dac
interpretm valoarea asociat camerei (elementului din matrice) n zecimal, codificnd (memornd)
pentru fiecare direcie cu ieire valoarea 1, iar n caz contrar cu valoarea 0. irul format cu aceste patru
cifre binare este transformat (interpretat) n zecimal i este memorat n elementul L[i][j]. De exemplu
dac camera (i, j) are ieiri spre partea de sus i spre stnga, se obine valoarea binar 1001, adic 9 n
zecimal, valoare ce va fi memorat n L[i][j].
Ieirile vor fi codificate cu valoarea 16, motiv pentru care vom ataa dou linii, sus i jos, i
dou coloane, la stnga i respectiv dreapta, ce vor conine aceast valoare.
Drumul parcurs spre ieire se va memora ntr-o matrice, d, cu dou linii i un numr de coloane
egal cu dimensiunea matricii (n*m), care va memora n cele dou linii ale unei coloane k, linia i
coloana n care s-a ajuns n drumul spre ieire.
Funcia iesire, care determin drumul spre ieire din labirint, va realiza urmtoarele operaii:
- testeaz dac nu s-a ieit din labirint (L[i][j]==16);
- dac s-a ajuns la ieire se tiprete drumul gsit;
- dac nu, se rein n matricea d[0][k]=i, d[1][k]=j), k fiind numrul curent al camerei vizitate;
- se testeaz dac aceast camer a mai fost vizitat, iar dac da se iese din funcie;
- se testeaz pe rnd cele patru direcii (sus, dreapta, jos, stnga) i acolo unde este gsit o
ieire se apeleaz funcia cu noile coordonate, i se decremeteaz valoarea k.
ntregul program este prezentat n continuare.
// ieslabir.c - problema "labirintului": iesirea dintr-un labirint
// specificat printr-o matrice L, in care se memoreaza valorile zecimale
// ale codificarilor binare ale drumului spre iesire, si anume:
// fiecare directie care duce spre iesire e codificata cu 1, iar
// restul cu 0; deci pentru o casuta oarecare se considera directiile:
// sus, dreapta, jos si stanga, in aceasta ordine; de exemplu daca o
// camera va avea directii spre iesire sus si stanga, adica 1001, in
// binar, se va memora valoarea 9; matricea va fi completata cu 2
// linii si 2 coloane care vor contine valorile 16 (iesire din labirint)
// Drumul spre iesire va fi memorat in matricea d[2][n*m], pe prima linie
// i, iar pe cea de-a doua j, pentru drumul catre iesire din pozitia k.
#include <stdio.h>
#include <conio.h>
#define N 10
#define M 15
#define DIM (N-1)*(M-1)
// N, M - dimensiuni matrice labirint, 2 linii si 2 coloane atasate;
// pentru a delimita labirintul
int L[N][M] = {{16, 16, 16, 16, 16, 16, 16},
{ 16, 0, 2, 1, 1, 0, 16},
{ 16, 0, 2, 0, 8, 0, 16},
{ 16, 0, 2, 4, 8, 0, 16},
{ 16, 1, 7, 4, 2, 0, 16},
26

{ 16, 0, 2, 0, 2, 0, 16},
{ 16, 16, 16, 16, 16, 16, 16},
};
int d[2][DIM];
int i, j, n=5, m=5, nr_sol=0, nse=4;
void tiparire (int k, int d[2][DIM])
{int h;
nr_sol++;
// contorizare numar de solutii
printf("\nSolutia %d:\n", nr_sol);
for (h=1; h<=k; h++){
printf("[%d,%d]-> ", d[0][h], d[1][h]);
if (h%8==0)
printf("\n");
}
printf("\n");
if (nr_sol % nse == 0) // continua afisarea urmatorului ecran de solutii
{printf("\n"); // dupa afisarea a 'nse' solutii, apasa tasta 'Enter'
printf("Apasati ENTER pentru a continua afisarea");
getch();
clrscr();
}
}
void iesire (int k, int i, int j, int L[N][M], int d[2][DIM]){
int gasit, h;
if (L[i][j] == 16)
tiparire(k, d);
else
{k++;
d[0][k]=i;
d[1][k]=j;
gasit=0;
for (h=1; h<=k-1; h++)
if (d[0][h] == d[0][k] && d[1][h] == d[1][k])
gasit=1;
if (!gasit)
for (h=1; h<=4; h++)
switch (h){
case 1: if (L[i][j] & 8)
iesire(k, i-1, j, L, d);
break;
case 2: if (L[i][j] & 4)
iesire(k, i, j+1, L, d);
break;
case 3: if (L[i][j] & 2)
iesire(k, i+1, j, L, d);
break;
case 4: if (L[i][j] & 1)
iesire(k, i, j-1, L, d);
27

}
k--;
}
}
void main (void) {
int il, ic;
clrscr();
/*
printf("Dimensiuni labirint (matrice), linii, coloane: ");
scanf("%d%d", &n, &m);
for (il=1; il<=n; il++)
for (ic=1; ic<=m; ic++){
printf("L[%d, %d]=", il, ic);
scanf("%d", &L[il][ic])
}
*/
printf("Punctul de plecare din labirint (i, j): ");
scanf("%d%d", &i, &j);
// matricea L, impreuna cu cele 2 linii si 2 coloane, care
// delimiteaza labirintul, si specifica iesirea din labirint
// are dimensiunile (n+2)*(m+2)
for (ic=1; ic<=m; ic++){
L[0][ic]=16;
L[n+1][ic]=16;
}
for (il=1; il<=m; il++){
L[il][0]=16;
L[il][m+1]=16;
}
printf("\nLabirintul are urmatoarea configuratie:\n\n");
for (il=0; il<n+2; il++){
for (ic=0; ic<m+2; ic++)
if (il==i && ic==j)
printf(" x");
else
printf("%3d", L[il][ic]);
printf("\n\n");
}
printf("Coordonate punct plecare L[%d,%d]=%d\n", i, j, L[i][j]);
iesire(0, i, j, L, d);
printf("\nNumar total de solutii: %i\n", nr_sol);
printf("Apasati ENTER pentru a termina programul !");
getch();
}

28

Problema fotografiei
Se consider o fotografie alb-negru, dat sub forma unei matrice binare, care reprezint mai
multe obiecte. Se consider c poriunile corespunztoare obiectului n matrice au valoarea 1, retsul
fiind 0. Considernd c obiectele sunt separate prin zerouri, se cere s se detrmine numrul de obiecte
dintr-o fotografie. Ca i n cazul problemei precedente, pentru a delimita matricea, vom ataa dou linii
(0 i n+1) i dou coloane (0 i m+1) cu valoarea 0. Algoritmul este acela;i cu cel din problema
precedent, cu deosebirea c acum cutarea se face pe opt direcii, n loc de patru. Funcia obiect va
nlocui toate unitile unui obiect cu zero, dup care programul principal va reapela aceast funcie
pn cnd nu mai sunt uniti.
// obiectpo.c - problema "fotografiei": determina numarul de obiecte
// dintr-o fotografie. Obiectele din fotografie sunt codificate cu 1,
// iar celelalte sunt 0. Pornind de la prima unitate determinata
// se cauta unitati adiacente pe cele 8 directii, care sunt puse pe 0
// eliminandu-se astfel obiectul identificat.
// Functia main() reapeleaza functia "obiect" pentru unitatile ramase
#include <stdio.h>
#include <conio.h>
#define N 10
#define M 15
// N, M - dimensiuni matrice fotografie, cu 2 linii si 2 coloane atasate;
// pentru a delimita fotografia
int F[N][M] = {{0, 0, 0, 0, 0, 0, 0},
{ 0, 1, 0, 1, 0, 1, 0},
{ 0, 0, 0, 0, 0, 0, 0},
{ 0, 1, 0, 0, 1, 0, 0},
{ 0, 0, 1, 0, 0, 1, 0},
{ 0, 0, 1, 1, 0, 1, 0},
{ 0, 0, 0, 0, 0, 0, 0},
};
int i, j, n=5, m=5;
void obiect (int i, int j, int F[N][M]){
if (F[i][j]==1){
F[i][j]=0;
// sterg 1 al obiectului curent
obiect(i-1, j, F);
obiect(i-1, j+1, F);
obiect(i, j+1, F);
obiect(i+1, j+1, F);
obiect(i+1, j, F);
obiect(i+1, j-1, F);
obiect(i, j-1, F);
obiect(i-1, j-1, F);
}
}
void main (void) {
int il, ic, nr_ob=0; // nr_ob - numar obiecte
clrscr();
/*
printf("Dimensiuni fotografie (matrice), linii, coloane: ");
29

scanf("%d%d", &n, &m);


printf("Citire fotografie:\n");
for (il=1; il<=n; il++)
for (ic=1; ic<=m; ic++){
printf("L[%d, %d]=", il, ic);
scanf("%d", &F[il][ic])
}
*/
// matricea F, impreuna cu cele 2 linii si 2 coloane, care
// delimiteaza fotografia, si specifica iesirea din fotografie
// are dimensiunile (n+2)*(m+2)
for (ic=1; ic<=m; ic++){
F[0][ic]=0;
F[n+1][ic]=0;
}
for (il=1; il<=m; il++){
F[il][0]=0;
F[il][m+1]=0;
}
printf("\nFotografia are urmatoarea configuratie:\n\n");
for (il=1; il<n+1; il++){
printf("\t");
for (ic=1; ic<m+1; ic++)
printf("%2d", F[il][ic]);
printf("\n\n");
}
printf("\nCoordonate obiecte (stanga-sus):\n");
do
{il=0;
// se cauta prima unitate in ordinea linii/coloane
do
{
il++;
ic=0;
do
ic++;
while (ic<m+1 && F[il][ic]==0);
}while (il<n+1 && F[il][ic]==0);
if (F[il][ic] && ic<m+1 && il<n+1){ // conditie pusa pentru
printf("\n\tObiect %d: (%d,%d)\n", nr_ob+1, il, ic);
obiect(il, ic, F);
// a evita apelul la terminarea
nr_ob++;
// parcurgerii matricei
}
}while (il<n+1 || ic<m+1); // se reia parcurgerea pana matricea devine 0
printf("\n\tNumar obiecte: %i\n", nr_ob);
printf("\nApasati ENTER pentru a termina programul !");
getch();
}

30

nmulirea matricilor
Pentru nmulirea a dou matrici, C = A * B, de n x n elemente, algoritmul clasic provine direct
din regula de nmulire a dou matrici i necesit n3 nmuliri i (n-1) n3 adunri scalare. Timpul necesar
pentru calcularea matricii C este deci n Q(n3). ncercm s gsim un algoritm de nmulire matricial al
carui timp s fie ntr-un ordin mai mic dect n 3. Oricum limita inferioar pentru orice algoritm de
nmulire matricial este de Q(n2), deoarece trebuie s parcurgem cele n2 elemente ale matricii C.
Strategia divide et impera conduce la un alt mod de calcul al matricii C. Vom presupune n
continuare ca n este o putere a lui doi, sau cel puin un numr par. Partiionm pe A i B n cte patru
submatrici de n/2 * n/2 elemente fiecare. Matricea produs C se poate calcula conform formulei pentru
produsul matricilor de 2 * 2 elemente:
A11

A21

A12 B11
*
A22 B 21

B12 C11 C12


B 22 C 21 C 22

unde
C11 = A11 * B11 + A12 * B21
C21 = A21 * B11 + A22 * B21

C12 = A11 * B12 + A12 * B22


C22 = A21 * B12 + A22 * B22

Pentru n = 2, nmulirile i adunrile anterioare sunt scalare; pentru n > 2, aceste operaii sunt
ntre matrici de n/2 * n/2 elemente. Pentru fiecare nmulire matricial, aplicm recursiv aceste
partiionri, pn cnd ajungem la submatrici de 2 * 2 elemente.
n timp ce nmulirea matricilor necesit un timp cubic, adunarea matricilor necesit doar un
timp ptratic. Este, deci, de dorit ca n formulele pentru calcularea submatricilor C s folosim mai
puine inmuliri, chiar dac trebuie s mrim numrul de adunri. n 1969, Strassen a descoperit o
metod de calculare a submatricilor C, care utilizeaz 7 nmuliri i 18 adunri i scderi. Pentru
nceput, se calculeaz apte matrici de n/2 * n/2 elemente:
P = ( A11 + A22 ) * ( B11 + B22 )
Q = ( A21 + A22 ) * B11
R = A11 * ( B12 - B22 )
S = A22 * ( B21 B11 )
T = ( A11 + A12 ) * B22
U = ( A21 A11 ) * ( B11 + B22 )
V = ( A12 A22 ) * ( B21 + B22 )
Este uor de verificat c matricea produs C, se obine astfel:
C11 = P + S T + V
C21 = Q + S

C12 = R + T
C22 = P + R Q + U

Algoritmul lui Strassen este mai eficient dect algoritmul clasic de nmulire matricial. Pe
calculator, s-a constatat c, pentru n >= 40, algoritmul lui Strassen este mai eficient dect metoda
clasic, dar se folosete memorie suplimentar.
nmulirea numerelor ntregi mari
Pentru anumite aplicaii, trebuie s considerm numere ntregi foarte mari. Operaiile aritmetice
cu numere ntregi foarte mari nu mai pot fi efectuate direct prin hardware, deci nu mai necesit un timp
31

constant. Reprezentarea numerelor n virgul mobil ar duce la aproximri nedorite, deci trebuie s
implementam prin software operaiile aritmetice respective.
Vom defini un algoritm de tip divide et impera pentru nmulirea ntregilor foarte mari
(operaiile de tip aditiv se realizeaz relativ uor).
Considerm u si v doi ntregi foarte mari, fiecare avnd n cifre zecimale. Daca s = [n/2], partea
ntreag a lui n/2, reprezentm pe u i v astfel:
u = 10s * w + x;
v = 10s * y + z;
unde w i x, respectiv y i z sunt cele dou jumti ale lui u i respectiv v.
vu

yw

zx

n/2
n/2
ntregii w i y, respectiv x i z au cte [n/2] cifre. Din relaia:
u*v = 102s * w*y + 10s * (w*z+x*y) + x*z
obinem urmtorul algoritm divide et impera pentru nmulirea a dou numere ntregi mari.
produs (u, v)
n = cel mai mic ntreg astfel nct u i v s aib fiecare n cifre
if n este mic then calculeaz in mod normal produsul u*v
return produsul uv astfel calculat
else
s=n/2
w = u / 10s ; x = u % 10s
y = v / 10s ; z = v % 10s
return produs (w, y) * 102s +
( produs (w, z) + produs (x, y)) * 10s + produs (x, z)
Pentru a avea i mai puine nmuliri, observnd produsul:
r = (w+x)(y+z) = wy + (wz+xy) + xz = p + (wz+xy) + q
se poate nlocui termenul cu ponderea 10s cu termenul r p q, n ultima linie a algoritmului:
r = produs (w+x, y+z);
p = produs (w, y);
q = produs t(x, z);
return 102s *p + 10s *(r-p-q) + q

3.4. Algoritmi de programare dinamic


Programarea dinamic, ca i metoda divide et impera, rezolv problemele combinnd soluiile
subproblemelor. Algoritmii divide et impera partiioneaz problemele n subprobleme independente,
rezolv subproblemele n mod recursiv, iar apoi combin soluiile lor pentru a rezolva problema
iniial. Dac subproblemele conin subsubprobleme comune, n locul metodei divide et impera este
mai avantajos de aplicat tehnica programrii dinamice.
32

S analizm pentru nceput ce se ntampl cu un algoritm divide et impera n situaia n care


subproblemele conin subsubprobleme comune, care au fost rezolvate anterior. Descompunerea
recursiv a cazurilor n subcazuri ale aceleiai probleme, care sunt apoi rezolvate n mod independent,
poate duce uneori la calcularea de mai multe ori a aceluiai subcaz, i deci, la o eficien scazut a
algoritmului. Cazul clasic este cel al irului Fibbonacci, n care sunt recalculai termeni anteriori pentru
k

a calcula termenul n. La fel i pentru calculul coeficientului binomial C n :


int C(n, k){
if ( k==0 || n==0 )
return 1;
else
return C(n-1,k-1) + C(n-1, k);
}
Multe din valorile C(i, j) sunt calculate n mod repetat. Dac se memoreaz rezultatele
intermediare ntr-un tablou (triunghiul lui Pascal) obinem un algoritm mult mai eficient. De fapt, este
suficient sa memorm un vector de lungime k, reprezentnd linia curent din triunghiul lui Pascal, pe
care s-l reactualizm de la dreapta la stnga. Noul algoritm necesit un timp n O(nk). Am ajuns astfel
la primul principiu de baz al programrii dinamice: evitarea calculrii de mai multe ori a aceluiai
subcaz, prin memorarea rezultatelor intermediare.
k
Pentru cazul coeficienilor binomiali, C n , innd cont de modul de calcul (C(n,k)=C(n-1,k-1) +
C(n-1, k), C(k,k)=1, C(k,1)=1 i C(k,j)=1, pentru j>k) rezult c putem pstra aceste valori ntr-un
vector de lungime k, ce reprezint linia curent din triunghiul lui Pascal, i n care noile valori se
calculeaz din cele vechi prin nsumarea ultimilor doi termeni de la iteraia precedent, calculul lor
relizndu-se de la dreapta la stnga.
Pentru exemplul calculului coeficienilor binomiali programul este urmtorul:
/* tripasca.c - programul determina tabloul coeficientilor
binomului (a+b)^n, adica combinbari de n luate cate k,
Tabloul coeficientilor este urmatorul:
0 1 2 3 4 5 6 7 8
1 1 1
2 1 2 1
3 1 3 3 1
4 1 4 6 4 1
5 1 5 10 10 5 1
6 1 6 15 20 15 6 1
7 1 7 21 35 35 21 7 1
care utilizeaza relatiile:
C(n,k)=C(n-1,k-1) + C(n-1, k), C(k,k)=1, C(k,1)=1
si C(k,j)=1, pentru j>k
Utilizand "programarea dinamica" vom pastra linia curenta
din tablou intr-un vector si vom calcula linia urmatoare
din tablou in acelasi vector, de la dreapta la stanga,
observand ca un nou termen este suma precedentilor doi,
cu exceptia celor doua valori extreme, care sunt 1.
*/
#include<stdio.h>
#include <conio.h>
#define DIM 80
33

void main(void){
long int c[DIM];
int n=15, i, k, d;
clrscr();
for (k=0; k<=n; k++){
// construire vector v, linia n, din triunghi Pascal
for (i=k; i>=0; i--) // construire linie k din tringhiul Pascal
if (i==k)
c[i]=1;
else if (i==0)
c[i]=1;
else
c[i] += c[i-1];
printf("\n\n%2i", k);
// afisare linie k
d=3; // spatiu initial rezervat pentru afisarea unei valori
for (i=0; i<=k; i++){
printf("%*li", d, c[i]);
if (i < (n+1)/2)// pentru valori mai mari ale lui n
{if(i%2)
// se incrementeaza la cresterea lui
d++; // i spatiul d (din 2 in 2, cu 1)
}
else if((i+1)%2)
// iar pentru partea a doua a
d--; // triunghiului se reduce spatiul
}
// cu acelasi pas; pentru n>20 trebuie
if ((k+1)%16 == 0){ // modificat 'd' cu un pas mai mare
printf("\n\nContinua afisarea dupa apasarea unei taste!\n");
getch();
}
}
printf("\n");
printf("\n\n\tApasati o tasta pentru a termina programul\t!!!\n");
getch();
}
Putem spune c metoda divide et impera opereaz de sus n jos (top-down), descompunnd un
caz n subcazuri din ce n ce mai mici, pe care le rezolv apoi separat. Al doilea principiu fundamental
al programrii dinamice este faptul ca ea opereaz de jos n sus (bottom-up). Se pornete de obicei de la
cele mai mici subcazuri. Combinnd soluiile lor, se obin soluii pentru subcazuri din ce n ce mai
mari, pn se ajunge, n final, la soluia cazului iniial.
Programarea dinamic este folosit de obicei n probleme de optimizare. n acest context,
conform celui de-al treilea principiu fundamental, programarea dinamic este utilizat pentru a
optimiza o problem care satisface principiul optimalitaii: ntr-o secven optim de decizii sau
alegeri, fiecare subsecven trebuie s fie de asemenea optim. Cu toate c pare evident, acest principiu
nu este ntotdeauna valabil i aceasta se ntampl atunci cnd subsecvenele nu sunt independente,
adic atunci cnd optimizarea unei secvene intr n conflict cu optimizarea celorlalte subsecvene.
Pe lng programarea dinamic, o posibil metod de rezolvare a unei probleme care satisface
principiul optimalitaii este i tehnica greedy. Ca i n cazul algoritmilor greedy, soluia optim nu este
n mod necesar unic. Dezvoltarea unui algoritm de programare dinamic poate fi descris de
urmtoarea succesiune de pai:
34

- se caracterizeaz structura unei solutii optime;


- se definete recursiv valoarea unei soluii optime;
- se calculeaz de jos n sus valoarea unei soluii optime.
Dac pe lng valoarea unei soluii optime se dorete i soluia propriu-zis, atunci se mai efectueaz
urmtorul pas:
- din informaiile calculate se construiete de sus n jos o soluie optim.
Acest pas se rezolv n mod natural printr-un algoritm recursiv, care efectueaz o parcurgere n sens
invers a secvenei optime de decizii calculate anterior prin algoritmul de programare dinamic.
n concluzie programarea dinamic poate fi vzut ca o mbuntire a metodei greedy. Spre
deosebire de metoda greedy, care introduce un nou element n soluie pe baza unui criteriu de
accesibilitate, urmnd ca optimul s rezulte durect sau printr-o nlocuire ulterioar, programarea
dinamic amendeaz acest criteriu cernd ca s fie respectat permanent un criteriu de optimalitate.
Acest criteriu poate depinde de elementul ales, motiv pentru care metoda are atributul de dinamic,
adic este posibil o alt alegere, de la caz la caz. Alegerea optim la un pas nu conduce ntotdeauna la
o soluie optim de ansamblu.
S considerm urmtorul exemplu, i anume: fiind dat un ir de numere se cere s se determine
cel mai mare subir cresctor. S considerm urmtorul ir, de 15 valori:
1, 5, 2, 6, 7, 4, 10,
13, 9, 20, 14, 7, 15, 12, 17.
n care avem urmtoarele subiruri cresctoare (maxime pentru fiecare etap):
(1)
17;
(2)
12, 17;
(3)
15, 17;
(4)
7, 15, 17
sau 7, 12, 17;
(5)
14, 15, 17;
(6)
20; deci rmne precedentul subir
(7)
9, 14, 15, 17;
(8)
13, 14, 15, 17;
(9)
10, 13, 14, 15, 17;
(10) 4, 10, 13, 14, 15, 17;
(11) 7, 10, 13, 14, 15, 17, sau precedentul subir;
(12) 6, 7, 10, 13, 14, 15, 17;
(13) 2, 6, 7, 10, 13, 14, 15, 17;
(14) 5, 6, 7, 10, 13, 14, 15, 17;
(15) 1, 5, 6, 7, 10, 13, 14, 15, 17,
sau
1, 2, 6, 7, 10, 13, 14, 15, 17.
Evident avem dou subiruri cresctoare maxime, i ultimele dou, care au cte 9 elemente. Se observ,
ca la un pas oarecare, de exemplu subirul care ncepe cu 13 are lungimea 4, iar urmtorul subir care
ncepe cu 10 are lungimea 5, adic este cu o poziie mai lung dect precedentul. Avem deci un criteriu
de optimalitate, i anume dac s1, s2, s3,. . ., sn este cel mai mare (lung) subir cresctor, atunci s2, s3,. . .,
sn este cel mai lung subir cresctor care ncepe cu s2, este cel mai lung subir care ncepe cu s2, i aa
mai departe. De asemenmea lungimile subirurilor crsctoare pot fi deduse succesiv pe baza observaiei
anterioare. Vom nota n programul urmtor cu L[k] lungimea celui mai mare subir care ncepe cu
elementul de pe poziia k. Avem urmtoarea relaie ntre lungimile asociate elementelor subirurilor:
L[p]=L[q]+1, dac cele dou elemente q i p sunt elemente succesive dintr-un subir crector, n
ordinea p, q.
Dup cum se poate vedea din exemplul anterior, sunt situaii n care la unele subiruri se
renun (subirurile 2, 4, 6, 7 i 10), ntruct nu respect criteriul de optimalitate (care depinde de
elementul ales), deci este o problem de programare dinamic.
Algoritmul va determina lungimile subirurilor la fiecare pas folosind relaia anterioar
(L[p]=L[q]+1), alegnd la fiecare pas dintre toate subirurile ce se pot forma cu o nou valoare p,
35

subirurile de lungime maxim formate anterior, prin parcurgerea, cu un indice q, a spaiului [p+1, n]
i determinarea valorilor maxime L[q]. Lungimea noului sub;ir determinat va fi L[p]=L[q]+1.
Deoarece relaia leag un termen cu ranf mai mic, p, (mai la stnga) de unul cu rang mai mare, q, (mai
la dreapta), rezult c vom parcurge irul de la dreapta la stnga.
Vom parcurge irul de la dreapta la stnga i vom calcula pentru fiecare valoare curent,
lungimea celui mai mare subir cresctor care ncepe de la elementul curent, determinnd pentru
elementele urmtoare, care are asociat cea mai mare lungime, cu condiia ca irul obinut s fie tot
cresctor.
La fiecare pas k (n-1, n-2, . . . , 1) se caut valoarea vi mai mare dect vk i actualizm lungimea
asociat acetsui element L[k]=L[i]+1, iar dac nu exist o astfel de valoare rmne L[k]=1.
Dup ce se construiete vectorul L, valoarea maxim din acest vector reprezint lungimea
subirului de lungime maxim, iar indexul acestei valori reprezint poziia de nceput a subirului.
ncepnd de la acest element se va afia elementul urmtor care este mai mare i a crui lungime
asociat este mai mic cu o unitate (altfel nseamn c nu face parte din acest subir).
/* sirmaxpd.c - programul determina cel mai mare subsir crescator,
dintr-un sir dat, utilizand "programarea dinamica" */
#include<stdio.h>
#include <conio.h>
#define DIM 100
void main(void){
int vmax, v[DIM]={1, 5, 2, 6, 7, 4, 10, 13, 9, 20, 14, 7, 15, 12, 17};
int n=15, i, k, max, imax, L[DIM], Lmax;
clrscr();
/*printf("Numar elemente:");
scanf("%d", &n);
for(i=0; i<n; i++) // citire sir unde caut cel mai mare subsir crescator
{
printf("v(%d)= ", i+1);
scanf("%d", &v[i]);
} */
L[n-1]=1;
/* calculez L[k]=L[i]+1, unde 'i' este indicele unde v[k]<=v[i] */
for (k=n-2; k>=0; k--){ // L[k] contine numarul max. de succesori pentru v[k]
L[k]=1; // determin pentru fiecare numar v[k]
imax=k; // care pot fi succesorii sai
Lmax=1; // selectand dintre acestia pe cei care ofera lungimea
for (i=k+1; i<n; i++) // maxima (L[k] va contine pentru pozitia k
if (v[k] <= v[i] && Lmax <= L[i]){ // numarul total
Lmax=L[i]; // de elemente din subsirul
imax=i;
// crescator, maxim, ce se
L[k]=L[imax]+1;
// poate forma
}
}
printf("Vectorul L este urmatorul:\n");
for (i=0; i<n; i++)
printf("%3i", L[i]);
printf("\n");
Lmax=L[0]; imax=0; // se determina inceputul subsirului crescator maxim
36

for (i=1; i<n; i++){ // acesta incepe de la pozitia corespunzatoare


if (Lmax < L[i]){ // pozitiei 'imax' a valorii maxime din L
Lmax=L[i];
imax=i;
}
}
printf("\nSubsirul cel mai lung este (incepe de la poz %d):\n", imax+1);
printf("%5d", v[imax]);
vmax=v[imax]; // prima valoare din subsirul cautat
Lmax=L[imax]; // dimensiunea subsirului de tiparit
for (i=imax+1; i<n; i++) // urmatoarea valoare din subsir este mai mare
if (vmax <= v[i] && L[i]==Lmax-1){ // decat cea anterioara
vmax=v[i]; // si are lungimea cu o unitate mai mica
printf("%5d ", vmax);
Lmax--; // actualizare dimensiune subsir ramas de tiparit
}
printf("\n\n\tApasati o tasta pentru a termina programul\t!!!\n");
getch();
}

37

S-ar putea să vă placă și