Sunteți pe pagina 1din 10

1 METODA BACKTRACKING..................................................................................................................2 1.1 CONSIDERAII TEORETICE......................................................................................................................2 1.1.1 Aplicabilitatea algoritmilor de tip Backtracking......................................................................2 1.1.

2 Modul de lucru al algoritmilor de tip Backtracking.................................................................2 1.2 IMPLEMENTARE....................................................................................................................................3 1.3 EXEMPLE............................................................................................................................................4 1.3.1 Sritura calului pe tabla de ah...............................................................................................4 1.4 PROBLEME PROPUSE.............................................................................................................................9 1.4.1 Ieirea din labirint...................................................................................................................9 1.4.2 Ieirea cea mai rapid din labirint........................................................................................10 1.4.3 Problema celor 8 regine........................................................................................................10

1 Metoda Backtracking
1.1 Consideraii teoretice
1.1.1 Aplicabilitatea algoritmilor de tip Backtracking
Algoritmii de tip backtracking se aplic la problemele unde spaiul soluiilor S este de forma unui produs cartezian S = S0 S1 ... Sn-1. Orice element x din spaiul soluiilor S va fi un vector de forma x = (x0, x1, ..., xn-1), cu xi Si, 0<i<n. Nu toate elementele xS sunt soluii valide ale problemei. Doar acele elemente x care satisfac anumite condiii impuse de problem vor fi soluii valide. Definim condiiile care trebuie satisfcute sub forma unei funcii booleene Solutie(x0,x1,...,xn-1). Un element x=(x0, x1, ..., xn-1) S este soluie a problemei dac funcia Solutie aplicat componentelor lui x va returna valoarea true. Scopul este de a gsi acei vectori xS pentru care funcia Solutie returneaz true.

1.1.2 Modul de lucru al algoritmilor de tip Backtracking


Modul de lucru al algoritmului este urmtorul: se alege un element x00 din S0, apoi un element x10 din S1, apoi un element x20 din S2, .a.m.d, pn cnd se va fi ales un element xn-10 din Sn-1. n acest moment vom avea un vector x = (x00, x10, ..., xn-20, xn-10) S. Evalum rezultatul funciei Solutie pentru acest vector. Dac obinem rezultatul true atunci avem o soluie corect a problemei. Dac obinem rezultatul false, soluia nu este corect i continum cutarea. Aplicm principiul revenirii pe cale, adic ne ntoarcem cu un pas n urm, nainte de alegerea elementului xn-10 Sn-1. De aici continum alegnd un alt element xn1 1 0 0 0 0 1 1 Sn-1, xn-1 xn-1 . Vom obine un nou vector x' = (x0 ,x1 ,...,xn-2 ,xn-1 ) S asupra cruia aplicm din nou funcia Solutie. Dac rezultatul este false, revenim din nou cu un pas n urm i continum cu alegerea unui alt element xn-12 Sn-1, xn-12 xn-10 i xn-12 xn1 1 . Repetm aceti pai pn la epuizarea tuturor elementelor din Sn-1. Dup ce am epuizat toate elementele mulimii Sn-1, vom fi la faza n care am ales elementele x00, x10, ..., xn-20. Ar trebui s alegem un element din Sn-1, dar cum toate elementele din Sn-1 au fost deja alese, revenim cu nc un pas n urm pe cale, i alegem un alt element xn-21 Sn-2. Pe urm rencepem alegerea elementelor din Sn-1 cu primul dintre ele, xn-10 Sn-1. Vom avea vectorul x = (x00, x10,..., xn-30, xn-21, xn-10). Procednd n acest mod, vom ajunge practic s construim toi vectorii xS, adic vom explora ntreg spaiul soluiilor posibile. Exist n principiu dou categorii de probleme: unele care cer gsirea unei singure soluii i unele care cer gsirea tuturor soluiilor. Atunci cnd se cere gsirea unei singure soluii, putem opri cutarea dup gsirea primului vector x pentru care funcia Solutie returneaz valoarea true. La cealalt categorie de probleme este nevoie s parcurgem ntreg spaiul soluiilor pentru a gsi toate soluiile. Parcurgerea ntregului spaiu al soluiilor posibile este foarte mare consumatoare de timp. n general se poate accelera aceast parcurgere prin urmtoarea optimizare:

