Sunteți pe pagina 1din 39

LICEUL COLEGIUL NATIONAL GEORGE COSBUC MOTRU

PROIECT ATESTAT
INFORMATICA

ELEV: DULEA TIBERIU


TEMA: METODA BACKTRACKING
CLASA: A XII-A C
PROFESOR COORDONATOR: CIOCAN NATALIA

TEMA:
Metoda backtracking

CUPRINS
Despre metoda backtracking
. pag.4
Prezentare
generala
pag.5
Descrierea metodei
. pag.7
Caracteristici ale metodei
. pag.11 Variante
ale metodei Backtracking
..pag.30 Probleme si exercitii
..pag 32
Bibliografie
..
pag.37

1.Despre metoda Backtracking


n practic ntlnim foarte multe probleme n care o soluie se reprezint sub forma unui vector x=(
x1, x2, ..., xn) unde fiecare component xi ia valori dintr-o mulime Si cu i=1,2,,n. De regul
exist mai multe astfel de soluii. Teoretic am putea s generm ntr-un mod oarecare toate
elementele produsului cartezian S1xS2x ... xSn i dintre acestea alegem pe cele care satisfac cerinele
(condiiile) enunate n problem. Ne ntrebm ns dac este eficient o astfel de abordare. Un
rspuns la aceast ntrebare avem dac vom considera cea mai simpl situaie n care toate mulimile
n

S1, S2, ..., Sn au fiecare doar cte dou elemente. Atunci exist 2

elemente care se pot genera

pentru produsul cartezian. S analizm ct ar dura s le generm pe toate dac


9
avem la dispoziie un calculator care execut un miliard (adic 10 ) operaii pe secund. Presupunnd c
generarea unei singure posibiliti nseamn o singur operaie, avem:
n
10
20
30
40
50
100

Timpul de execuie
9
2 /10 =2 /10 =0,000001024 secunde
n
9
20
9
2 /10 =2 /10 =0,001 secunde
n
9
30
9
2 /10 =2 /10 =1,074 secunde
n
9
40
9
2 /10 =2 /10 =1100 secunde
n
9
50
9
2 /10 =2 /10 =313 ore
n
9
100
9
2 /10 =2 /10 =40.196.936.841.331 ani
n

10

Ai atepta atta timp ca s generai toate elementele produsului cartezian i apoi ai


mai avea rbdarea necesar s alegei dintre acestea doar pe cele care v intereseaz, adic pe cele
care ndeplinesc condiiile cerute de o anumit problem?
Aceast modalitate de a rezolva o problem de acest tip mai poart numele de
metoda forei brute.
1
Aa cum vom vedea n continuare, metoda Backtracking este cea care ne ajut ca pentru astfel de
probleme s nu generm toate elementele produsului cartezian, construind soluiile problemei pas cu
pas, evitnd generarea a foarte multe elemente ale
produsului cartezian care nu pot fi niciodat soluii ale problemei.

2. Prezentare general
Putem s spunem c metoda Backtracking se aplic la probleme n care soluia se poate reprezenta
sub forma unui vector unde mulimile S1, S2, ..., Sn sunt mulimi finite avnd s1, s2, ..., respectiv sn
1

elemente (unde am notat cu si cardinalul mulimii Si, adic numrul de elemente pe care le
conine mulimea Si).

Pentru fiecare problem concret sunt date anumite relaii ntre componentele vectorului x, numite
condiii interne.
Mulimea finit S1xS2x ... xSn (n matematic fiind numit produs cartezian, pe care s-l notm
cu S) se numete spaiul soluiilor posibile.
Soluiile posibile care satisfac condiiile interne se numesc soluii rezultat.
Ceea ce ne propunem este de a determina toate soluiile rezultat (cu scopul de a le lista sau de a alege
dintre ele pe cele care maximizeaz sau minimizeaz o eventual funcie obiectiv dat, adic soluiile
optime). Sunt i probleme n care se cere o singur soluie, iar n acest caz ne vom opri imediat ce am
determinat una dintre ele.
Aa cum am vzut, o metod simpl de determinare a soluiilor rezultat const n a genera ntr-un
mod oarecare toate soluiile posibile i, dintre acestea, de a verifica i a le alege doar pe cele satisfac
condiiile interne.
S lum dou exemple pentru a nelege mai bine la ce tipuri de probleme se aplic metoda
Backtracking, dar vom ncerca, mai nti, s le rezolvm prin metoda forei brute.
Problema 9.1. (Mulimi de litere) Fie dou mulimi de litere S1={A,B,C} i
S2={M,N}. Se cere s se determine acele perechi (x1,x2) cu
proprietatea c dac x1 este A sau B, atunci x2 nu poate fi N.

x 1 S 1 i x 2 S 2
cu

Soluie. Pentru aceast problem sunt posibile urmtoarele ase soluii:


(A,M), (A,N), (B,M), (B,N), (C,M), (C,N).
Acestea fiind toate elementele produsului cartezian S1xS2 (care am vzut c se numete spaiul
soluiilor posibile).
Dintre acestea numai patru ndeplinesc condiiile din enunul problemei:
(A,M), (B,M), (C,M), (C,N)
acestea fiind soluii rezultat.
Problema 9.2. (Pronosport). Fie un joc de tip pronosport la care, pentru simplitate, vom considera c
sunt n discuie doar 4 meciuri. S presupunem c o persoan dorete s participe la acest joc, plecnd cu
convingerea c n variantele ctigtoare nu pot exista mai mult de dou meciuri nule (cu pronostic X), iar
n ultimul meci gazdele nu ctig (pronosticul nu poate fi 1) i vrea s gseasc toate variantele care
ndeplinesc aceste condiii.
Soluie. Prin urmare, problema cere determinarea unor vectori n care valorile componentelor sunt
pronosticuri (X, 1 sau 2) pentru cele 4 meciuri. Rezult c pentru
aceast

problem

spaiul

soluiilor

posibile

este

produsul

cartezian

S1 x

S2xS3xS4,

unde

S1=S2=S3=S4={1,X,2}. Pentru a fi soluie rezultat, un asemenea vector trebuie s ndeplineasc


