Sunteți pe pagina 1din 25

COLEGIUL NAŢIONAL

„ŞTEFAN VELOVAN” –
CRAIOVA

PROFESOR : ELEV:
PREDOI MARIANA MIŢU ANDREEA OLIVIA

– 2007 –
CUPRINS :

1. METODA BACKTRACKING ............................. 3

1. 1. Stiva ....................................................................... 3

1. 2. Prezentarea tehnicii Backtracking ..................... 4

2. INTRODUCERE ÎN RECURSIVITATE ............ 9

2. 1. Prezentare generalã ............................................. 9

2. 2. Aplicaţie recursivitate ........................................ 12

3. BACKTRACKING RECURSIV ........................ 14

3. 1. Problema damelor .............................................. 14

3. 2. Partiţiile unui numãr natural ............................ 19

3. 3. Plata unei sume de bani ..................................... 21

4. CONCLUZII ........................................................ 24

2
5. BIBLIOGRAFIE .................................................. 25

1. METODA BACKTRACKING
1. 1. Stiva
Stiva este acea formã de organizare a datelor ( structurã de date ) cu
proprietatea cã operaţiile de introducere şi extragere a datelor se fac în vârful ei.

Pentru a înţelege modul de lucru cu stiva, sã ne imaginãm un numãr n de


farfurii identice, aşezate una peste alta ( o „stivã” de farfurii ). Adãugarea sau
scoaterea unei farfurii se face, cu uşurinţã, numai în vârful stivei. Dacã toate
cele n farfurii sunt aşezate una peste alta, spunem cã stiva este plinã. Dupã ce
am scos toate farfuriile din stivã, spunem cã aceasta este vidã.

Stivele se pot simula utilizând vectori.

Fie ST(i) un vector. ST(1), ST(2), ....., ST(n) pot reţine numai litere sau
numai cifre. O variabilã k indicã în permanenţã vârful stivei.

În continuare, se prezintã modul de lucru cu stiva :

 în stiva iniţial vidã se introduce litera A, vârful stivei va fi la nivelul


1 ( k = 1 );

A
 introducem în stivã litera B, deci k va lua valoarea 2;

B
A
 scoatem din stivã pe B ( A nu poate fi scos );

3
A
 scoatem din stivã pe A; stiva rãmâne vidã.

A
Observaţii :

 în mod practic, la scoaterea unei variabile din stivã,


scade cu 1 valoarea variabilei ce indicã vârful stivei, iar atunci când scriem ceva
în stivã, creşte cu 1 valoarea variabilei ce indicã vârful stivei;

 stivele pot fi simulate şi altfel decât cu vectori;

 pe un anumit nivel se reţine, de regulã, o singurã informaţie ( literã


sau cifrã ), însã este posibil, sã avem mai multe informaţii, caz în care avem de
a face cu stive duble, triple, etc;

 întreaga teorie a recursivitãţii se bazeazã pe structura de tip stivã.

1. 2. Prezentarea tehnicii Backtracking

Aceastã tehnicã se foloseşte în rezolvarea problemelor care îndeplinesc


simultan urmãtoarele condiţii:

 soluţia lor poate fi pusã sub forma unui vector S = x1 x2 , ... , xn, cu
x1 ε A1, x2 ε A2, ... , xn ε An;

 mulţimile A1, A2, ... , An sunt mulţimi finite, iar elementele


lor se considerã cã se aflã într – o relaţie de ordine bine stabilitã;

 nu se dispune de o altã metodã de rezolvare, mai rapidã.

Observaţii :

 nu pentru toate problemele n este cunoscut de la început;

 x1, x2, ... , xn pot fi la rândul lor vectori;

4
 în multe probleme, mulţimile A1, A2, ... , An coincid.

La întâlnirea unei astfel de probleme, dacã nu cunoaştem


