You are on page 1of 8

Metoda Backtracking

I.1. Necesitate
Deseori n practic trebuie s rezolvm probleme care au un numr foarte mare de soluii posibile. De cele mai multe ori ns, nu ne intereseaz toate soluiile, ci numai o parte dintre ele, care ndeplinesc anumite condiii specifice problemei. Pentru astfel de probleme este indicat folosirea metodei backtracking care evit generarea soluilor inutile. Desigur c o persoan cu o gndire simplist ar putea spune : generm toate soluiile, apoi le alegem pe cele care ndeplinesc condiiile cerute. Foarte simplu, dar... oare i eficient ? Ce rost ar avea s se genereze nite soluii care oricum nu convin? Din punctul de vedere al timpului necesar calculatorului pentru a obine toate soluiile, ct de realist este o astfel de abordare?

I.2. Descrierea metodei


Prima idee pe care trebuie s o reinem ar fi: nu se genereaz toate soluiile posibile, ci numai acelea care ndeplinesc anumite condiii specifice problemei, numite condiii de validare (n unele lucrrii de specialitate acestea mai sunt numite i condiii interne). Soluiile problemei vor fi generate succesiv ntr-o stiv implementat sub forma unui vector pe care l vom nota st. REAMINTIM : n cadrul unui program, utilizatorul i poate defini o structur numit stiv. Aceasta funcioneaz dup principiul LIFO ( Last in First Out, n traducere Ultimul intrat, primul ieit ). Cel mai simplu mod de a implementa o stiv este cu ajutorul unui vector st. Pentru a simula caracterul de stiv, privim vectorul st ca i cum elementele sale ar fi aezate pe vertical, unul peste altul. Dac la un moment dat stiva st conine elementele st[1], st[2], ..., st[p], atunci poziia p a elementului cel mai de sus, se numete vrful stivei. n general, poziia unui element n vectorul stiv este numit nivel al stivei. Adugarea i extragerea de elemente n /din stiv, se poate face numai pe la capatul de sus al acesteia

n general , numarul de elemente care intr n componena soluiilor poate s difere de la o soluie la alta. Pentru nceput vom considera cazul n care toate soluiile au acelai numr de elemente, ntruct acesta se ntlnete cel mai frecvent. Vom nota cu n numrul de elemente ale soluiilor reprezentate pe stiva st. Astfel, o configuraie a vectorului-stiv st format din elementele (st[1], st[2], ..., [n]), se va numi soluie final. Tot pe caz general, fiecare component st[i] poate lua valori ntr-o anumit mulime Si , cu i=1,2,...,n. Ansamblul mulimilor Si , adic produsul cartezian S1xS2xSn, se numete spaiul soloiilor posibile. Din nou vom face o particularizare, considernd pentru nceput c toate componentele st[i] ( i=1,2,...,n) iau valori n aceeai mulime S. n continuare trebuie s raspundem la ntrebarea: cum putem s evitm generarea tuturor soluiilor posibile i s le obinem numai pe acelea care ndeplinesc condiiile de validare? Fiecare soluie va fi construit n stiva st pas cu pas, completnd stiva nivel cu nivel. Astfel, pentru a obine o soluie final cu n nivele, de forma st=( st[1], st[2], ..., st[n] ), vom trece prin nite configuraii intermediare ale stivei numite soluii pariale. Cu alte cuvinte,o soluie parial este o configuraie a stivei de forma (st[1], st[2], ..., st[p] ), unde p va lua succesiv toate valorile de la 1 la n. La rndul su, fiecare soluie parial se obine prin completarea cu nc un nivel a soluiei pariale anterioarei.

I.3. Implementarea metodei. Varianta recursiv.