urmtoarele dou condiii:
- n vector nu pot s apar mai mult de dou pronosticuri X;
- ultima component poate fi doar X sau 2.
Desigur, exist mai muli vectori care ndeplinesc aceste condiii, ca de exemplu:
(X,1,2,X), (1,2,1,2) etc.
4
Utiliznd metoda forei brute, se genereaz pe rnd toate cele 3 =81 soluii posibile i dintre ele se
aleg cele care satisfac condiiile din enun. S observm ns c dac n loc de 4 meciuri vom considera
13 meciuri, aa cum se ntmpl n realitate, vor exista
13

=1.594.323 variante, adic un numr foarte mare de soluii posibile. Mai precis,

numrul soluiilor posibile, deci i timpul de lucru, depind exponenial de lungimea n a vectorului x.
Metoda Backtracking ncearc s evite generarea tuturor soluiilor posibile.

3.Descrierea metodei
Pentru a evita generarea tuturor soluiilor posibile, metoda Backtracking atribuie pe
rnd valori elementelor vectorului x=(x1, x2, ..., xn) n ordinea cresctoare a indicilor, ncepnd cu x1.
Astfel, lui xk i se atribuie o valoare numai dac au fost atribuite deja valori lui x1, x2, ..., xk-1. Mai mult,
odat stabilit o valoare pentru xk, nu se trece direct la atribuirea de valori lui xk+1, ci se verific nite
condiii de continuare referitoare la x1, x2, ..., xk. Aceste condiii stabilesc situaiile n care are sens s

trecem la

calculul

lui

xk+1,

nendeplinirea

lor

exprimnd

faptul

oricum

am

alege

xk+1,...,xn nu vom putea ajunge la o soluie rezultat, adic o soluie pentru care condiiile interne s fie
satisfcute. Deci, verificarea condiiilor de continuare este strict necesar pentru obinerea unei soluii.
De exemplu, dac n problema 9.2 componentele x1 i x2 au primit amndou valoarea X, atunci lui
x3 nu i se mai poate atribui valoarea X (pentru a nu nclca restricia ca numrul maxim de pronosticuri X
s fie cel mult 2, aceasta nsemnnd c nu sunt ndeplinite condiiile de continuare).
Evident c n cazul nendeplinirii condiiilor de continuare va trebui s facem o alt alegere pentru xk
sau, dac Sk a fost epuizat, se merge napoi la mulimea Sk-1 i se ncearc s se fac o nou alegere
pentru componenta precedent xk-1 a vectorului (ceea ce nseamn s micorm pe k cu o unitate) dup
care nainm la Sk i facem o nou alegere pentru xk (reconsidernd toate valorile din Sk). Aceast
revenire (prin micorarea lui k) d numele metodei, ilustrnd faptul c atunci cnd nu putem avansa,
revenim la componenta anterioar din soluie.
Este evident c ntre condiiile de continuare i condiiile interne exist o strns legtur.
O bun alegere pentru condiiile de continuare are ca efect o reducere a numrului
de calcule. Faptul c valorile curente v1, v2, ..., vk-1 ale lui x1, x2, ..., respectiv xk-1 satisfac condiiile
de continuare nu este suficient pentru a garanta c se va obine o soluie ale crei prime k-1 componente
coincid cu valorile v1, v2, ..., vk-1.
De exemplu, n cazul problemei 9.2, chiar dac valorile curente pentru x1 i x2 sunt
X, iar pentru x3 este 1 (deci aceste valori ndeplinesc condiiile de continuare), totui
(X, X, 1, 1) nu este soluie.
De obicei condiiile de continuare reprezint restricia condiiilor interne pentru primele k componente
ale vectorului. Este evident c n cazul k=n condiiile de continuare sunt chiar condiiile interne.
Prin metoda Backtracking, orice vector soluie este construit progresiv, ncepnd cu prima component
i mergnd ctre ultima, cu eventuale reveniri asupra valorilor atribuite anterior (cu unul sau mai muli
pai napoi, atta timp ct e posibil revenirea). Prin atribuiri sau ncercri de atribuire euate din cauza
nerespectrii condiiilor de continuare, anumite valori sunt "consumate" (reamintim c x1, x2, ..., xn
primesc valori

n mulimile finite S1, S2, ..., Sn). Vom nota cu Ck mulimea valorilor consumate deja
la momentul curent pentru componenta xk evident Ck S k .
Dac ne imaginm c vectorul x mai are o component suplimentar xn+1 (care nu
poate lua nici o valoare, adic Sn+1=) i ncercm s dm valori componentei k=n+1, atunci
putem spune c vectorul v=(v1, v2, ..., vn) a ndeplinit condiiile de continuare (ce coincid cu
condiiile interne) i prin urmare v este o soluie rezulat. n practic, aceast condiie este cea utilizat de
obicei pentru a sesiza obinerea unei soluii.
O problem important este aceea a determinrii momentului n care se ncheie procesul de gsire a
tuturor soluiilor. Avnd n vedere c mulimile S1, S2, ..., Sn sunt mulimi finite, iar prin reveniri
succesive nu se ajunge de dou ori la aceeai soluie parial, rezult c, la un moment dat, tot revenind, se
va ajunge la momentul n care i pentru x1 se epuizeaz toate valorile din S1, n aceast situaie putnd
s ne imaginm c vectorul x mai are o component suplimentar x0 (care nu poate lua nici o valoare,
adic S0=) i n acest caz nseamn c-am epuizat toate situaiile de a obine o soluie rezultat. n
practic, aceast condiie (k=0) este cea utilizat de obicei pentru a sesiza obinerea tuturor soluiilor
rezultat.
Prin urmare, algoritmul general pentru metoda Backtracking poate fi sintetizat astfel:
Iniializeaz mulimile de valori S1, S2, ..., Sn
C1, C2, ..., Cn {Iniial vectorul soluie nu are valori}
k1; {Se pornete de la prima component}
ct timp k>0 execut {Nu s-au construit toate soluiile}
dac k=n+1 atunci {S-a construit o soluie}
Reine/afieaz soluia v=(v1, v2, ..., vn)
kk-1 {Revine dup construirea unei soluii}
altfel
dac Ck<>Sk atunci {Mai exist valori neconsumate}
Alege o valoare vk din Sk\Ck; CkCk U {vk };
dac v=(v1,v2,...,vk) satisfac condiiile de continuare atunci {Atribuie i avanseaz}
xkvk; kk+1
altfel {ncercare euat}

