35
COLEGIUL NAŢIONAL „ŞTEFAN VELOVAN” – CRAIOVA PROFESOR : ELEV:

Backtracking Recursiv

Embed Size (px)

DESCRIPTION

proiect, folositor celor de la M-I.

Citation preview

Page 1: Backtracking Recursiv

COLEGIUL NAŢIONAL

„ŞTEFAN VELOVAN” – CRAIOVA

PROFESOR : ELEV:

PREDOI MARIANA MIŢU ANDREEA OLIVIA

– 2007 –

Page 2: Backtracking Recursiv

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

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

2

Page 3: Backtracking Recursiv

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;

BA

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

A

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

3

Page 4: Backtracking Recursiv

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;

î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.

4

Page 5: Backtracking Recursiv

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 anumitecondiţ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 );

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.

5

Page 6: Backtracking Recursiv

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

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.

6

Page 7: Backtracking Recursiv

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 ( ) ; }

Else K – – ;}

}

7

Page 8: Backtracking Recursiv

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

Page 9: Backtracking Recursiv

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 :

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.

Î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ã ).

9

Page 10: Backtracking Recursiv

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.

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.

10

Page 11: Backtracking Recursiv

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 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.

11

Page 12: Backtracking Recursiv

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 :

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 :

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 :

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>

12

Page 13: Backtracking Recursiv

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();}

3. BACKTRACKING RECURSIV

13

Page 14: 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 :

DD

DD

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

D DD

DD

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

DD

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.

14

Page 15: Backtracking Recursiv

DD

D D

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

DD

DD

Î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.

DD

DD

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

DD

DD

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

15

Page 16: Backtracking Recursiv

DD

DD

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

DD

DD

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

Page 17: Backtracking Recursiv

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

DD

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

DD 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 strategiei Backtracking. Aceasta presupune ca imediat ce am gãsit douã dame care se atacã, sã reluam cãutarea.

17

Page 18: Backtracking Recursiv

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(){

clrscr ();cout<<"Dati n = ";cin>>n;dame(1);

18

Page 19: Backtracking Recursiv

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

Page 20: Backtracking Recursiv

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

Page 21: Backtracking Recursiv

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

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.

21

Page 22: Backtracking Recursiv

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

00

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

10

în acest mod se achitã numai suma 3 ( 0 * 1 + 1 * 3 );

1 la nivelul 2 nu avem succesor, se coboarã la nivelul 1;

11

suma obţinutã este 4, la nivelul 2 nu avem succesor;

2

la nivelul 1 am gãsit succesorul 2;

12

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

22

Page 23: Backtracking Recursiv

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];sol[i]=-1;

}plata(1,0);

23

Page 24: Backtracking Recursiv

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

Page 25: Backtracking Recursiv

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