aceastã tehnicã, suntem tentaţi sã generãm toate elementele produsului
cartezian A1 x A2 x ... x An şi fiecare element sã fie testat dacã este soluţie.
Rezolvând problema în acest mod, timpul de execuţie este foarte mare,
algoritmul neavând nici o valoare practicã.
De exemplu, dacã dorim sã generãm toate permutãrile unei mulţimi finite
A, nu are rost sã generãm produsul cartezian A x A x ... x A, pentru ca apoi sã
testãm, pentru fiecare element al acestuia, dacã este sau nu permutare ( nu are
rost sã generãm 1, 1, 1, ... , 1, pentru ca apoi sã constatãm cã nu am obţinut o
permutare ).

Tehnica Backtracking are la bazã un principiu extrem de simplu :

 se construieşte soluţia pas cu pas : x1, x2, ... , xn;

 dacã se constatã cã, pentru o valoare aleasã, nu avem cum sã


ajungem la soluţie, se renunţã la acea valoare şi se reia cãutarea din punctul în
care am rãmas.

Adicã :

 se alege primul element x1, ce aparţine lui A1;

 presupunând generate elementele x1, x2, ... , xk, aparţinând


mulţimilor A1, A2, ... , Ak, se alege ( dacã existã ), xk+1, primul element
disponibil din mulţimea Ak+1, apar douã posibilitãţi:

1) nu s – a gãsit un astfel de element, caz în care caz în care se reia


cãutarea considerând generate elementele x1, x2, ... , xk-1, iar aceasta se reia de la
urmãtorul element al mulţimii Ak, rãmas netestat;

2) a fost gãsit, caz în care se testeazã dacã acesta îndeplineşte anumite


condiţii de continuare, apãrând astfel douã posibilitãţi :

2. 1) le îndeplineşte, caz în care se testeazã dacã s – a ajuns la


soluţie şi în acest caz avem douã posibilitãţi :

2. 1. 1) s – a ajuns la soluţie, se tipãreşte soluţia şi se reia


algoritmul considerând generate elementele x1, x2, ... , xk ( se cautã,
în continuare, un alt element al mulţimii Ak+1 rãmas netestat );

5
2. 1. 2) nu s – a ajuns la soluţie, caz în care se reia
algoritmul considerând generate elementele x1, x2, ... , xk+1, şi se
cautã un prim element xk+2 ε Ak+2.

2. 2) nu le îndeplineşte, caz în care se reia algoritmul considerând


generate elementele x1, x2, ... , xk, iar elementul xk+1 se cautã între
elementele mulţimii Ak+1 rãmase netestate.
Algoritmul se terminã atunci când nu mai existã nici un element x1 ε A1
netestat.

Observaţie :

 tehnica Backtracking are ca rezultat obţinerea tuturor soluţiilor


problemei; în cazul în care se cere o singurã soluţie, se poate forţa oprirea,
atunci când a fost gãsitã.

În continuare este prezentatã o rutinã unicã, aplicabilã oricãrei probleme,


rutinã care utilizeazã noţiunea de stivã. Aceastã rutinã va apela funcţii care au
întotdeauna acelaşi nume şi parametri şi care, din punct de vedere al metodei,
realizeazã acelaşi lucru. Sarcina programatorului este de a scrie explicit, pentru
fiecare problemã în parte, funcţiile apelate de rutina Backtracking.

O astfel de abordare conduce la programe lungi însã, dupã înţelegerea


metodei, putem sã scriem programe scurte, specifice fiecãrei probleme în parte
(de exemplu, scurtãm substanţial textul doar dacã renunţãm la utilizarea
funcţiilor).

Am arãtat cã orice soluţie se genereazã sub formã de vector. Vom


considera cã generarea soluţiilor se face într – o stivã. Astfel, x1 ε A1, se va gãsi
pe primul nivel al stivei, x2 ε A2 se va gãsi pe al doilea nivel al stivei, ... , xk ε Ak
se va gãsi pe nivelul k al stivei. În acest fel, stiva ( notatã ST ) va arãta ca mai
jos :

xk
...
...
x2
x1