altfel {Revenire}
Ck; kk-1

Programarea algoritmului de mai sus se simplific mult, fr a restrnge


generalitatea sa, dac se consider c mulimile de valori S1, S2, ..., Sn sunt de forma:
S1={1,2,...,s1}, S2={1,2,...,s2}, ..., Sn={1,2,...,sn}

deci fiecare mulime Sk este format din primele sk numere naturale, ncepnd cu 1. Prin urmare,
mulimea Sk poate fi reprezentat simplu prin numrul su de elemente sk. Se presupune de asemenea
c valorile pentru fiecare component xk vor fi alese n ordine cresctoare.

n aceast situaie, fiecare mulime de valori consumate Ck este de forma


{1,2,...,vk} i drept consecin poate fi reprezentat doar prin valoarea vk. Cum vk este numrul
elementelor mulimii Ck, aceast mulime este vid dac i numai dac vk=0. De exemplu, n problema
9.1, vom conveni s reprezentm pe A, B, C respectiv prin
1, 2, 3 iar pe M i N prin 1 i 2. Prin urmare, mulimea S1 va fi reprezentat prin numrul 3, iar S2 prin
numrul 2. Se observ c dei A i M sunt codificate prin acelai numr, ele se disting prin poziia pe care
o ocup n vectorul soluie x=(x1,x2). Astfel, x=(1,1) reprezint soluia x=(A,M).
n foarte multe probleme, mulimile n care pot lua valori componentele vectorului
sunt toate identice cu o mulime finit oarecare S cu s elemente, codificat astfel:
S={1,2,...,s}.
n acest caz, algoritmul corespunztor este:
ntregi k,i;
pentru i=1,n execut x[i]0; {Soluia iniial}

k1;
ct timp k> 0 execut
dac k=n+1 atunci { S-a construit o soluie}
retsol; {Rene/afieaz soluia}
kk-1; {Revenire dup construirea unei soluii}
altfel
dac x[k]<s atunci {Mai exist valori neconsumate pentru x[k]}
x[k]x[k]+1; {Se alege valoarea urmtoare}
dac continuare(k) atunci kk+1; {Avanseaz}

altfel {Revenire}
x[k]0; kk-1

Subprogramul Backtracking apeleaz:


- subrutina retsol care reine soluia, adic valorile vectorului x (n mod concret,
aceasta poate nsemna listarea soluiei, compararea ei cu soluiile precedente etc.);
- funcia continuare(k) verific dac valorile primelor k componente ale vectorului x satisfac
condiiile de continuare; n caz afirmativ este ntoars valoarea adevrat, iar n caz contrar valoarea fals.

Un exemplu de aplicare a metodei


De exemplu, programul de mai jos genereaz soluiile jocului de pronosport
prezentat n problema 9.2. Pentru a folosi doar valori numerice elementele mulimii S
au fost notate cu 1,2, i 3 n loc de X, 1, respectiv 2. Programul este urmtorul:
#include <iostream>
using namespace std;
const int N=4; // Numarul de meciuri
const int S=3; // Nr. de valori {1,x,2}
int x[N+1], nrv=0; // x=vectorul solutie si nrv=nr. variantei
void init(int k)
{
x[k]=0;
}
void retsol()
{
int i;
nrv++; cout<<"Varianta "<<nrv<<" : ";
for (i=1;i<=N;i++)
switch (x[i])
{
case 1 : cout<<"X "; break; case 2
:

cout<<"1

";

break;

case

cout<<"2 "; break;


}
cout<<"\n";
}
int continuare(int k)
{
int nx=0,i; // nx este nr. pronosticuri X
for (i=1;i<=k;i++)
if (x[i]==1) nx++;
if ((nx<=2)&&(x[N]!=2))
return 1;
else return 0;
}

void backtracking()
{
int k=1;init(1);
while (k>0)
if (k==N+1)
{retsol(); k--;}
else
if (x[k]<S)
{
x[k]++;
if (continuare(k)) k++;
}
else {init(k); k--;};
}
int main()
{
backtracking();
return 0;
}

4. Caracteristici ale metodei


Metoda Backtracking

este o metod constructiv (n sensul c soluia se construiete

ncepnd cu primul element al vectorului x la care adugm pas cu pas urmtoarele elemente, numai c,
dac nu se mai poate nainta, se revine la pasul anterior, reconsiderndu-se toate elementele). n acest
fel pot fi determinate toate soluiile rezultat. n funcie de cerinele problemei, ne putem mulumi cu o
singur soluie sau le determinm pe toate. Sunt probleme (de optimizare) n care ni se cere ca dintre toate
soluiile rezultat s alegem, pe baza unor anumite criterii, doar pe cele care le satisfac (acele soluii care
maximizeaz sau minimizeaz o anumit funcie, numit funcie de optim).

Oportunitatea utilizrii metodei


Metoda Backtracking este una destul de costisitoare, n sensul c ea consum mult timp i mult
memorie, dar sunt probleme practice pentru care nu avem alte metode mai bune de rezolvare. Unele
dintre ele vor fi rezolvate n acest capitol. Chiar dac e o metod costisitoare, pentru anumite probleme ea
nu poate fi evitat, neexistnd alte metode cu o eficien mai bun. Ea se dovedete util n probleme i
jocuri de logic (cum ar fi jocul de ah, jocul sudoku, foarte multe jocuri de tip puzzle, rebus i altele), n
unele aplicaii din domeniul matematicii cum ar fi: generarea permutrilor, a aranjamentelor, combinri,
partiii ale unei mulimi, etc. De asemenea, ea st la baza unei serii de limbaje de programare logica, cum
ar fi Icon, Planner i Prolog.

Problema celor n dame.


Fiind dat o tabl de ah, de dimensiune nxn, se cer toate soluiile de aranjare a n dame, astfel nct
dou dame oarecare s nu se afle pe aceeai linie, coloan sau diagonal (adic damele s nu se atace
reciproc).
Soluie. Evident c pe fiecare linie va fi plasat o singur dam (dac ar fi plasate dou dame pe
aceeai linie atunci ele s-ar ataca). Prin urmare, o soluie posibil se poate reprezenta sub forma unui
vector x cu n componente, unde pentru fiecare k de la
1 la n, xk va preciza care este coloana pe care este plasat dama de pe linia k.
Figura urmtoare prezint notaiile pentru tabla de ah obinuit (cazul n=8).
x1 x2 x3 x4 x5 x6 x7 x8
1
2
3
4
5
6
7
8
Notaiile pentru tabla de ah
n cazul acestei probleme avem si=n, numrul soluiilor posibile fiind nn, ceea ce face ca timpul
determinrii tuturor soluiilor posibile s fie foarte mare. Pentru a pune n