pornind de la funcia Solutie(x0, x1, ..., xn-1), definim o funcie Continuare(x0, x1, ..., xk) care s ne spun dac, avnd alese la un moment dat elementele x0, x1, ..., xk exist o ans de a se ajunge la o soluie. Aceast optimizare este foarte util deoarece de multe ori dup alegerea ctorva elemente x0, x1, ..., xk ne putem da seama c nu vom putea gsi nici o soluie valid care conine elementele respective. n asemenea situaii nu mai are rost s continum alegerea de elemente pn la xn-1, ci putem direct s revenim cu un pas n urm pe cale i s ncercm cu un alt element xk'.

1.2 Implementare
Algoritmul backtracking redactat sub form de pseudocod arat n felul urmtor:
k = 0; while (k >= 0) { do { * alege urmatorul x[k] din multimea S[k] * evalueaza Continuare(x[1], x[2], ..., x[k]) } while ( !Continuare(x[1], x[2], ..., x[k]) && (* mai sunt elemente de ales din multimea S[k]) ) if (Continuare(x[1], x[2], ..., x[k])) { if (k == n-1) { if (Solutie(x[1], x[2], ..., x[n])) * afiseaza solutie } else { k = k + 1; } } else { k = k - 1; }

Dac analizm modul de funcionare al algoritmului backtracking, vom vedea ca la orice moment de timp ne este suficient un tablou de n elemente pentru a memora elementele x0, x1, ..., xn-1 alese. Ca urmare n implementare declarm un tablou x de dimensiune n. Tipul de date al tabloului depinde de problem, de tipul mulimilor S. Variabila k ne indic indicele mulimii din care urmeaz s alegem un element. La nceput de tot trebuie s alegem un element din mulimea S0, de aceea l iniializm pe k cu valoarea 0. Pe urm k se modific n funcie de modul n care avansm sau revenim pe calea de cutare. Pentru alegerea urmtorului xk din mulimea Sk, ne bazm pe faptul c ntre elementele fiecrei mulimi Sk exist o relaie de ordine. Dac nc nu s-a ales nici un element din mulimea Sk atunci l alegem pe primul conform relaiei de ordine. Dac

deja s-a ales cel puin un element, atunci l alegem pe urmtorul neales, conform relaiei de ordine.

1.3 Exemple
1.3.1 Sritura calului pe tabla de ah
Enun Avem o tabl de ah de dimensiune 8x8. S se gaseasc toate modalitile de a deplasa un cal pe aceast tabl, astfel nct calul s treac prin toate csuele de pe tabl exact o dat. Rezolvare Pentru a parcurge fiecare csu de pe tabla de ah exact o dat, calul va trebui s fac exact 8 8 = 64 de pai. La fiecare pas el poate alege oricare din cele 64 de csue de pe tabl. S codificm csuele de pe tabla de ah n modul urmtor: csua de la linia i i coloana j o notm prin perechea (i,j). S notm mulimea tuturor csuelor de pe tabl cu C: C = {(0,0), (0,1), ..., (0,7), (1,0), ..., (7,7)}. O soluie a problemei o putem nota printr-un vector x = (x0, x1, ..., x63), unde xS = C C C ... C (produs cartezian n care mulimea C apare de 64 de ori), iar xi C, i {0, 1, ..., 63}. Cu aceste elemente putem vedea c se poate aplica o rezolvare de tip backtracking. Funcia Solutie va verifica s nu existe dou elemente ci i cj care au aceeai valoare, deoarece asta ar nsemna c s-a trecut de dou ori prin aceeai csu. n plus funcia mai trebuie s verifice faptul c i {0, 1, ..., 61, 62} calul poate sri de la csua ci la csua ci+1. Asta nseamn c fie ci i ci+1 se afl la dou linii distan i la o coloan distan, fie ele se afl la o linie distan i la dou coloane distan. Funcia Continuare trebuie s fac exact aceleai verificri ca i funcia Solutie, dar nu pentru toate 64 de csue ci pentru cele k csue care au fost alese pn la un moment dat. Cod surs n continuare prezentm codul surs pentru rezolvarea acestei probleme.
#include <stdio.h> #include <conio.h> #include <stdlib.h> /* Dimensiunea tablei de sah definita ca si constanta. Pentru o tabla de dimensiune 8x8 gasirea solutiilor dureaza foarte mult, de aceea lucram pe o tabla de 5x5 unde solutiile sunt gasite mult mai repede. */ #define N 5 #define INVALID -1 int main(void) { /* Pentru o tabla de dimensiune N vom memora solutiile intr-un vector de dimensiune N*N. Fiecare element din vector va fi la randul lui un vector cu doua elemente, primul element va memora linia de pe tabla, iar al doilea element va memora coloana de pe tabla. */ int c[N*N][2];

int k, i; int pe_tabla, continuare; int delta_l, delta_c; /* Numaram si cate solutii sunt gasite. */ int count = 0; /* Pentru inceput marcam toate elementele vectorului "c" cu INVALID, semn ca nu am ales nici un element din multimile produsului cartezian. */ for (i=0; i<N*N; i++) { c[i][0] = INVALID; c[i][1] = INVALID; } k = 0; while (k >= 0) { /* Incercam sa plasam mutarea "k" a calului in fiecare casuta a tablei de joc, pe rand. Evaluam la fiecare alegere functia "Continuare". Ne oprim fie atunci cand am incercat toate casutele de pe tabla, fie atunci cand gasim o casuta unde functia "Continuare" returneaza "true". */ do { /* Aici alegem urmatorul element din multimea "C[k]". Daca elementul "c[k]" este setat pe INVALID, inseamna ca inca nu am ales nici un element din multimea curenta, deci alegem primul element (plasam calul in casuta de la linia 0 si coloana 0). */ if (c[k][0] == INVALID) { c[k][0] = 0; c[k][1] = 0; pe_tabla = 1; } /* Daca elementul "c[k]" nu este setat pe invalid, inseamna ca deja am ales o casuta din multimea "C[k]". Acum alegem urmatoarea casuta de pe tabla. Cu alte cuvinte incercam sa plasam calul in urmatoarea casuta. Daca este posibil incercam sa ramanem pe aceeasi linie si sa ne deplasam cu o coloana spre dreapta. */ else if (c[k][1] < N-1) { c[k][1]++; pe_tabla = 1; } /* Daca cumva eram chiar la ultima casuta

din linie, atunci alegem prima casuta din linia urmatoare. Ne asiguram ca nu eram cumva si pe ultima linie a tablei, caz in care am epuizat toate casutele. */ else if (c[k][0] < N-1) { c[k][1] = 0; c[k][0]++; pe_tabla = 1; } /* Daca eram pe ultima linie a tablei, atunci am epuizat toate casutele. Marcam acest lucru setand variabila "pe_tabla" pe 0. */ else { pe_tabla = 0; } /* Daca casuta "c[k]" aleasa este valida (se afla pe tabla de joc), atunci trecem la calculul functiei "Continuare". */ if (pe_tabla) { /* Daca suntem la prima mutare a calului, atunci mutarea este valida oriunde ar fi ea pe tabla. */ if (k == 0) continuare = 1; /* Daca nu suntem la prima mutare, atunci trebuie sa facem o serie de verificari. */ else { /* In primul rand verificam daca de la pozitia precedenta a calului pe tabla ("c[k-1]") se poate ajunge in pozitia aleasa acum printr-o mutare. */ delta_l = abs(c[k-1][0]-c[k][0]); delta_c = abs(c[k-1][1]-c[k][1]); continuare = (((delta_l == 1) && (delta_c == 2)) || ((delta_l == 2) && (delta_c == 1))); /* Si apoi verificam daca nu cumva s-a mai trecut prin casuta aleasa acum. */ for (i=0; continuare && (i<k); i++) { if ((c[i][0] == c[k][0]) && (c[i][1] == c[k][1])) continuare = 0; }

} }

/* Daca casuta "c[k]" aleasa este in afara tablei de sah, atunci functia "Continuare" va returna automat "false". */ else { continuare = 0; } } while (!continuare && pe_tabla); /* Daca am obtinut rezultat pozitiv in urma verificarilor de "Continuare", atunci consideram piesa asezata la pozitia "c[k]" si continuam cautarea. */ if (continuare) { /* Daca s-a parcurs toata tabla de sah atunci afisam solutia. */ if (k == N*N - 1) { for (i=0; i<N*N; i++) printf("(%d,%d) ", c[i][0], c[i][1]); printf("\n"); count++; } /* Daca nu s-a parcurs inca toata tabla atunci trecem cu un pas inainte pe calea de cautare. */ else { k++; } } /* Daca casuta aleasa nu este valida, atunci marcam elementul "c[k]" cu INVALID si revenim cu un pas inapoi pe calea de cautare. */ else { c[k][0] = INVALID; c[k][1] = INVALID; k--; } } printf("%d solutii\n", count); return 0; }