6
Nivelul k + 1 al stivei trebuie iniţializat ( pentru a alege, în ordine
elementele mulţimii k + 1 ). Iniţializarea trebuie fãcutã cu o valoare aflatã ( în
relaţia de ordine consideratã pentru mulţimea Ak+1 ) înaintea tuturor valorilor
posibile din mulţime.

De exemplu, pentru generarea permutãrilor mulţimii { 1, 2, ... , n },


orice nivel al stivei va lua valori de la 1 la n. Iniţializarea unui nivel ( oarecare )
se face cu valoarea 0. Funcţia de iniţializare o vom numi Init ( ).
Gãsirea urmãtorului element al mulţimii Ak ( element care a fost netestat
se face cu ajutorul funcţiei int Am_Succesor ( ). Dacã existã succesor, acesta
este pus în stivã şi funcţia returneazã 1, altfel funcţia returneazã 0.

Testul dacã s – a ajuns sau nu la soluţia finalã se face cu ajutorul funcţiei


Soluţie ( ), iar o soluţie se tipãreşte cu ajutorul funcţiei Tipar ( ).

Testarea condiţiilor de continuare ( adicã dacã avem şansã sau nu ca prin


valoarea aflatã pe nivelul k + 1 sã ajungem la soluţie ) se face cu ajutorul
funcţiei int E_Valid ( ).

Prezentãm în continuare rutina Backtracking :

Void Back ( ) {

Int AS;

K = 1; Init ( );

While ( K > 0 ) {

Do {

}While ( (AS = Am_Succesor ( )) && !E_Valid ( ) );

If ( AS )

If ( Soluţie ( ) )

Tipar ( );

Else { K ++; Init ( ) ; }

7
Else K – – ;
}
}

Observaţii :

 problemele rezolvate prin aceastã metodã necesitã un timp


îndelungat, din acest motiv, este bine sã utilizãm metoda numai atunci când nu
avem la dispoziţie un alt algoritm mai eficient.

 variabila AS are rolul de a reţine dacã, atunci când s – a ieşit din


ciclu, a existat succesor, caz în care acesta este valid ( contrar nu s – ar fi ieşit
din ciclu );

 cu toate cã existã probleme pentru care nu se pot elabora alţi


algoritmi mai eficienţi, tehnica Backtracking trebuie aplicatã numai în ultimã
instanţã.

 denumirea Backtracking elementar este folositã pentru a desemna


utilizarea tehnicii Backtracking pentru probleme la care soluţia este sub formã
de vector, altfel spus, probleme pentru care stiva are lãţimea 1.

 existã probleme pentru care se foloseşte stiva dublã, triplã ( numite


probleme de Backtracking în plan ).

 rutina prezentatã corespunde variantei iterative.

8
2. INTRODUCERE ÎN RECURSIVITATE

2. 1. Prezentare generalã

Recursivitatea este una din noţiunile fundamentale ale informaticii.


Utilizarea frecventã a recursivitãţii s – a fãcut dupã anii 80. Multe din limbajele
de programare evoluate şi mult utilizate ( Fortran, Cobol ) nu permiteau
scrierea programelor recursive.

În linii mari, recursivitatea este un mecanism general de elaborare a


programelor.

De multe ori, calculele matematice utilizeazã formule recursive ( în sens


matematic ).

Exemplu :

Fie funcţia de mai jos, definitã pe N X N :

 n + 1; m = 0 
 
a(m, n) =  a(m − 1,1);n = 0 
 a(m − 1, a(m, n − 1));a ltfel
 

Se cere sã se calculeze valoarea acestei funcţii, pentru o pereche de


numere naturale m şi n.

Existã posibilitatea de a scrie programul corespunzãtor şi fãrã a utiliza


direct recursivitatea, dar este mai simplu dacã limbajul permite scrierea directã a
acestei formule.

9
În primul rând, prin analogie cu noţiunea de funcţie matematicã, suntem
tentaţi sã folosim un subprogram de tip funcţie. Din analiza formulei ( cazurile 2
şi 3 ) rezultã cã funcţia are posibilitatea de a se autoapela ( din corp o vom apela
tot pe ea, pânã când o anumitã condiţie va fi îndeplinitã ).

Din cele prezentate rezultã urmãtoarele :

 recursivitatea a apãrut din necesitãţi practice ( transcrierea directã


a formulelor matematice recursive );

 recursivitatea este acel mecanism prin care un subprogram (funcţie,


procedurã ) se autoapeleazã.

Un algoritm recursiv are la bazã un mecanism de gândire diferit de cel


iterativ. Pentru a – l explica, vom folosi câteva exemple intuitive :

 o camerã de luat vederi are în obiectiv un televizor care transmite


imaginile primite de la camerã; evident, în televizor se va vedea un
televizor iar în acesta un televizor, ş. a. m. d. ;

 în anumite restaurante se întâlneşte anunţul „AZI NU SE


FUMEAZÃ !”;

 orice militar din ciclul 2 afirmã cã atunci când era în ciclul 1, cei din
ciclul 2 s – au purtat urât cu el şi de aceea şi el se poartã urât cu cei din ciclul 1;

 într – o ţarã cu nivel de trai scãzut, orice partid care ajunge la putere
afirmã cã de vinã sunt cei de la care au preluat puterea.

Atunci când scriem un algoritm recursiv, este suficient sã gândim ce se


întâmplã la un anumit nivel, pentru cã la orice nivel se întâmplã exact acelaşi
lucru.

Un algoritm recursiv corect trebuie sã se termine, în caz contrar,


programul se va termina cu eroare şi nu vom primi rezultatul aşteptat.
Condiţia de terminare va fi pusã de cãtre programator.

Un rezultat matematic de excepţie afirmã cã pentru orice algoritm iterativ


existã unul recursiv echivalent ( rezolvã aceeaşi problemã ) şi invers, pentru
orice algoritm recursiv existã unul iterativ echivalent.

10
Cu toate acestea, fiind datã o problemã trebuie sã ne gândim bine dacã
pentru ea se va elabora un algoritm recursiv sau unul iterativ. Sunt cazuri când
elaborarea unui algoritm recursiv duce la un timp de calcul foarte mare, iar dacã
elaborãm un algoritm iterativ timpul necesar de calcul este cu mult mai mic. Pe
de altã parte, scriind un algoritm recursiv se obţine un text sursã extrem de scurt
şi de cele mai multe ori clar.

Pentru a putea implementa recursivitatea, se foloseşte structura de date


numitã „stivã” ( structurã care a fost prezentatã ). De data aceasta, stiva nu este
gestionatã de programator, ci chiar de limbajul de programare. În fapt, o parte
din memoria internã rezervatã programului este rezervatã stivei ( corect
segmentului de stivã ).

Un registru al microprocesorului va reţine în permanenţã adresa bazei


stivei, altul adresa vârfului ei. Stiva este folositã chiar şi în cazul programelor
care nu utilizeazã recursivitatea, dar care conţin apeluri de proceduri şi funcţii.
La apelul unei funcţii care are k parametri efectivi, aceştia se reţin în stivã.

Astfel, pentru parametrii transmişi prin valoare se reţine valoarea lor


( aceasta explicã de ce nu putem întoarce valori utilizând variabile transmise
astfel – la revenire nu mai avem acces la stivã ), iar pentru cei transmişi prin
referinţã se reţine adresa lor ( în stivã se rezervã spaţiu pentru adresã, iar
conţinutul va fi obţinut prin utilizarea acesteia ).

Mecanismul prezentat mai sus poate fi generalizat cu uşurinţã pentru


obţinerea recursivitãţii. Atunci când o funcţie se autoapeleazã se depun în stivã :

 valorile parametrilor transmişi prin valoare;

 adresele parametrilor transmişi prin referinţã;

 valorile tuturor variabilelor locale ( declarate la nivelul funcţiei ).

Sã presupunem cã funcţia s – a autoapelat. Instrucţiunile rãmân


nemodificate. Funcţia va lucra asupra datelor transmise pe stivã. În cazul unui
nou autoapel, se creeazã un alt nivel pentru stivã, în care se depun noile valori.
Odatã îndeplinitã condiţia de terminare, urmeazã sã se revinã din autoapelãri.
Funcţia care s – a autoapelat se reia de la instrucţiunea care urmeazã
autoapelului. Ea va lucra cu variabilele ( valorile ) reţinute pe stivã în momentul
autoapelului, la care se adaugã valorile returnate.

O problemã care meritã atenţia este datã de urmãtoarea observaţie : de


vreme ce recursivitatea necesitã o stivã, rezultã cã memoria, necesarã unui astfel

11
de program este mai mare. Sã nu uitãm un fapt important : memoria necesarã
stivei este oricum rezervatã, de compilator. Totuşi, în cazul în care numãrul
autoapelãrilor succesive este foarte mare, se poate ocupa întreg spaţiul de
memorie alocat stivei, caz în care programul se terminã cu eroare. Acelaşi
rezultat se obţine atunci când lipseşte ( sau este pusã greşit ) condiţia de
terminare.

Din punct de vedere al modului în care se realizeazã autoapelul, existã


douã tipuri de recursivitate : directã şi indirectã.

Recursivitatea directã a fost deja prezentatã. Recursivitatea indirectã are


loc atunci când o funcţie apeleazã o altã funcţie, care la rândul ei o apeleazã pe
ea.

Exemplu :

Se considerã douã valori reale, pozitive a0, b0 şi n numãr natural. Definim


şirul :

a n −1 + bn −1
an = ; bn = a n −1 * bn −1
2

Vom folosi douã funcţii a ( n ) şi b ( n ). Fiecare din ele se autoapeleazã


dar o apeleazã şi pe cealaltã.

2. 2. Aplicaţie recursivitate

Se dã funcţia :
 n + 1; m = 0 
 
a(m, n) =  a(m − 1,1);n = 0 
 a(m − 1, a(m, n − 1));a ltfel
 

Funcţia este definitã pe produsul cartezian N X N. Sã se citeascã m şi n şi


sã se calculeze Ack ( m, n ).

Exemplu :

Pentru m = 2 şi n = 1, avem, ack ( 2, 1 ) = 5 :

12
ack ( 2, 1 ) = ack ( 1, ack ( 2, 0 ) ) = ack ( 1, ack ( 1, 1 ) ) = ack (1, ack ( 0, ack
( 1, 0 ) ) ) = ack ( 1, ack ( 0, ack ( 0, 1 ) ) ) = ack ( 1, ack ( 0, 2 ) ) = ack ( 1, 3 ) =
ack ( 0, ack ( 1, 2 ) ) = ack ( 0, ack ( 0, ack ( 1, 1 ) ) ) = ack ( 0, ack ( 0, ack ( 0,
ack ( 1, 0 ) ) ) ) = ack ( 0, ack ( 0, ack ( 0, 1 ) ) ) = ack ( 0, ack ( 0, ack ( 0, 2 ) ) )
= ack ( 0, ack ( 0, 3 ) ) = ack ( 0, 4) = 5
Programul :

#include<iostream.h>
#include<conio.h>

int ack(int m,int n){

int a;

if(m==0)

a=n+1;

else if(n==0)

a=ack(m-1,1);

else a=ack(m-1,ack(m,n-1));

return a;
}

void main(){

int m,n,a;

clrscr();

cout<<"Dati m :";cin>>m;

cout<<"Dati n :";cin>>n;

a=ack(m,n);

cout<<a;

getch();
}

13
3. BACKTRACKING RECURSIV

3. 1. Problema damelor
Enunţ :

Fiind datã o tablã de şah n X n , se cer toate soluţiile de aranjare a n


dame, astfel încât sã nu se afle douã dame pe aceeaşi linie, coloanã sau
diagonalã ( damele sã nu se atace reciproc ).

Exemplu :

Presupunând cã dispunem de o tablã de dimensiune 4 X 4, o soluţie ar fi


urmãtoarea :

D
D
D
D
Observãm cã o damã trebuie sã fie plasatã singurã pe linie. Plasãm prima
damã pe linia 1, coloana 1 :

D D
D
D
D
A douã damã nu poate fi aşezatã decât în linia 2, coloana 3 :

14
D

D D
Observãm cã cea de – a treia damã nu poate fi plasatã în linia 3. Încercãm
atunci plasarea celei de – a doua dame în coloana 4.

D
D

D D
A treia damã nu poate fi plasatã decât în coloana 2.

D
D
D
D
În aceastã situaţie, dama 4 nu mai poate fi aşezatã.

Încercând sã avansãm ca dama a treia, observãm cã nu este posibil sã o


plasãm nici în coloana 3, nici în coloana 4, deci o vom scoate de pe tablã. Dama
a doua nu mai poate avansa, deci şi ea este scoasã de pe tablã.

Avansãm cu prima damã în coloana 2.

D
D
D
D
A doua damã nu poate fi aşezatã decât în coloana 4.

D
D
D

15
D

Dama a treia se aşeazã în prima coloanã.

D
D
D
D
Acum este posibil sã plasãm a patra damã în coloana 3 şi astfel am
obţinut o soluţie a problemei.

D
D
D
D
Algoritmul continuã în acest mod pânã când trebuie scoasã de pe tablã
prima damã.

Pentru reprezentarea unei soluţii putem folosi un vector cu n componente


( având în vedere cã pe fiecare linie se gãseşte o singurã damã ). De exemplu,
pentru soluţia gãsitã avem vectorul ST ce poate fi asimilat unei stive.

Douã dame se gãsesc pe aceeaşi diagonalã dacã şi numai dacã este


îndeplinitã condiţia : | st (i) – st (j) | = | i – j | ( diferenţa, în modul, între linii şi
coloane este aceeaşi ).

În general, ST ( i ) = K, semnificã faptul cã pe linia i, dama ocupã poziţia


K.

3 ST ( 4 )
1 ST ( 3 )
4 ST ( 2 )
2 ST ( 1 )

16
Exemplu : în tabla 4 X 4 avem urmãtoarea situaţie :

D
D

D D
ST ( 1 ) = 1; i = 1

ST ( 3 ) = 3; j = 3

| ST ( i ) – ST ( j ) | = | 1 – 3 | = 2

|i–j|=|1–3|=2

Sau situaţia :

D
D D
ST ( 1 ) = 3; i = 1

ST ( 3 ) = 1; j = 3

| ST ( i ) – ST ( j ) | = | 3 – 1 | = 2

|i–j|=|3–1|=2

Întrucât douã dame nu se pot gãsi în aceeaşi coloanã, rezultã cã o soluţie


este sub formã de permutare. O primã idee ne conduce la generarea tuturor
permutãrilor şi la extragerea soluţiilor pentru problemã ( ca douã dame sã nu fie
plasate pe aceeaşi diagonalã ). A proceda astfel înseamnã cã lucrãm conform

17
strategiei Backtracking. Aceasta presupune ca imediat ce am gãsit douã dame
care se atacã, sã reluam cãutarea.

Programul :

# include<iostream.h>
# include<conio.h>
# include<math.h>

int st[100],n;

void tipar(){

for(int i=1;i<=n;i++ )
cout<<"linia "<<i<<" coloana "<<st[i]<<endl;
cout<<"*********************"<<endl;
}

void dame(int k){

int i,j,corect;

if(k==n+1)
tipar();
else{
for(i=st[k]+1;i<=n;i++){
st[k]=i;
corect=1;
for(j=1;j<=k-1;j++)
if((st[j]==st[k])||(abs(st[k]-st[j])==k-j))
corect=0;
if(corect)
dame(k+1);
}
}
st[k]=0;
}

void main(){

18
clrscr ();
cout<<"Dati n = ";cin>>n;
dame(1);
getch();
}
3. 2. Partiţiile unui numãr natural
Enunţ :

Se citeşte un numãr natural n. Se cere sã se tipãreascã toate modurile de


descompunere a sa ca sumã de numere naturale.

Exemplu :

Pentru n = 4, avem : 4, 31, 22, 211, 13, 121, 112, 1111

Observaţie :

 ordinea numerelor din sumã este importantã; astfel, se tipãreşte 112,


dar şi 211 şi 121.

Funcţia part are doi parametrii : componenta la care s – a ajuns ( k ) şi o


valoare v. Iniţial, aceasta este apelatã pentru nivelul 1 şi valoarea n. Imediat ce
este apelatã, funcţia va apela o alta pentru a tipãri vectorul ( iniţial se va tipãri
valoarea n ).

Din valoarea care se gãseşte pe un nivel ( st [k] ) se scad, pe rând,


valorile 1, 2, ... , st [k – 1], valori cu care se apeleazã funcţia pentru nivelul
urmãtor. La revenire se reface valoarea existentã.

În concluzie, putem afirma :

 nivelul 1 ia valoarea n;

 fiind dat un nivel oarecare se tipãreşte vectorul, apoi se scad, pe


rând, toate valorile posibile ( pânã când acesta rãmâne cu valoarea 1 ), valori cu
care funcţia este reapelatã, având grijã ca la revenire sã refacem valoarea
( pentru a se executa corect scãderea urmãtoare ).

19
Programul :

#include<stdio.h>
#include<conio.h>

int st[20],n;

void tipar(int k){

for(int i=1;i<=k;i++)
cout<<st[i];

cout<<endl;
}

void part(int k,int v){

int i;

st[k]=v;

tipar(k);

for(i=1;i<=st[k]-1;i++){

st[k]=st[k]-i;
part(k+1,i);
st[k]=st[k]+i;
}
}

void main(){

clrscr();
cout<<"Dati n : ";cin>>n;
part(1,n);
getch();
}

20
3. 3. Plata unei sume de bani

Enunţ :

Se dau suma S ce trebuie plãtitã şi n tipuri de monede având valori de a1,


a2, ... , an lei. Se cer toate modalitãţile de platã a sumei S utilizând aceste
monede.

Observaţii :

 valorile celor n monede se reţin în vectorul a;

 se presupune cã dispunem de atâtea monede din fiecare tip, câte sunt


necesare ( acest numãr se obţine în ipoteza cã toatã suma S este plãtitã numai cu
monede de acel tip şi se reţine în vectorul b );

 o soluţie este sub forma unui vector cu n componente ( sol ) în care


componenta i reţine numãrul de monede de tipul i care se folsesc pentru plata
sumei S; acest numãr poate fi şi zero;

 toate componentele vectorului sol sunt iniţializate cu – 1 ( valoare


aflatã înaintea valorilor posibile ); iniţializarea unei componente se face şi
atunci când se revine la componenta de indice imediat inferior;

 funcţia plata ( ) are doi parametrii : indicele componentei din sol


care urmeazã a fi completatã ( k ) şi suma plãtitã pânã în acel moment ( s0 );

 apelul funcţiei plata ( ) ( din cadrul programului principal ) se face


pentru prima componentã şi suma 0;

Fiind dat un nivel oarecare k, se procedeazã astfel :

 atâta timp cât este posibil sã utilizãm în platã încã o monedã din
tipul respectiv ( nu se depãşeşte suma care trebuie plãtitã ) :

 se incrementeazã nivelul ( se foloseşte o nouã monedã de tipul k );

21
 suma plãtitã se majoreazã cu valoarea acelei monede;

 în cazul când a fost obţinutã o soluţie ( s0 = S ), aceasta se tipãreşte,


contrar se apeleazã funcţia pentru nivelul urmãtor, cu noua valoare s0.

Exemplu :

Suma este 5 şi avem douã tipuri de monede ( n = 2 ), valorile acestora


fiind de 1, respectiv 3. Rezultã B ( 1 ) = 5 ( pot folosi maximum 5 monede de
valoarea 1 ) şi B ( 2 ) = 1.

0
 0 este o valoare validã;
0
0
 şi la nivelul 2, 0 este o valoare validã, dar nu avem o soluţie întrucât
suma plãtitã pânã la acest nivel este 0;
1
0
 în acest mod se achitã numai suma 3 ( 0 * 1 + 1 * 3 );

1
 la nivelul 2 nu avem succesor, se coboarã la nivelul 1;
1
1
 suma obţinutã este 4, la nivelul 2 nu avem succesor;

2
 la nivelul 1 am gãsit succesorul 2;
1
2

22
 la nivelul 2 avem succesorul 1 şi obţinem astfel soluţia : douã
monede de valoarea 1 şi o monedã de valoarea 3.

Programul :

# include <iostream.h>
# include <conio.h>

int sol[10],a[10],b[10],n,i,s;

void tipar(int k){


cout <<endl<<" SOLUTIE : "<<endl;
for(i=1;i<=k;i++)
if(sol[i])
cout<<sol[i]<<" bancnote de "<<a[i]<<endl;
}

void plata(int k,int s0){


while((sol[k]<b[k])&&(s0+a[k]<=s)){
sol[k]=sol[k]+1;
if(sol[k]>0)
s0+=a[k];
if(s0==s)
tipar(k);
else if(k<n)plata(k+1,s0);
}
sol[k]=-1;
}

void main(){

clrscr();

cout<<"Cate tipuri de bancnote avem ? ";cin>>n;


cout<<"Suma ce trebuie platita : ";cin>>s;

for(i=1;i<=n;i++){
cout<<"Valoarea monedei de tipul "<<i<<" : ";cin>>a [i];
b[i]=s/a[i];

23
sol[i]=-1;
}
plata(1,0);

getch();
}

4. CONCLUZII

Tehnica Backtracking este folositã pentru a rezolva probleme care pot


avea mai multe soluţii, evitând totodatã generarea soluţiilor inutile.

Tehnica Backtracking foloseşte pentru rezolvare o stivã.

Tehnica Backtracking se preteazã la recursivitate.

Datoritã recursivitãţii, o funcţie se poate autoapela.

Folosind recursivitatea, corpul unei funcţii devine mai scurt şi mai simplu
de înţeles.

Principiul de funcţionare al unei funcţii de Backtracking recursiv,


corespunzãtor unui nivel k, este urmãtorul :

 în situaţia în care avem o soluţie, o tipãrim şi revenim pe nivelul


anterior;

 în caz contrar, se iniţializeazã nivelul şi se cautã un succesor;

 când am gãsit un succesor, verificãm dacã este valid; funcţia se


autoapeleazã pentru k + 1, în caz contrar urmând a se continua cãutarea
succesorului;

 dacã nu avem succesor, se trece pe nivelul inferior ( k – 1 ) prin


ieşirea din funcţia de Backtracking recursiv.

Fiecare programator îşi poate crea propria rutinã de Backtracking


recursiv.

24
5. BIBLIOGRAFIE

1. Petrovici V., Goicea F., Programarea în limbajul C, Ed.


Tehnicã, 1993

2. Odãgescu I., Furtunã F., Metode şi tehnici de programare, Ed.


Computer Libris Agora, Cluj Napoca,

3. Negrescu L., Introducere în limbajul C, Ed.


MicroInformatica, Cluj Napoca, 1993

4. Mocanu M., Ghid de programare în limbajele C / C ++, Ed.


SITECH, Craiova, 2001

5. Schildt H., C ++ Manual Complet, Ed. Teora, Bucureşti,


2000

6. Tudor S., Tehnici de programare, Ed. L & S Infomat,


Bucureşti, 1996

25