eviden

condiiile

de

continuare prin aplicarea metodei Backtracking vom presupune c pe tabla de ah sunt plasate corect
damele de pe primele k-1 linii. Atunci dama de pe linia k poate fi plasat pe linia k dac sunt ndeplinite
urmtoarele condiii:
1) pe coloana xk s nu mai fi fost plasat nici o dam adic x k

x i i k .

2) pe diagonala ce trece prin csua (k,xk) s nu mai fi fost plasat nici o dam

Programul este urmtorul:

#include <iostream>
#include <algorithm>
using namespace std;
#define NR 20
int n, x[NR];
int continuare(int k)
{
int i,b;
b=1; i=1;
while (b && (i<k))
if ((x[k]==x[i]) || (abs(x[k]-x[i])==k-i)) b=0;
else i++;
return b;
}
void retsol()
{
int i;
for (i=1;i<=n;i++) cout<<x[i]<<" ";
cout<<"\n";
}
void backtracking()
{
int k=1,i;
for (i=1;i<=n;i++) x[i]=0;
while (k!=0)
if (k==n+1)
{
retsol(); k--;
}
else
if (x[k]<n)
{
x[k]++;
if (continuare(k))

k++;
}
else
{
x[k]=0;
k--;
}
}
int main()
{
cout<<"Dimensiunea tablei de sah n= ";
cin>>n;
backtracking();
}

Generarea partiiilor unui numr natural


Se citete un numr natural n. Se cere s se tipreasc toate modurile de descompunere a sa ca sum
de
numere naturale.
Exemplu. Pentru n=4 avem: 1111, 112, 121, 13, 211, 22, 31, 4.
Observaie. Ordinea numerelor din sum este important. Astfel, se tiprete 112
dar i 211 precum i 121.

Soluie. Vecorul x conine numerele naturale n care poate fi descompus n. Observm c el are
lungimi diferite, n funcie de soluia determinat de algoritm. Mai observm c lungimea maxim a unei
soluii este chiar n, deoarece acesta poate fi descompus astfel: 1+1++1 (n valori, toate egale cu 1),
aceasta fiind prima soluie.
Mulimile S1, S2 , , Sn sunt toate egale cu muimea {1,2, ,n}, prin urmare, pentru ca xk s
aib successor, e suficient ca xk<n.
Condiia de continuare este ca suma s=x1+x2++xk<n. Se ajunge la o soluie
atunci cnd s=n. Programul e urmtorul:
#include <iostream> using
namespace std; const int
NR=30;
int x[NR],k,n,i;
void init(int k)
{
x[k]=0;
}
int succesor(int k)
{
if ( x[k]<n ) return 1;
else return 0;
}
int continuare(int k)
{
int s=0;
for(i=1;i<k;i++) s+=x[i];
if(s<n) return 1;
else return 0;
}
int solutie(int k)
{
int s=0;
for(i=1;i<k;i++) s+=x[i];
if(s==n) return 1;
else return 0;
}
void afisare()

{
int i;
cout<<x[1];
for(i=2;i<k;i++) cout<<'+'<<x[i];
cout<<'\n';
}
void back()
{
k=1; init(1);
while (k!=0)
{
if (solutie(k)) {afisare();k--;}
else
if (succesor(k))
{
x[k]++;
if (continuare(k)) k++;
}
else {init(k); k--;}
}
}
int main(void)
{
cout<<"Dati n = "; cin>>n;
back();
return 0;
}
Metoda backtracking poate fi reprezentat uor, pe un arbore construit astfel:
- nivelul 1 conine rdcina;
- din orice vrf de pe nivelul k pleac sk muchii spre nivelul k+1 etichetai cu cele
sk muchii ale lui Sk.
Nivelul n+1 va conine s1 s2 ... sn vrfuri. Pentru fiecare vrf de pe nivelul
n+1, etichetele muchiilor coninute pe drumul ce leag rdcina de acest vrf reprezint
o soluie posibil. S lum i un exemplu.

Submulimi de sum dat


Fie A=(a1, a2, ..., an) cu ai>0, i{1,2,,n}. Fie MR+. Se caut toate submulimile B ale lui A
pentru care suma elementelor este M. (Bacalaureat, sesiunea iunie-iulie 2001, variant 5, subiectul IV)
Pentru a putea rezolva problema prin metoda backtracking vom reprezenta soluia sub forma x =(x1,
x2, ..., xn) unde xi=1 dac aiB i xi=0 n caz contrar. Pentru n=4 arborele ataat metodei backtracking
este urmtorul:

Dac ntr-un vrf condiiile de continuare nu mai sunt verificate, se va renuna la parcurgerea
subarborelui care are ca rdcin acel vrf, n acest fel eliminnd foarte multe dintre soluiile posibile ce
nu pot fi soluii rezultat.
Diferitele variante ale metodei backtracking nu schimb esena ei care const n faptul c poate fi
reprezentat pe un arbore care este parcurs "cobornd" numai dac exist anse de a ajunge la o soluie
rezultat (aceast metod de parcurgere a arborilor fiind cunoscut sub numele DF (Depth First n
adncime) i a fost propus de Trmaux n secolul al XIX-lea ca tehnic de rezolvare a problemelor
legate de parcurgerea labirintelor).
Programul e urmtorul:
#include <iostream>
using namespace std;
int x[20],a[20],k,m,i,n,nrv=0;
void sortare()
{
int schimb=1,t;
while(schimb)
{
schimb=0;
for(i=1;i<n;i++)
if(a[i]>a[i+1])
{t=a[i];a[i]=a[i+1];a[i+1]=t;schimb=1;}

}
}
void init()
{
x[k]=0;
}
int succesor()
{
int s=0; for(i=1;i<=k;i++)s+=a[x[i]]; if(s<m)
{x[k]++;return 1;} else return 0;
}
int continuare()
{
for(i=1;i<k;i++)
if(x[i]>=x[i+1])return 0;
return 1;
}
int solutie()
{
int s=0; for(i=1;i<=k;i++)s+=a[x[i]]; return
(s==m);
}
void afiseaza()
{
nrv++;cout<<'B'<<nrv<<"={";
for(i=1;i<=k;i++)
cout<<a[x[i]]<<' ';
cout<<"}\n";
}
void backtracking()
{
int as; k=1;init(); while(k>0)
{
do {} while ((as=succesor())&&(!continuare()));
if(as)
if(solutie()) afiseaza();
else {k++;init();}
else k--;
}
}
int main()
{
cout << "suma= ";cin>>m;
cout<<"n= ";cin>>n;

for(i=1;i<=n;i++)
{
cout<<"a["<<i<<"]= ";cin>>a[i];
}
sortare();
backtracking();
if(!nrv)cout<<"Nu exista solutie";
return 0;
}