Optimizare Putem face o optimizare la programul prezentat, pornind de la faptul c se cunosc regulile dup care se poate deplasa calul pe tabla de ah. La alegerea din mulimea Ck, n loc s alegem pe rnd fiecare csut i apoi s verificm dac s-ar putea face mutare n csua respectiv, e mai eficient s alegem direct dintre csuele n care se poate face mutare. Pentru a putea determina aceste csue, folosim doi vectori care definesc mutrile posibile ale calului (numrul de csue cu care se poate deplasa pe orizontal i pe vertical). Prezentm mai jos codul surs care implementeaz aceast

optimizare. Implementarea este una recursiv, pentru a arta c modul de lucru al algoritmului backtracking se preteaz n mod natural la implementri recursive.
#include <stdio.h> #include <conio.h> #define N 5 int dx[8] = {-1, -2, -2, -1, int dy[8] = {-2, -1, 1, 2, int c[N*N][2]; int count = 0; void back(int pas) { int i, j, continuare; if (pas == N*N) { for (i=0; i<pas; i++) printf("(%d,%d) ", c[i][0], c[i][1]); printf("\n"); count++; } else { for (i=0; i<8; i++) { c[pas][0] = c[pas-1][0] + dy[i]; c[pas][1] = c[pas-1][1] + dx[i]; if ((c[pas][0]>=0) && (c[pas][0]<N) && (c[pas][1]>=0) && (c[pas][1]<N)) { continuare = 1; for (j=0; continuare && (j<pas); j++) { if ((c[j][0] == c[pas][0]) && (c[j][1] == c[pas][1])) continuare = 0; } if (continuare) back(pas+1); 1, 2, 2, 2, 1}; 1, -1, -2};

} } } }