Datorit faptului c fiecare nou configuraie a stivei se obine pornind de la precedenta, algoritmi backtracking se pot implementa foarte elegant ntr-o manier recursiv. Putem scrie i programe nerecursive, dar acestea sunt mai laborioase, de aceea vom ncepe cu varianta recursiv a metodei backtracking. n varianta recursiv, soluiile finale st=(st[1], st[2], ... , st[n] ) sunt generate n cadrul proceduri recursive {bktr(p:integer);}, unde p reprezint nivelul pn la care s-a ajuns pe stiv. Cu alte cuvinte, la fiecare execuie din lanul de auto-apeluri, procedura bktr trateaz nivelul p al stivei. De fapt, aceast procedur implementeaz algoritmul recursiv de backtracking, pe care l descriem n continuare n pseudocod: Pentu pval de la <v1> la <vn> execut nceput st[p] pval dac valid(p) returneaz true atunci dac <soluia este final> atunci apel tipar (p) altfel auto-apel bktr (p+1) sfrit. ntr-un ciclu prin variabila pval vor trece pe rnd toate valorile care ar putea fi ncercate pe nivelul pe al stivei. Plecm de la presupunerea c aceste valori sunt

succesive ntr-un interval delimitat de capetele v1 i vn, caz n care putem folosi un ciclu cu contor. La fiecare pas al ciclului, pentru fiecare dintre valorile variabilei pval: Memorm respectiva valuare pe nivel p, prin atribuirea st [p] pval. Obinem astfel soluia (st[1],st[2], ..., st[p] ), care momentan nu are dect statutul de soluie parial; Verificm dac aceast soluie este valid, testnd valuarea returnat de ctre funcia valid(p). n caz afirmativ (adic dac funcia valid(p) ar returna true), atunci trebuie s verificm dac respectiva soluie este i final (de obicei condiia de soluie final este una singur i poate fi testat ntr-un if, dar putem folosi i o funcie pentru aceasta); n caz afirmativ afim soluia, apelnd procedura tipar(p); n caz contrar, avem de-a face cu o soluie valid (ne aflm pe ramura dac valid(p) ), dar care nu este i final. Fiid vorba de o soluie parial, trecem la nivelul urmtor pentru a o completa. Cum anume ? Trebuie incrementat p-ul, lucru care se realizeaz prin auto-apelul bktr(p+1). Astfel, se va relua de la capt algoritmul, dar cu p+1 n loc de p. Procedura recursiv {bktr (p:integer) ; } implementeaz algoritmul de breacktracking, tratnd la fiecare execuie un nivel p al stivei. Pe fiecare nivel vor fi ncercate succesiv elementele u[1] i u[2] ale vectorului u (n spe literele A i M).Cum anume ? n ciclul for controlul pval va parcurge poziiile elementelor vectorului u, care sunt de la 1 la 2; la fiecare pas, pe nivelul p va fi pus elementul din vectorul u al crui indice este pval (prin atribuire st[p] : = u [pval] ).

Mai departe, algoritmul de bracktracking este cel mai standard, prezentat n artea de teorie: dac valoarea st[p] a generat o soluie (st[1], st[2], ...,st[p] ) valid (dac funcia valid[p] a returnat TRUE ), atunci: dac soluia respectiv e i final (p=n, pentru c se cer secvenele de n litere A i M ) atunci o tiprim (apelnd procedura tipar(p) ); n caz contrar, trecem la nivelul urmtor (pentru a completa soluia cu un nou nivel ), prin auto-apel bktr (p+1).

I.4. Varianta ne-recursiv a metodei backtracking.

Pentru a v putea permite o comparaie cu varianta recursiv (din punctul de vedere al accesibilitii), folosim acelai exemplu: generarea permutrilor de n elemente. La fel ca i n varianta recursiv, soluiile se construiesc intr-un vectorstiv st, i notm cu p nivelul vrfului stivei. Varianta ne-recursiv folosete aceleai subprograme, iniializri, tipar(p) i valid(p) pe care le-am prezentat pe larg n varianta recursiv. Acum vom aminti doar aciunea lor. Procedura { iniializri ; } citete datele de intrare i iniializeaz stiva. Procedura { tipar (p:integer) ; } afieaz o soluie cu p nivele reprezentat de configuraia (st[1], st[2], ... , st[p] ). Funcia {valid (p:integer) : boolean; } va returna true dac soluia (st[1], st[2], ... ,st[p] ) este valid, respectiv false n caz contrar. Mai nti descriem acest algoritm n pseudocod. p1; st[p] 0; ct timp p>0 execut nceput dac <mai exist valori nencercate pe nivelul p> atunci nceput st[p] <o nou valuare din mulimea soluiilor posibile> dac valid (p) returneaz true atunci dac <soluia este final> atunci apel tipar (p) altfel nceput p p+1; st[p] 0; sfrit sfrit altfel p p-1; sfrit Plecm de la primul nivel de pe stiv, iniializnd cu 1 indicele p al vrfului stivei. De asemenea, punem pe acest prim nivel valoarea de plecare 0. Algoritmul evolueaz ntr-un ciclu dirijat de p. La fiacare pas al ciclului se trateaz nivelul al stivei.