Pentru a spori eficiena algoritmului mai nti am sortat elementele mulimii i am


actualizat pe parcurs suma.
Complexitatea algoritmilor Backtracking
Dac mulimile S1, S2, ..., Sn au acelai numr s de elemente, timpul necesar este O(sn), deci
exponenial. Dac mulimile S1, S2 , ..., Sn nu au acelai numr de elemente, atunci putem s lum
min{s1, s2, ..., sn} i s notm aceast valoare cu m iar max{s1, s2, ..., sn} s o notm cu M. Atunci,
timpul necesar este cuprins ntre O(mn) i O(Mn), prin urmare tot exponenial.
n general, metoda Backtracking este ineficient avnd complexitate exponenial. Ea se utilizeaz
totui n situaiile n care se pot face optimizri n procesul de cutare, evitnd, ntr-o etap ct mai
timpurie a analizei, cile care nu duc la

soluie. Exist i situaii cnd rezolvarea prin metoda

Backtracking nu poate fi nlocuit prin alte metode mai eficiente cum ar fi, de exemplu, problemele
n care se cer toate soluiile acceptabile.
Deci, avnd un timp de execuie exponenial, utilizarea metodei Backtracking se
justific numai atunci cnd nu cunoatem o alt metod cu eficien mai mare.
Backtracking recursiv
Pn aici am studiat numai varianta iterativ pentru metoda Backtracking. n
continuare vom aborda modalitatea de implementare recursiv a metodei Backtracking.
Presupunem c toate cele n mulimi n care pot lua valori componentele vectorului sunt identice cu
mulimea finit S={1,2,...,s}. n aceast situaie, varianta recursiv a metodei Backtracking este
urmtoarea:

backtracking(ntreg k);
ntreg i ;
dac k = n+1 atunci retsol {Reine soluia}
altfel

pentru i = 1, s execut

x[k] := i;

dac continuare(k)


atunci Backtracking(k+1)

Parametrul k din aceast rutin reprezint numrul de ordine al componentei lui x ce urmeaz s
primeasc o valoare din S. Pentru iniierea procesului de generare a soluiilor, n unitatea de program
care iniiaz procesul backtracking se va utiliza apelul:
backtracking(1)
Colorarea hrilor. Fie dat o hart ca cea din figura de mai jos, n care sunt prezentate schematic 6
ri T1, T2, T3, T4, T5 i T6, dintre care unele au granie comune
T2
T4 T5

T1

T3

T6
O hart cu 6 ri
Presupunnd c dispunem doar de trei culori (galben, albastru i rou), se cere s se determine toate
variantele de colorare a hrii, care s respecte condiia ca orice dou ri vecine (care au frontier
comun) s fie colorate diferit.
Soluie. Pentru a memora relaia de vecintate dintre ri este folosit matricea
vecin definit prin:
vecini, j

dac rile Ti i Tjsunt vecine

dac rile Ti i Tj nu sunt vecine


0

Pentru harta din figura de mai sus, matricea vecin2 corespunztoare este urmtoarea:
0
1
0

1 0 1 0
0 1 1 1
1 0 0 1
1 0 0 1
1 11 1 0

1 0 10 1 1

0
1

Problema poate fi generalizat uor la o hart cu n ri ce trebuie colorat (cu


restricia menionat) folosind un numr dat de s culori.
Programul este urmtorul:
#include <iostream> using namespace
std; const int N=6;
const int S=3;
int nv=0; // Numarul variantei de colorare
int vecin[N+1][N+1],x[N];
int retsol()
{
int i; nv++;
cout<<"Varianta "<<nv<<" este: ";
for (i=1;i<=N;i++)
switch (x[i])
{
case 1 : cout<<" g ";break;
case 2 : cout<<" a ";break;
case 3 : cout<<" r ";break;
};
cout<<"\n";
return 0;
}
int continuare(int k)
{
int i=1,b=1;
while (b&&(i<k))
{
b=!(vecin[k][i]&&(x[k]==x[i]));
i++;
}
return b;
}
int citeste()
{
int i,j;
for (i=1;i<=N;i++)
for (j=1;j<=N;j++) vecin[i][j]=0;
for (i=1;i<=N;i++)
{
cout<<"Tara "<<i<<" vecina cu (0 pt. finalizare): ";
cin>>j;
while (j!=0)

{
vecin[i][j]=1;vecin[j][i]=1;
cin>>j;

}
}
cout<<"\n";
return 0;
}

int backtracking(int k)
{
int i;
if (k==N+1) retsol();
else
for (i=1;i<=S;i++)
{
x[k]=i;
if (continuare(k)) backtracking(k+1);
}
return 0;
}
int main()
{
citeste();
cout<<"Tara:

6"<<'\n';

backtracking(1);
return 0;
}

Plata unei sume folosind monede de valori date


Se dau n tipuri de monede avnd valori de v1, v2, , vn. Se cer toate modalitile de plat a sumei s
utiliznd aceste monede. Se presupune c fiecare moned exist ntr-un numr nelimitat de exemplare.