int main(void) { int i,j; for (i=0; i<N; i++) for (j=0; j<N; j++) { c[0][0] = i; c[0][1] = j; back(1); } printf("%d solutii\n", count);

return 0;

1.4 Probleme propuse


1.4.1 Ieirea din labirint
S se scrie un program care gsete toate cile de ieire dintr-un labirint. Configuraia labirintului se citete din fiierul text labirint.dat. Labirintul are forma unei matrici de dimensiune NxM. Un element al matricii poate fi perete sau spaiu liber. n fiierul de intrare labirintul este descris pe N linii de cte M caractere. Peretele se codific prin litera 'P', iar spaiul liber prin caracterul '.'. Un exemplu de fiier de intrare care descrie un labirint este: PPPP.PPP P....PPP PP..PPSP PP.PP..P P..PP.PP P.PPP..P P...PP.P PPP.PP.P PPP....P PPPPPPPP n afar de caracterele 'P' i '.', n fiierul de intrare va mai apare i caracterul 'S', o singur dat. Caracterul 'S' reprezint un spaiu liber n labirint i marcheaz punctul de unde ncepem cutarea ieirilor. Deplasarea se poate face doar pe vertical i pe orizontal ntre oricare dou celule nvecinate, cu condiia ca ambele celule s fie spaii libere. Nu este permis s se treac de mai multe ori prin aceeai celul. Pentru fiecare cale de ieire gsit, programul trebuie s afieze aceast cale pe ecran, dup care va atepta apsarea unei taste. Dup apsarea unei taste se va trece la urmtoarea cale de ieire gsit, .a.m.d. Pentru afiarea unei ci de ieire se va folosi aceeai codificare ca i cea din fiierul de intrare, cu diferena c se va marca drumul de ieire prin caracterul 'x'. Spre exemplu un drum de ieire posibil pentru labirintul de mai sus este: PPPPxPPP P.xxxPPP PPx.PPSP PPxPPxxP PxxPPxPP PxPPPxxP PxxxPPxP

PPPxPPxP PPPxxxxP PPPPPPPP Dac nu exist nici un drum de ieire din labirint, programul va afia mesajul Nu avem nici un drum de ieire. Un labirint va avea maxim 24 de linii i 80 de coloane. Valorile pentru N i M nu sunt date explicit, va trebui s le deducei din structura fiierului de intrare. Se garanteaz c datele de intrare sunt corecte.

1.4.2 Ieirea cea mai rapid din labirint


Pentru problema anterioar s se determine i s se afieze pe ecran doar cel mai scurt drum de ieire din labirint.

1.4.3 Problema celor 8 regine


Avem o tabl de ah de dimensiune 8x8. S se aeze pe aceast tabl de ah 8 regine astfel nct s nu existe dou regine care se atac ntre ele. Implementarea trebuie s fie fcut folosind recursivitatea.

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