Mai nti trebuie s testm dac pe nivelul p mai putem ncerca vreo valuare. Altfel spus, dac mai exist valori din mulimea soluiilor posibile care nu au fost ncercate pe nivel p. n caz afirmativ: soluiilor Atribuim elementului st[p] o nou valuare din mulimea posibile (cu alte cuvinte ncercm o nou valuare pe nivel p);

Dac soluia (st[1], st[2], ... , st[p] ) astfel generat este valid (adic dac funcia valid (p) a returnat true), atunci: - dac soluia n cauz este i final atunci o tiprim, apelnd procedura tipar (p); - n caz contrar, trecem la nivelul urmtor al stivei prin atribuirea p p+1, apoi re-iniializm acest nou nivel p prin atribuirea st[p] 0. n caz contrar, respectiv dac pe nivelul p nu mai putem ncerca nici o valuare, nu ne rmne altceva de fcut dect pasul napoi la nivelul anterior, prin decrementarea lui p ( p p-1). Algoritmul ne-recursiv de backtracking va fi implementat ntr-o procedur back fr parametri. n programul principal avem doar dou apeluri, pentru procedurile iniializri i back.

S se scrie un programcare, pentru un plan de aezare dat, produce harta corespunztoare. Dac exist mai multe hari posibile, se vor determina toate. Pentru planu de aezare dat exist mcar o hart de aezare.

Soluie: Prezentm direct programul cu comentarile necesare nelegeri algoritmului. programul efectul_domino; type ve1=array[1..4] of integer; ve2=array[1..10] of integer; const dx:ve1=(-1,0,1,0); dy:ve1=(0,1,0,-1); var a,b:array[1..7,1..8] of integer; f:text; p,q,r:array[1..28] of integer; i,j,k:byte; n:integer; function intab(x,y:byte):Boolean; begin intab:=(x in [1..7] ) and (y in [1..8] ); end; function piesa( u,v:byte): byte; var i:byte; begin piesa:=0; for i:=1 to 28 do if (p[i], q[i]]=[u,v]) then if (r[i]=0) then begin r[i]:=1; piesa:=i; exit; end else exit; end; function vecin( x,y:byte): Boolean; var i, xv, yv: byte; begin

vecin:=false; for i:=1 to 4 do begin xv:=x+dx[i]; yv:=y+dy[i]; if intab( xv,yv) and (b[xv,yv]<>0) then begin vecin:=true; exit; end; end end; procedure scrie; var i,j: byte; begin wrteln( sol., n); for i:=1 to 7 do begin for j:=1 to 8 do write (b[i,j]: 3); writeln; end; procedure caut ( x,y,nr: byte); var x1, x3, y1, y3, i, t, g: byte; begin for i:=1 to 4 do begin x1:=x+dx[i]; y1:=y+dy[i]; if intab( x1,y1) then if b[x1,y1]=0 then begin t:=piesa (a[x,y], a[x1,y1]); if t <> 0 then begin b[x1,y1]:=t; b[x,y]:=t; if nr=28 then begin n:=n+1; scrie; readln; end else begin g:=0; for x3:=1 to 7 do begin for y3:=1 to 8 do if intab(x3,y3) then if b[x3, y3]=0 then if vecin( x3, y3) then

begin g:=1; caut( x3, y3, nr+1); break; end; if g=1 then break; end; end; b[x,y]:=0; b[x1,y1]:=0; r[t]:=0; end; end; end; end; begin assign( f, f1.txt); reset(f); for i:=1 to 7 do for j:=1 to 8 do begin read(f, a[I,j]); b[i,j]:=0; end; for i:=0 to 6 do for j:=i to 6 do begin k:=k+1; p[k]:=i: q[k]:=j; end; n:=0; caut (1,1,1); close( f ); end.