Soluie. Valorile celor n monede sunt reinute de vectorul v. Astfel, v[1] va reine valoarea monedei de
tipul 1, v[2] valoarea monedei de tipul 2, .a.m.d. Numrul de monede din fiecare tip va fi reinut de
vectorul sol. Astfel, sol[1] reine numrul de monede de tipul 1, sol[2] reine numrul de monede de tipul
2, .a.m.d.
Orice soluie are exact n componente (pentru monedele care nu sunt luate n calcul n vectorul sol se
reine 0, din acest motiv la nceput, fiecare component a vectorului sol va fi iniializat cu valoarea -1).
La

pasul

avem

la

dispoziie

suma

obinut:

s=v[1]*sol[1]+v[2]*sol[2]++v[k-1]*sol[k-1]. O soluie avem


numai dac:
s=v[1]*sol[1]+v[2]*sol[2]++v[n]*sol[n]=Suma
Programul este urmtorul:
#include <iostream>
using namespace std;
int v[100],sol[100],n,i,s,suma;
void backtracking(int k)
{
if (s==suma)
{
cout<<"Solutie\n";
for (i=1;i<=k-1;i++)
if (sol[i]!=0) cout<<sol[i]<<" monede de " <<v[i]<<"\n";
cout<<"\n";
}
else
{
sol[k]=-1;
while (((s+sol[k]*v[k])<suma)&&(k<n+1))
{
sol[k]++; s=s+sol[k]*v[k];
backtracking(k+1); s=ssol[k]*v[k];
}
}
}
int main()
{

cout<<"Suma = "; cin>>suma;


cout<<"\n n= "; cin>>n;
for (i=1;i<=n;i++)
{
cout<<"v["<<i<<"]= ";
cin>>v[i];
}
backtracking(1);
}
V rmne ca exerciiu rezolvarea problemei n situaia n care numrul de monede
este limitat pentru fiecare tip de moned n parte. Astfel, numrul de monede de valoare vi de tipul i este
reinut n componenta bi din vectorul b. Astfel, pentru s=100, v=(2,5,50),

b=(10,9,4),

varianta

s=10x5+1x50 nu corespunde cerinei deoarece nu avem la dispoziie 10 monede de 5, ci doar 9.


Backtracking generalizat (n plan)
n rezolvarea problemelor care necesit determinarea unor trasee de deplasare a
unor obiecte n plan n zone codificate matriceal se poate utiliza o generalizare a metodei
Backtracking. ntruct soluiile unor astfel de probleme nu se mai reprezint printr-un singur vector ci
prin doi vectori

x=(x1,x2,,xf) i y=(y1,y2,,yf) n care (xi,yi) reprezint indicii elementelor

matricii ce constituie traseul de deplasare a obiectului, aceti indici putem s spunem c reprezint
coordonatele unor puncte din planul XOY reprezentnd traseul de deplasare.

(0,0)
x
y1
y2
y3

yf-1

yf
Y

x2

x3

xf-1

xf

Mergnd i mai departe cu generalizarea, putem s aplicm metoda ntr-un spaiu


cu n dimensiuni (soluia fiind reprezentat prin n vectori).
n unele situaii problemele nu cer afiarea traseului de deplasare ci se cere s se
determine doar dac exist sau nu un asemenea traseu.
S studiem n continuare la modul general o problem n spaiul cu dou dimensiuni
(n plan).
Fie un caroiaj avnd m linii i n coloane ca cel din figura urmtoare.
1

j0

i0
M
Exemplu de caroiaj
S presupunem c un mobil (pies de ah, robot etc.) pleac din punctul (ptratul) iniial (i0,j0), unde
i0 reprezint numrul liniei, iar j0 reprezint numrul coloanei i c el se deplaseaz conform unor
reguli sintetizate (memorate) n doi vectori di i dj

avnd o dimensiune d dat, cu urmtoarea semnificaie: dac mobilul se afl n punctul de coordonate (i,
j), el se poate deplasa doar ntr-unul dintre punctele (i+di[k],j+dj[k]), k=1,2,...,d, bineneles cu condiia
ca s nu ias din caroiaj.
De exemplu, s presupunem c mobilul se poate deplasa doar astfel:
- cu o poziie la dreapta pe aceeai linie;
- cu o poziie la stnga pe aceeai linie;
- cu o poziie n sus pe aceeai coloan;
- cu o poziie n jos pe aceeai coloan.
Ca

urmare

acestor

posibiliti

de

micare

mobilului,

vom

avea: di=(0,0,1,-1) i

dj=(1,-1,0,0), astfel c, de exemplu, pentru k=2 vom avea di[k]=0 i dj[k]=-1 ceea ce are urmtoarea
semnificaie: mobilul se deplaseaz zero linii i o coloan n jos (semnul minus indicnd micarea
mobilului la stnga i n jos iar semnul plus indicnd micarea la dreapta i n sus).
n practic apar de nenumrate ori astfel de situaii i, mai mult, n unele probleme, se consider c
anumite ptrate ale caroiajului sunt "ocupate" (ceea ce nseamn c mobilul nu poate fi plasat prin
deplasri succesive ntr-un astfel de ptrat). Bineneles c ptratul iniial se presupune c nu este ocupat.
Frecvent se ntlnesc probleme ce constau n:
- simularea micrii mobilului;
- determinarea ptratelor n care mobilul poate s ajung prin deplasri permise;
- determinarea unei succesiuni de deplasri n urma crora mobilul poate s ajung
ntr-un punct (if,jf) dat, dac o astfel de succesiune exist;
- determinarea celei mai scurte succesiuni de deplasri n urma crora mobilul poate s ajung ntr-un
punct (if,jf) dat, accesibil din punctul iniial (traseul optim).
Astfel de probleme pot fi rezolvate i prin aplicarea metodei Backtracking (dar n unele situaii sunt
mult mai eficiente alte metode cum ar fi, de exemplu, metoda Branch and Bound).
Pentru rezolvarea unor astfel de probleme cu metoda Backtracking, vectorului x ntlnit pn acum
n acest capitol va avea n continuare o nou semnificaie. Elementele sale vor lua valori n
mulimea {1,2,...,d} iar semnificaia sa este urmtoarea: dac dup k-1 mutri mobilul a ajuns n
poziia (i,j), atunci, dup urmtoarea sa mutare, mobilul va ajunge n poziia (i+di[x[k]],j+dj[x[k]]). Cu
alte cuvinte, vectorul x va conine numrul de ordine al deplasrilor permise (adic va conine indicele la
care s-a ajuns n tablourile di i dj).
Funcia continuare va fi tot o funcie boolean ce permite s determinm dac o anumit ncercare de
deplasare este permis, adic nu se ajunge la vreuna dintre eventualele poziii ocupate i nu se iese din
caroiaj.
Rutina Backtracking este asemntoare cu cele utilizate anterior n acest capitol numai c vor aprea
n plus n ea variabilele i i j cu semnificaia c (i,j) este poziia curent a mobilului.
S aplicm toate acestea la o problem concret.

