Documente Academic
Documente Profesional
Documente Cultură
Backtracking
Backtracking
0
Pentru prelucrarea informaţiei omul a inventat calculatorul, dar datoritǎ dezvoltǎrii
vertiginoase a prelucrǎrilor de date cu calculatorul, s-au putut aborda şi rezolva probleme
din ce în ce mai complexe.
Definit în anul 1971 de cǎtre Niklaus Wirth, limbajul Pascal (numit astfel în cinstea
matematicianului francez Blaise Pascal), este un limbaj de interes general, coceput iniţial
în scop didactic, ca instrument de învǎţare a programǎrii în mod sistematic. Datoritǎ
calitǎţilor sale (foloseşte un mod bine structurat de reprezentare a programelor, asigurǎ
claritate, sugestivitate şi simplitate), acest limbaj a fost adoptat rapid ca limbaj de iniţiere
în programare şi a fost apoi axtins cu facilitǎţi care sǎ-i asigure utilizarea performantǎ ca
limbaj înalt ce poate fi folosit în rezolvarea unei mari diversitǎţi de probleme, cu grad
sporit de complexitate.
Limbajul de programare PASCAL este un limbaj agreat de programatori,
permiţându-le acestora sǎ alcǎtuiascǎ cu uşurinţǎ în mod sistematic programe complexe.
Lucrarea urmǎreşte prezentarea şi explicarea detaliatǎ a metodei
BACKTRACKING punând accent pe forma generalizatǎ a metodei. Aceasta cuprinde, pe
lângǎ toate toate aspectele usual abordate în cadrul metodei, şi unele chestiuni de
dificultate sporitǎ, oferind astfel puncte de pornire în aprofundarea metodei.
În capitolele I şi II, pentru fixarea cunoştinţelor de bazǎ şi pentru crearea unor
deprinderi de organizare riguroasǎ în cadrul metodei Backtracking simple şi generalizate.
S-au inclus în capitolele III, IV şi V probleme ce se rezolvǎ cu ajutorul metodei,
toate fiind însoţite de explicarea şi rezolvarea completǎ, dând şi indicaţii despre
particularitǎţi ale metodei. Problemele nu sunt de acelaşi nivel, dificultatea acestora
crescând pe parcursul celor 3 capitole.
Facem referire cǎ problemele rezolvate sub formǎ de program în limbaj de
programare Borland Pascal au fost mai întâi testate şi verificate pentru evitarea
erorilor.
Capitolul VI exemplificǎ utilizarea metodei Backtracking generalizat recursive, dǎ
detalii în legǎturǎ cu modul acesta de rezolvare a problemelor, în capitolul VII fiind reluatǎ
problema din capitolul III dar implementatǎ acum recursiv.
1
De multe ori, în aplicaţii apar probleme în care se cere gǎsirea unor soluţii de forma
x=x1x2... xn unde xi A, i = 1,…,n în care x1…xn trebuie sǎ îndeplineascǎ anumite condiţii.
Am putea sǎ generǎm toate combinaţiile posibile de valori şi apoi sǎ le alegem doar pe cele
convenabile. Considerând mulţimile A = {ai,1,ai,2,…,ai,n(i)}, aceste combinaţii s-ar putea construi
astfel: pentru fiecare valoare posibilǎ fixatǎ pentru componenta xi, vom alege toate valorile
posibile pentru componenta xi+1 şi pentru fiecare astfel de valoare fixatǎ pentru xi+1 vom alege
toate valorile posibile pentru componenta xi+2, etc.
Rezolvând problema în acest mod, deci generînd tote elementele produsului cartezian
A1 A2 ... An şi verificând abia apoi dacǎ fiecare combinaţie este o soluţie, eficientǎ este
scǎzutǎ.
Astfel, dacǎ de exemplu ne propunem sǎ generǎm toate cuvintele formate cu litere a,b,c,
aşa încât fiecare literǎ sǎ aparǎ o singurǎ datǎ, combinaţiile posibile sunt în numǎr de 27, dintre
care convin doar 6.
Tehnica Backtracking propune generarea soluţiei prin completarea vectorului x în ordine
x1x2... xn şi are la bazǎ un principiu “de bun simţ”: dacǎ se constatǎ cǎ având o combinaţie parţialǎ
de formǎ v1v2...v k-1 (unde vi,…,vk-1 sunt valori deja fixate), dacǎ alegem pentru xk o valoare vk şi
combinaţia rezultatǎ nu ne permite sǎ ajungem la o soluţie, se renunţǎ la aceastǎ valoare şi se
încearcǎ o alta (dintre cele netestate în aceastǎ etapǎ). Într-adevǎr, oricum am alega celelalte
valori, dacǎ una nu corespunde nu putem avea o soluţie.
Pentru exemplu ales anterior se observǎ cǎ dacǎ notǎm cuvântul cu x1x2x3, combinaţia aax3
nu ne poate conduce la o soluţie (literele trebuie sǎ fie distincte) şi deci nu are sens sǎ mai
încercǎm sǎ stabilim valori pentru x3.
Pe baza acestor date vom scrie apoi procedurile şi funcţiile pe care le vom apela în
altgoritmul general al metodei, dat mai jos, care se poate aplica tuturor problemelor ce respectǎ
condiţiile menţionate anterior. Aceste proceduri şi funcţii au o semnificaţie comunǎ, prezentând
însǎ particularitǎţi în funcţie de fiecare problemǎ în parte.
Astfel, se va nota cu x vectorul care conţine soluţia; x[k] = v va avea ca semnificaţie
faptul cǎ elementul al-v-lea din mulţimea de valori ppsibile Ak a fost selectat pentru componenta
xk. dacǎ mulţimea Ak are m elemente, a1a2…am,pentru uşurinţǎ ne vom referii la indicii lor 1,2,
…,m. Deci valorile posibile pentru o componentǎ vor fi 1,2,…,m în aceastǎ ordine.
Iniţial, când pentru o componentǎ nu am testat încǎ nimic, aceasta va avea valoarea 0 (un
indice care nu existǎ). Aceastǎ operaţie se va realiza în procedura INIT care va avea ce
parametru poziţia k.
☺Observaţie: De obicei valorile posibile sunt chiar successive şi în acest caz se poate
considera cǎ x[k] = v are semnificaţia cǎ pentru componenta xk s-a ales chiar
valoarea v. În acest caz iniţializarea trebuie fǎcutǎ cu o valoare imediat înaintea
primei valori posibile. De exemplu dacǎ valorile posibile ar fi 5,6,7,… atunci
iniţializarea se va face cu valoarea x[k] = 4
Funcţia EXISTA(k) verificǎ dacǎ ultima valoare aleasǎ pentru componenta xk nu a atins
limita maximǎ admisǎ (indicele de valoare maximǎ). Întrucât elementele sunt testate în ordine,
acest lucru este echivalent cu a verifica dacǎ mai avem valori netestate încǎ pentru aceastǎ
componentǎ.
Funcţia CONT(k) verificǎ dacǎ valoarea aleasǎ pentru x[k] îndeplineşte condiţiile de
continuare, deci dacǎ aceastǎ combinaţie parţialǎ v1v2…vk poate sǎ conducǎ la o soluţie.
☺Observaţie: La implementarea acestei funcţii, de obicei se porneşte de la premisa cǎ
se Funcţia
poate obţine o soluţie şi verificǎ
SOLUTIE(k) se identificǎ
dacǎ acele cazuri
s-a ajuns la oînsoluţie
care acest lucru este posibil.
finalǎ.
Procedura TIPAR(k) tipǎreşte o soluţie.
3
Altgoritmul propus este:
procedure BKT;
var k:integer;
begin
k:=1;
INIT(k);
while k>0 do
if EXISTA(k) then
begin
x[k]:=x[k]+1;
VALPOS(k);
if CONT(k) then
if SOLUTIE(k) then
TIPAR(k)
else
begin
k:=k+1;
INIT(k);
end;
end
else
k:=k-1;
end;
4
Astfel, pentru a genera toate valorile posibile pentru o componentǎ k este suficient sǎ
generǎm toate valorile posibile pentru aceastǎ variabilǎ auxiliarǎ şi pe baza lor sǎ aflǎm valorile
lui x[k].
De exemplu dacǎ x este vectorul soluţie, iar d vectorul auxiliar, putem descrie un tip de
bazǎ pentru o componentǎ astfel:
const nmax=20 ;
type component=record
caract1: tip1;
caract2: tip2;
…………
end;
var x:array[1..nmax] of component;
d:array[1..nmax] of integer;
☺Observaţie: Uneori este posibil ca prima componenetǎ (sau primele) sǎ aibǎ valori
deja fixate şi în acest caz generarea se realizeazǎ pentru componentele 2,3, …
5
Se dǎ un labirint sub formǎ de
matrice
cu m linii si n coloane. Fiecare element al
matricei se codificǎ cu: ‘0’ dacǎ este zid şi
cu ‘1’ dacǎ este culoar.
0 0 1 0 0
0 0 1 0 0
1 1 1 1 1
0 0 0 1 1
0 0 0 0 0
unde l0=3, c0=3,
am putea avea posibilitǎţile:
0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0
0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Pentru a scrie programul stabilim urmǎtoarele:
- vom folosi matricea tab pentru a memora configuraţia labirintului.
- vectorul soluţie are un numǎr variabil de componente şi conţine o succesiune de
elemente ale tabloului care ar reprezenta un drum în labirint. Un element al vectorului,
x[k], conţine coordonatele unui punct în care ne aflǎm la pasul respective: linia si
coloana (l,c).
- pentru a determina mulţimea de valori posibile pentru o componentǎ, ţinem cont de
urmǎtoarele:
Aflându-se într-un punct dat, persoana poate sǎ mearga în 4 direcţii pe care le codificǎm cu
1,2,3,4
Fiecare direcţie de deplasare induce un anumit deplasament liniei, respectiv coloanei.
Astfel:
j - pe direcţia 1: linia scade cu o unitate, deci deplasamentul ei este –1
coloana rǎmâne aceeşi, deci deplasamentul ei este 0
1 - pe direcţia 2: linia rǎmâne aceeaşi, deci deplasamentul ei este 0
6
i 3 * 2 coloana creşte cu o unitate, deci deplasamentul ei este 1
4 - pe direcţia 3: linia creşte cu o unitate, deci deplasamentul ei este 1
coloana rǎmâne aceeşi, deci deplasamentul ei este 0
- pe direcţia 4: linia rǎmâne aceeaşi, deci deplasamentul ei este 0
coloana scade cu o unitate, deci deplasamentul ei este -1
d1d2d3… x1 x2 x3 …
0 (3,3) Stabilim valoarea primei componente, care este corespunzǎtoare
punctului din care se pleacǎ(l0,c0). Direcţia din care s-a ajuns aici
nu intereseazǎ.
01 (3,3)(2,3) Prima direcţie posibilǎ este 1. Din (3,3) pe directia 1 ajungem in
punctul (2,3) -convine este pe culoar. Trecem la urmǎtoarea
componentǎ, k=3.
011 (3,3)(2,3)(1,3) Începem cu prima direcţie posibilǎ pentru acestǎ componentǎ,
d[3]=1; coordonatele punctului corespunzǎtor lui k=3 în care
ajungem din x[2]pe direcţia 1 sunt (1,3) -convine. Se observǎ cǎ
am ajuns la marginea labirintului, deci am obţinut o soluţie ,pe care
o tipǎrim. Rǎmânem la k=3.
012 (3,3)(2,3)(2,4) Urmǎtoarea valoare posibilǎ pentru d[3] este 2; punctul în care
ajungem este (2,4) care nu convine, fiind în zid. Rǎmânem la k=3.
013 (3,3)(2,3)(3,3) Pornind din (2,3) pe direcţia 3 ajungem în (3,3)- nu convine, am
mai trecut pe aici. Deci in continuare k=3.
014 (3,3)(2,3)(2,2) Pentru urmǎtoarea direcţie, 4, punctul în care se ajunge din (2,3)
este (2,2) –nu convine este zid. Am epuizat toate direcţiile posibile
pentru k=3, deci revenim la k=2.
02 (3,3)(3,4) Urmǎtoarea direcţie posibilǎ pentru k=2 este 2; punctul în care se
ajunge este (3,4), care convine. Trecem la k=3;
021 (3,3)(3,4)(2,4) Prima direcţie aleasa este 1; pornind din x[k-1] –punctul (3,4),
7
ajungem în (2,4) – nu convine, fiind zid, rǎmânem la k=3;
022 (3,3)(3,4)(3,5) Pe direcţia d[3]=2, punctul în care se ajunge este (3,5) –este pe
margine, deci am obţinut o nouǎ soluţie. Rǎmânem la k=3, pentru o
nouǎ direcţie.
023 (3,3)(3,4)(4,4) Urmǎtoarea valoare pentru d[3]este 3, iar x[3]-punctul (4,4) –
convine; trecem la k=4.
0231 (3,3)(3,4)(3,4) Prima direcţie posibilǎ este d[4]=1, iar x[4]este punctul (3,4) –
nu convine; rǎmânem la k=4.
… Procedeul continuǎ pânǎ când am epuizat toate valorile posibile
pentru componenta d[2](de la ea începe generarea).
Procedura BKTG este modificatǎ în ceea ce priveste faptul cǎ generarea se face pentru
componentele 2,3,… deoarece prima poziţie este fixatǎ.
Programul corespunzǎtor este:
program labirint1;
uses crt;
type component=record
l,c:integer;
end;
vectsol=array[1..100]of component;
auxiliar=array[1..100]of 0..4;
labirint=array[1..25,1..25]of char;
const depl:array[1..4]of component=((l:-1;c:0),(l:0;c:1), (l:1;c:0),
(l:0;c:-1));
var x:vectsol;
d:auxiliar;
tab:labirint;
n,m:integer;
l0,c0:integer;
procedure citire;
var i,j:integer;
begin
clrscr;
writeln('configuratia labirintului: ');
write('m=');readln(m);
write('n=');readln(n);
writeln('Se codifica astfel: 1-culoar; 0-zid');
for i:=1 to m do
for j:=1 to n do
begin
write('tab[',i,',',j,']=');
readln(tab[i,j]);
end;
writeln('Pozitia initiala: ');
write('l0=');readln(l0);
write('c0=');readln(c0);
x[1].l:=l0;
x[1].c:=c0;
end;
procedure INIT(k:integer);
begin
d[k]:=0;
end;
8
function EXISTA(k:integer):boolean;
begin
EXISTA:=d[k]<4;
end;
procedure VALPOS(k:integer);
begin
x[k].l:=x[k-1].l+depl[d[k]].l;
x[k].c:=x[k-1].c+depl[d[k]].c;
end;
function CONT(k:integer):boolean;
var i:integer;
begin
CONT:=true;
for i:=1 to k-1 do
if(x[i].l=x[k].l)and(x[i].c=x[k].c)then
CONT:=false;
with x[k] do
if tab[l,c]='0'then
CONT:=false;
end;
function SOLUTIE(k:integer):boolean;
begin
with x[k] do
SOLUTIE:= (l=1)or(c=1)or(l=m)or(c=n)
end;
procedure TIPAR(k:integer);
var i,j:integer;
begin
for i:=1 to k do
with x[i] do
write(l,' ',c, ' ');
readln;
end;
procedure BKTG;
var k:integer;
begin
k:=2;
INIT(k);
while k>1 do
if EXISTA(k)then
begin
d[k]:=d[k]+1;
VALPOS(k);
if CONT(k)then
if SOLUTIE(k)then
TIPAR(k)
else
begin
k:=k+1;
INIT(k);
end;
end
else
k:=k-1;
end;
9
begin
CITIRE;
BKTG;
end.
1 20 17 12 3
16 11 2 7 18
21 24 19 4 13
10 15 6 23 8
25 22 9 14 5
d1d2d3… x1 x2 x3 …
0 (1,1) Stabilim valoarea primei componente, care este
corespunzǎtoare punctului de plecare (l0,c0).
Direcţia din care a ajuns aici nu ne intereseazǎ.
01 (1,1)(-1,2) Prima direcţie posibilǎ este 1. din (1,1) pe direcţia 1
ieşim de pe tabla de şah, deci nu convine; rǎmânem
la k=2.
02 (1.1)(0,3) Pe aceastǎ direcţie de asemenea pǎrǎsim tabla, deci
rǎmânem la k=2.
03 (1,1)(2,3) Pentru aceastǎ direcţie ajungem în (2,3)
-convine,deci trecem la k=3.
031 (1,1)(2,3)(0,4) Testǎm prima direcţie posibilǎ: pornind din (2,3) pe
direcţia 1 ajungem în (0,4) – nu convine, am ieşit
de pe tablǎ. Deci în continuare k=3.
032 (1,1)(2,3)(1,5) Pentru urmǎtoarea direcţie, 2, punctual în care se
ajunge din (2,3)este (1,5) –convine, deci trecem la
k=4.
… Procedeul continuǎ pânǎ când am epuizat toate
valorile pentru componenta d[2] (de la ea începe
generarea).
Datele folosite în program sunt:
type component=record
l,c:integer;
end;
vectsol=array[1..100]of component;
auxiliar=array[1..100]of 0..4;
tabla=array[1..25,1..25]of integer;
const depl:array[1..8]of component=((l:-2;c:1),(l:-1;c:2),(l:1;c:2),
(l:2;c:1),(l:2;c:-1),(l:1;c:-2),(l:-1;c:-2),(l:-2;c:-1));
var x:vectsol;
d:auxiliar;
n,m:integer;
l0,c0:integer;
11
write('n=');readln(n);
writeln('Pozitia initiala: ');
write('l0=');readln(l0);
write('c0=');readln(c0);
x[1].l:=l0;
x[1].c:=c0;
end;
function EXISTA(k:integer):boolean;
begin
EXISTA:=d[k]<=8;
end;
function CONT(k:integer):boolean;
var i:integer;
begin
CONT:=true;
with x[k] do
if (l<1)or(c<1)or(l>m)or(c>n)then
CONT:=false;
for i:=1 to k-1 do
if(x[i].l=x[k].l)and(x[i].c=x[k].c)then
CONT:=false;
end;
function SOLUTIE(k:integer):boolean;
begin
SOLUTIE:=(k=m*n);
end;
function EXISTA(k:integer):boolean;
begin
EXISTA:=d[k]<=8;
end;
function CONT(k:integer):boolean;
var i:integer;
begin
with x[k] do CONT:=cote[x[k-1].l,x[k-1].c]>cote[l,c];
end;
13
function SOLUTIE(k:integer):boolean;
begin
with x[k] do SOLUTIE:=(l=1)or(c=1)or(l=m)or(c=n);
end;
Dacǎ am scrie un altgoritm care operaţiile efectuate asupra unei componente (fie ea k) a
vectorului soluţie generat prin metoda Backtracking generalizat, atunci am putea apela acest
altgoritm şi pentru componenta urmatoare, k+1 (pentru cǎ acţiunile realizate asupra acestei
componente sunt similare, însǎ aplicate altor valori). Dar cum trecerea de la componenta k la
urmǎtoarea face parte din acţiunea descrisǎ de acest altgoritm, înseamnǎ cǎ apelul este recursiv.
Vom demonstra cǎ altgoritmul recursiv urmeazǎ corect paşii metodei Backtracking
generalizat.
Presupunem ca altgoritmul descries pentru o componentǎ k se încheie la epuizarea
valorilor posibile pentru aceasta. În aceastǎ situaţie se revenea la componenta anterioarǎ, reluând
testǎrile pentru aceastǎ componentǎ. Într-un apel recursiv, la încheierea execuţiei acestuie se
revine la programul apelant – în cazul nostru am fǎcut apel din altgoritmul corespunzǎtor lui k-1,
deci aceastǎ întoarcere nu trebuie fǎcutǎ explicit.
Altgoritmul general în varianta recursivǎ ar putea avea forma:
procedure BKTGR(k:integer);
begin
INIT(k);
while EXISTA(k) do
begin
d[k]:=d[k]+1;
VALPOS(k,d);
if CONT(k) then
if SOLUTIE(k) then
TIPAR(k)
else
BKTGR(k+1)
end;
end;
Celelalte funcţii care apar au aceeaşi semnificaţie şi implementare ca şi în varianta
nerecursivǎ.
În programul principal procedura se va apela având ca parametru prima componentǎ
pentru cǎ aceasta se va completa prima. Se observǎ cǎ altgoritmul se va încheia atunci când vor fi
testate toate valorile posibile pentru aceastǎ componentǎ şi deci seria de apeluri este încheiatǎ.
14
program labirintul; end;
uses crt; procedure VALPOS(k:integer);
type component=record begin
l,c:integer; x[k].l:=x[k-1].l+depl[d[k]].l;
end; x[k].c:=x[k-1].c+depl[d[k]].c;
vectsol=array[1..100]of component; end;
auxiliar=array[1..100]of 0..4; function CONT(k:integer):boolean;
labirint=array[1..25,1..25]of 0..2; var i:integer;
const depl:array[1..4]of component= begin
((l:-1;c:0),(l:0;c:1), (l:1;c:0), CONT:=true;
(l:0;c:-1)); for i:=1 to k-1 do
var x:vectsol; if(x[i].l=x[k].l)and
d:auxiliar; (x[i].c=x[k].c)then
tab:labirint; CONT:=false;
n,m:integer; with x[k] do
l0,c0:integer; if tab[l,c]=0 then
procedure citire; CONT:=false;
var i,j:integer; end;
begin function SOL(k:integer):boolean;
clrscr; begin
writeln('configuratia labirintul:'); with x[k] do
write('m=');readln(m); SOL:= (l=1)or(c=1)or
write('n=');readln(n); (l=m)or(c=n)
writeln('Se codifica astfel: end;
1-culoar; 0-zid'); procedure TIPAR(k:integer);
for i:=1 to m do var i,j:integer;
for j:=1 to n do begin
begin for i:=1 to k do
write('tab[',i,',',j,']='); with x[i] do
readln(tab[i,j]); writeln(l,' ',c);
end; readln;
writeln('Pozitia initiala: '); end;
write('l0=');readln(l0);
write('c0=');readln(c0); procedure BKTGR(k:integer);
x[1].l:=l0; begin
x[1].c:=c0; INIT(k);
end; while EXISTA(k) do
procedure INIT(k:integer); begin
begin d[k]:=d[k]+1;
d[k]:=0; VALPOS(k);
end; if CONT(k)then
function EXISTA(k:integer):boolean; if SOL(k)then
begin TIPAR(k)
EXISTA:=d[k]<4; else
15
BKTGR(k+1); CITIRE;
end BKTGR(2);
end; end.
begin
BIBLIOGRAFIE:
3. S. Niculescu şi colaboratori
Bacalaureat şi atestat
Editura L&S, 1998
16