Sritura calului. Fie dat o "tabl de ah", de dimensiune nxn. Se pleac cu un cal din poziia (1,1),
adic din colul din stnga sus. Nefiind interzis nici o poziie, se cere s se efectueze cu calul,
conform regulilor jocului de ah, o succesiune de mutri astfel nct calul s treac o dat i numai
o dat prin fiecare ptrat al tablei de ah.
Soluie. Ca urmare a posibilitilor micrii calului notat n figura de mai jos cu C,

ptrelele negre reprezentnd locurile unde poate fi mutat calul, vom avea:
di=(1,2,2,1,-1,-2,-2,-1) i
dj=(2,1,-1,-2,-2,-1,1,2).
Se va utiliza o matrice a. Pentru ca verificrile efectuate de funcia continuare s fie mai uoare, vom
"borda" matricea a cu dou prime linii, cu dou ultime linii, cu dou prime coloane i cu dou ultime
coloane, ptratele nou introduse prin bordare intrnd n categoria celor ocupate (interzise). Bordarea s-a
fcut cu cte 2+2 linii i 2+2 coloane deoarece calul poate s sar cu cte dou ptrele la dreapta, sus,
stnga sau jos. Drept urmare, liniile i coloanele vor fi numerotate cu -1,0,1,...,n,n+1,n+2. Ne propunem
ca n final matricea a s ilustreze traseul calului, deci s conin elementele 1,2,...,n2 astfel nct o sritur
a calului de pe o poziie pe poziia urmtoare s corespund la numere succesive n matrice. Poziiile
interzise vor primi valoarea -1, iar cele libere (poziiile efective ale tablei de ah) vor primi valoarea 0
(aceste iniializri fiind efectuate de rutina iniializare). Prin urmare, calul poate s treac n poziia (i,j)
dac i numai dac a[i,j]=0.
Pentru a evita determinarea de soluii simetrice, vom pune a[2,3]=2 i vom ncepe cu k=2, a[1,1]=1
i a[2,3]=2. De altfel vom urmri determinarea unei singure soluii pentru c vom vedea c i aa
timpul de execuie va fi destul de mare chiar pentru valori mici ale lui n. Deficiena acestui
algoritm const n faptul c ordinea de deplasare a calului plecnd din poziia iniial este prestabilit
prin vectorii di i dj, fixai de la nceput.
Rutina backtracking() se termin cnd au fost ocupate toate cele n2 cmpuri ale tablei de ah.
Programul este urmtorul:
#include <iostream>
using namespace std;
int

di[8]={1,2,2,1,-1,-2,-2,-1},
dj[8]={2,1,-1,-2,-2,-1,1,2},
[13],x[100],
i,n,n2;

a[13]

int initializare()
{
int i,j;
for (i=0;i<=n+3;i++)
{
a[0][i]=1; a[1][i]=1; a[n+2]
[i]=1; a[n+3][i]=1; a[i][0]=1;
a[i][1]=1; a[i][n+2]=1; a[i]
[n+3]=1;
}
for (i=2;i<=n+1;i++)
for (j=2;j<=n+1;j++) a[i][j]=0;
return 0;
}
int continuare(int i,int j)
{
if (a[i][j]==0) return 1;
else return 0;
};
int retsol()
{
int i,j;
for (i=2;i<=n+1;i++)
{
for (j=2;j<=n+1;j++)
if (a[i][j]>9) cout<<a[i][j]<<" ";
else cout<<" "<<a[i][j]<<" ";
cout<<"\n";
}
return 0;
}
int backtracking()
{
int k,i,j; a[2][2]=1;a[3][4]=2;
i=3;j=4;k=2;x[k]=-1;
while (k>1)

if (k==n2) k=0;
else
if (x[k]<7)
{
x[k]++;
if (continuare(i+di[x[k]],j+dj[x[k]]))
{
i=i+di[x[k]];j=j+dj[x[k]];
k++;a[i][j]=k;x[k]=-1;
}
}
else
{
a[i][j]=0;k--;
i=i-di[x[k]];j=j-dj[x[k]];
}
return 0;
}
int main()
{
cout<<"n= ";cin>>n;cout<<"\n";
initializare(); n2=n*n;
backtracking();
retsol();
return 0;
}
Problema sriturii calului poate fi rezolvat folosind i alte metode de programare
(ca, de exemplu, metoda Branch and bound sau algoritmii probabilistici cum ar fi algoritmii Las
Vegas) ce permit obinerea soluiei ntr-un timp mult mai scurt.

5.Variante ale metodei Backtracking


Pn acum am prezentat metoda Backtracking n forma sa standard. n practic se
ntlnesc nenumrate "abateri" de la aceast form standard a metodei Backtracking, dintre care cele mai
uzuale sunt urmtoarele:
- soluia x poate avea un numr variabil de componente (deci nu neaprat n);
- dintre soluii trebuie aleas una care s maximizeze (minimizeze) o funcie de cost dat iar aceast
soluie se va numi soluie optim.
Problema care urmeaz ilustreaz ambele aspecte menionate mai sus.
Problema 9.9. (Subir maxim). Fie dat un vector a cu n componente ntregi. S se determine un
subir al lui a (ale crui elemente sunt luate dintre componentele vectorului a nu neaprat unul
imediat dup cellalt) de lungime maxim, subir ale crui elemente sunt n ordine cresctoare. Mai
precis, se caut cea mai mare valoare k pentru care exist indicii x[1]<x[2]< ... <x[k] astfel nct
a[x[1]]<a[x[2]]< ... <a[x[k]].
Exemplu. Pentru n=8 i a=(1,2,5,3,9,4,7,8), se obine k=6, o secven de indici satisfcnd condiiile
date fiind 1,2,4,6,7,8, creia i corespunde subirul (1,2,3,4,7,8) format din elemente ale vectorului a
care sunt n ordine cresctoare (n vectorul luat ca exemplu ele sunt subliniate).
Soluie. O soluie va fi un vector x ce conine secvena de indici ai tabloului a cu restriciile
menionate n problem i care satisface n plus condiia c el nu mai poate fi suplimentat n plus cu nc o
component. Astfel, pentru exemplul dat, cazul particular x=(1,2,3,7,8) este considerat soluie (soluia
n acest caz fiind subirul (1,2,5,7,8)
corespunztor indicilor dai de x); n schimb x=(1,2,3) nu este soluie, deoarece poate fi suplimentat, de
exemplu, cu componenta 7. Lungimea efectiv va fi memorat n variabila k.
Funcia de cost ataeaz fiecrei soluii lungimea sa.
n variabila kf va fi memorat lungimea unei soluii optime, iar soluia optim va fi memorat n
vectorul xf. Vom pune iniial kf=0, urmnd ca de fiecare dat cnd determinm o soluie x de lungime k
s verificm dac k>kf, n caz afirmativ actualiznd pe xf i kf (acest lucru realizndu-l rutina retsol).
Cerina problemei ca indicii s fie n ordine cresctoare impune ca pentru k>1, iniializarea
pentru x[k] s fie:
x[k]:=x[k-1]
Pentru a uura verificrile realizate de funcia continuare, vom introduce dou
componente suplimentare: a[0] care va fi iniializat cu cea mai mic valoare ntreag i, respectiv
a[n+1] care va fi iniializat cu cea mai mare valoare ntreag; n acest mod putnd identifica uor
faptul c o secven cresctoare de indici x[1]<x[2]<...<x[k] nu poate fi suplimentat, verificnd condiia
x[k]<n+1.
Programul este urmtorul:
#include <iostream>
using namespace std;

#define MAXINT 32767


int a[100],x[100],xf[100],n,kf,i;
int continuare(int k)
{
if (a[x[k-1]]<a[x[k]]) return 1;
else return 0;
}
void retsol(int k)
{
int i;
if (k>kf)
{
kf=k;
for (i=1;i<=kf;i++) xf[i]=x[i];
}
}
void backtracking()
{
int k=1;
x[0]=0; x[1]=0;
while (k>0)
if (x[k]<n+1)
{
x[k]++;
if (continuare(k))
{
if (x[k]==n) { retsol(k); k--; }
else { k++; x[k]=x[k-1]; }
}
}
else k--;
}
int main()
{
cout<<"n= "; cin>>n;cout<<'\n';
cout<<"Elementele vectorului\n"; for
(i=1;i<=n;i++) cin>>a[i]; a[0]=-MAXINT; n++;
a[n]=MAXINT; kf=0; backtracking();
cout<<"O secventa crescatoare de lg. max. este:\n";
for (i=1;i<=kf-1;i++)
cout<<a[xf[i]]<<" ";
cout<<"\n";
cout<<" si are lungimea "<<kf-1;

return 0;
}

6.Probleme si exercitii
Sa se afiseze in ordine alfabetica anagramele unui cuvant format din litere distincte.
Exemplu:
date.in
rac
date.out
acr
arc
car
cra
rac
rca
REZOLVARE:
#include <fstream>
#include <cstring>
using namespace std;
ifstream is("date.in");
ofstream os("date.out");
int n;
char v[100],st[50], p[50];
void scrie()//afisez litere conform permutarii
{
int i;
{
for(i=1;i<=n;i++)
os<<v[st[i]-1];
os<<'\n';
}
}

void back(int k) /generez permutarile multimii {1,2,...n}


{
for(int i=1;i<=n;i++)
if(!p[i])
{
st[k]=i;
p[i]=1;
if(k==n) scrie();
else back(k+1);
p[i]=0;
}
}
int main()
{
int i;
is.get(v,1000);
n=strlen(v);
for(int i=0;i<n;i++) //ordonez alfabetic literele din cuvantul citit
for(int j=i+1;j<n;j++)
if(v[i]>v[j])
{
char aux=v[i]; v[i]=v[j]; v[j]=aux;
}
back(1);
is.close();
os.close();
return 0;
}

Se citesc n numere naturale. Determinati o aranjare a acestor numere sub forma unui
cerc, astfel incat suma produselor de cate doua numere alaturate sa fie maxima.
Exemplu:
date.in
6

183254
date.out
124853
REZOLVARE:
#include <fstream>
using namespace std;
ifstream is("date.in");
ofstream os("date.out");
int x[20],n,b[20],pmax=0;
void afis()
{
for (int i = 1; i <= n; i++) os<<b[i]<<" ";
os<<endl;
}
void alege()//alege permutarea pentru care se obtine suma maxima
{
int p=x[1]*x[n];
for(int i=1;i<n;i++)
p=p+x[i]*x[i+1];
if(p>pmax)
{
pmax=p;
for(int i=1;i<=n;i++) b[i]=x[i];
}
}
void inter(int &x, int &y)//interschimba doua valori
{
int aux=x; x=y; y=aux;
}
void perm(int k, int n)//genereaza permutarile

{
for (int i = k; i <= n; i++)
{
inter(x[k], x[i]);
if(k==n) alege();
else perm(k+1, n);
inter(x[k], x[i]);
}
}
int main()
{
is>>n;
for(int i=1;i<=n;i++) is>>x[i];
perm(1,n);
afis();
return 0;
}
Se citeste un numar natural n. Afisati permutarile multimii 1,2,3...n in care
elementele pare sunt puncte fixe (se afla pe pozitie egale cu valoarea lor).
Exemplu:
n=5
permutarile care respecta conditia sunt:
12345
12543
32145
32541
52143
52341
(2 si 4 sunt puncte fixe)
REZOLVARE:
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

int n, sol[40], p[40];


void afis()
{
for(int i=1;i<=n;i++)
if(i%2==0) fout<<i<<" ";
else fout<<sol[i]<<" ";
fout<<endl;
}
void back(int k)
{
for(int i=1;i<=n;i=i+2)
if(p[i]==0)
{
sol[k]=i;
p[i]=1;
if(n%2==0 && k==n-1 || n%2==1 && k==n) afis();
else back(k+2);
p[i]=0;
}
}
int main()
{
fin>>n;
back(1);
fin.close();
fout.close();
}

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