Sunteți pe pagina 1din 66

V.

Metode generale de proiectare a algoritmilor i programelor

1. Metoda Greedy 2. Metoda Divide and Conquer(Tehnica divizrii) 3. Metoda Backtracking(Algoritmi cu revenire)

Metode generale de proiectare


Introducere
n teoria i practica programrii exist un numr foarte mare de probleme pentru care trebuie gsite rezolvri. Din totalitatea acestor probleme se disting clase de probleme similare. Pentru problemele dintr-o asemenea clas se poate aplica aceeai metod general de rezolvare, evident cu mici ajustri care depind de problema concret. n timp s-au cristalizat mai multe metode generale de rezolvare a problemelor. Programatorii experimentai stpnesc foarte bine aceste metode generale i le aplic n mod automat atunci cnd au posibilitatea. Vom prezenta n continuare trei asemenea metode i anume, Greedy, Divide and Conquer i Backtracking. Se recomand studiul foarte atent al acestor metode, nelegerea contextului n care ele se pot aplica i nsuirea lor n asemenea mod nct aplicarea s poat deveni un automatism.

Greedy
Explicarea numelui
n limba englez cuvntul greedy nseamn lacom. Algoritmii de tip greedy sunt algoritmi lacomi: ei urmresc s construiasc ntr-un mod ct mai rapid soluia problemei. Algoritmii de tip greedy se caracterizeaz prin luarea unor decizii rapide care duc la gsirea unei soluii a problemei. Nu ntotdeauna asemenea decizii rapide conduc la o soluie optim, dar vom vedea c exist anumite tipuri de probleme unde se pot obine soluii optime sau foarte apropiate de optim.

Greedy
Principii
Metoda Greedy se aplic la acele probleme la care, dndu-se mulimea A a datelor de intrare, se cere s se determine o submulime B a sa, care trebuie s ndeplineasc anumite condiii pentru a fi acceptat ca soluie posibil. n general, exist mai multe soluii posibile. Dintre acestea se pot selecta, conform unui anumit criteriu, nite submulimi B* care reprezint soluii optime ale problemei. Scopul este acela de a gsi una dintre mulimile B*. Dac acest lucru nu este posibil, atunci scopul este gsirea unei mulimi B care s fie ct mai aproape de mulimile B*, conform criteriului de optimalitate impus. Soluiile posibile au urmtoarea proprietate: dac B este o soluie posibil atunci orice submulime a sa, inclusiv mulimea vid este, de asemenea, soluie posibil.

Greedy
Principii
Construirea mulimii B se face printr-un ir de decizii. Iniial se pornete cu mulimea vid (B = ). Fiecare decizie const n alegerea unui element din mulimea A, analiza lui i eventual introducerea lui n mulimea B. n funcie de modul n care se iau aceste decizii, mulimea B se va apropia mai mult sau mai puin de soluia optim B*. n cazul ideal vom avea B = B*. La fiecare pas se analizeaz cte un element din mulimea A i se decide dac s fie sau nu inclus n mulimea B care se construiete. Astfel se progreseaz de la cu un sir de mulimi intermediare (, B0, B1, B2, ...), pn cnd se obine o soluie final B.

Greedy
Strategii
Metoda Greedy nu urmrete s determine toate soluiile posibile ca s aleag apoi pe cea optim, conform criteriului de optimizare dat. O astfel de metod, care ar face cutare exhaustiv n spaiul soluiilor, necesit, de regul, un efort de calcul foarte mare. Metoda Greedy, n schimb, nu necesit nici timp de calcul, nici spaiu de memorie mare, comparativ cu metodele exacte. Pe baza proprietii enunate anterior, la fiecare pas al metodei exist o soluie posibil care se va mbogi cu un nou element, ales astfel nct irul soluiilor posibile s convearg spre soluia optim. Noua soluie "nghite" elementul cel mai "promitor".

Greedy
Strategia 1
Exist mai multe strategii prin care se poate implementa metoda Greedy. n continuare se prezint dou astfel de strategii care difer prin ordinea de efectuare a unor operaii. Se consider c mulimea datelor de intrare, A, are iniial n elemente. B reprezint, n orice moment, soluia posibil. Funcia logic posibil are valoarea true dac elementul selectat, transmis ca parametru, formeaz mpreun cu mulimea B o nou soluie posibil. Verificrile efectuate n aceast funcie trebuie s rezulte din enunul problemei. Iniial, B este mulimea vid. La fiecare pas al algoritmului se alege, ntr-un anumit fel, un element din A neales la paii precedeni (funcia alege). Se adaug acest nou element la soluia posibil anterioar, dac prin aceast adugare se obine tot o soluie posibil.

Greedy
Strategia 1
B = multimea vida for (i=0; i<n; i++) { x = alege( A); if (posibil( B ,x)) * adauga elementul x la multimea B; } Dificultatea la aceast prim variant const n scrierea funciei alege. Dac funcia alege este bine conceput, atunci putem fi siguri c soluia B gsit este o soluie foarte bun, apropiat de cea optim sau chiar optim. Dac funcia alege nu este foarte bine conceput, atunci soluia B gsit va fi doar o soluie posibil i nu va fi optim. => Criteriul de selecie implementat n funcia alege, o poate apropia mai mult sau mai puin de soluia optim B*.

Greedy
Strategia 2
n anumite cazuri, ordinea n care trebuie considerate elementele din mulimea A se poate stabili de la nceput (funcia prelucreaza), obinndu-se, pe baza mulimii A, un vector V cu n componente : B = multimea vida; prelucreaza(A,V); for (i=0; i<n; i++) { x = V[i]; if (posibil(B,x)) * adauga elementul x la multimea B; } La a doua variant, dificultatea funciei alege nu a disprut, ci s-a transferat funciei prelucreaza. Dac prelucrarea mulimii A este bine fcut, atunci se poate ajunge la o soluie optim. Altfel se va obine doar o soluie posibil, mai mult sau mai puin apropiat de optim.

Greedy
Probleme de optimizare
Un exemplu tipic de aplicare al metodei Greedy l reprezint problemele de optimizare. n acest tip de probleme, de regul, se cere s se selecteze din datele de intrare acele elemente care maximizeaz o funcie de cost. Ideea general a metodei este de a alege la fiecare pas acel element care determin cea mai mare cretere a acestei funcii. Neanaliznd influena corelaiei dintre elemente asupra funciei de cost, metoda nu poate garanta c aceste maximizri locale, succesive, conduc ntotdeauna la maximul global ateptat. Aceasta nseamn c sunt situaii n care metoda Greedy nu genereaz soluia optim, dei aceasta exist.

Greedy
Probleme de optimizare - exemplu
Se d o mulime X = {x0, ..., xn-1} cu elemente reale. Se cere s se determine o submulime Y a sa astfel nct f = y s fie maxim.
yY

Se observ c o soluie posibil este orice submulime a lui X avnd numai elemente pozitive, iar soluia optim este cea mai cuprinztoare dintre aceste submulimi, adic aceea care include toate elementele pozitive din X. Se opteaz pentru strategia 2 a metodei Greedy. Organiznd de la bun nceput X i Y ca vectori, funcia prelucreaza nu mai este necesar n acest caz. Iniializarea lui B pe se reduce la atribuirea k = 0. Funcia posibil este reprezentat prin condiia x[i] > 0 iar operaia de obinere a unei noi soluii posibile, prin adugarea unui element din intrare este dat de operaiile y[k] = x_ales i k++.

Greedy
Probleme de optimizare - exemplu
k = 0; suma = 0; for (i=0; (i<N) && (x[i]>0); i++) { x_ales = x[i]; y[k] = x_ales; k++; suma += x_ales; }

Greedy
Interclasarea optimal a mai multor iruri
Se dau n iruri S1, S2, ..., Sn de lungimi l1, l2, ..., ln. n cadrul fiecrui ir elementele sunt ordonate cresctor. Se cere s se obin un ir S cu l1+l2+...+ln elemente, ordonate de asemenea cresctor, coninnd elementele cumulate ale celor n iruri iniiale. Se dispune de o funcie capabil s interclaseze dou iruri ordonate care i se transmit ca parametri. Se cere, de asemenea, ca programul realizat s fie optim din punct de vedere al timpului total necesar pentru interclasare. Optimizarea cerut se va reflecta la nivelul programului principal i const n determinarea ordinii optime n care trebuie efectuate interclasrile astfel nct timpul total s fie minim.

Greedy
Interclasarea optimal a mai multor iruri
Din teoria legat de interclasarea irurilor se cunoate faptul c timpul necesar pentru interclasarea a dou iruri ordonate, A i B, de lungimi a i resp. b, este proporional cu a+b {O(a+b)}, rezultat din faptul c sunt necesare a+b deplasri de elemente. Exemplu : L = (90, 70, 40, 10) t1 = (90+70) +(160+40) +(200+10) = 570 depl. t2 = (10+40) +(50+70) +(120+90) = 380 depl. Soluie Greedy: se interclaseaz cele mai scurte dou iruri, rezultnd n-1 iruri; se interclaseaz apoi cele mai scurte dou iruri dintre cele rmase . a. m. d.

Greedy
Problema comis-voiajorului (TSP)
TSP - Traveling Salesman Problem Se consider n puncte distincte. Se furnizeaz ca date de intrare distanele dintre aceste puncte. Se cere s se determine un drum care s porneasc dintr-un punct oarecare, s treac o singur dat prin toate celelalte i s se ntoarc n punctul iniial, astfel nct lungimea drumului s fie minim. Toi algoritmii care conduc la soluia optim n aceast problem necesit timp de calcul exponenial {O(2n)}. Un algoritm care utilizeaz metoda Greedy (neoptimal) necesit pentru determinarea soluiei un timp polinomial {O(n2)}. Ideea pentru Greedy: se adaug ntotdeauna la drum acel punct pentru care distana fa de punctul adugat anterior este minim i nu este nc legat. Dup ce s-au cuprins n drum toate punctele se face legtura ntre ultimul punct i primul.

Greedy
Problema comis-voiajorului (TSP)
Exemplu :

7
4

2 5

1
2

1
3

Greedy
Problema comis-voiajorului (TSP)

7
4

2 5

1
2

1
3
nod de plecare : 1 lungime drum = 13

Greedy
Problema comis-voiajorului (TSP)

7
4

2 5

1
2

1
3
nod de plecare : 3 lungime drum = 9

Greedy
Problema comis-voiajorului (TSP)
Distanele ntre orae le memorm ntr-un tablou bidimensional d. Distana ntre oraele (i,j) va fi reprezentat de elementul di,j al matricii. n termeni Greedy, mulimea iniial A este mulimea tuturor perechilor de orae. Mulimea B care trebuie gsit va conine o parte din aceste perechi de orae, i anume acele perechi care nlnuite s formeze un drum ce trece prin toate oraele.

Greedy
Problema comis-voiajorului (TSP)
n implementare nu vom lucra cu mulimea A sub aceast form explicit de perechi de orae, ci vom folosi matricea distanelor d. De asemenea drumul comis-voiajorului nu l vom pstra sub form de perechi de orae, ci sub forma unui sir al oraelor, numit drum. Care va conine indicii oraelor parcurse, n ordinea parcurgerii. Pentru a ti care orae au fost parcurse, facem o marcare logic a oraelor folosind un tablou unidimensional vizitat. Elementele din acest tablou care au valoarea 1 reprezint orae vizitate.

Greedy
Problema comis-voiajorului (TSP)
#include <stdio.h> #define N_MAX 30 /* Numarul maxim de orase. */ #define MINIM 10000 /* Constanta care se foloseste ca valoare de initializare la cautarea minimului. */ int n; /* Numarul de orase. */ int d[N_MAX][N_MAX]; /* Matricea distantelor dintre orase. */ int drum[N_MAX]; /* Drumul comis voiajorului. Contine indicii oraselor in ordinea in care sunt ele parcurse. */ int vizitat[N_MAX]; /* Vector care memoreaza ce orase au fost vizitate. */

Greedy
Problema comis-voiajorului (TSP)
/*Functia alege urmatorul urmatorul oras care sa fie vizitat. */ void alege(int ultimul, int *min, int *j_min) { int j; /* Cautam drumul minim pana la orasele neparcurse inca. */ *min = MINIM; *j_min = -1; for (j=0; j<n; j++) if (!vizitat[ j ]) { if (d[ ultimul ][ j ] < *min) { *min = d[ ultimul ][ j ]; *j_min = j;} } }

Greedy
Problema comis-voiajorului (TSP)
int main(void) { FILE *fin; int i, j, count, cost, min, j_min; fin = fopen("comis.in", "rt"); if (!fin) { printf("Eroare: nu pot deschide fisierul.\n"); return -1; } fscanf(fin, "%d", &n); /* Citim datele din fisier. */ for (i=0; i<n; i++) for (j=0; j<n; j++) fscanf( fin, "%d", &(d[ i ][ j ]));

Greedy
Problema comis-voiajorului (TSP)
printf("Avem %d orase.\n", n); printf("Distantele dintre orase sunt:\n"); for (i=0; i<n; i++) { for (j=0; j<n; j++) printf("%d ", d[ i ][ j ]); printf("\n"); } printf("\n"); for (i=0; i<n; i++) vizitat[i] = 0; /* Initial nici un oras nu este vizitat. */ /* Primul oras vizitat este cel cu numarul "0". */ drum[0] = 0; vizitat[0] = 1; count = 1; cost = 0;

Greedy
Problema comis-voiajorului (TSP)
/* Parcurgem restul de n-1 orase. */ for (i=0; i<n-1; i++) { /* Alegem urmatorul oras care sa fie vizitat. */ alege(drum[count-1], &min, &j_min); printf("Am ales drumul (%d, %d) de cost %d.\n", drum[count-1], j_min, min); drum[count] = j_min; vizitat [j_min ] = 1; count++; cost += min; }

Greedy
Problema comis-voiajorului (TSP)
/* Parcurgem drumul de la ultimul oras vizitat catre primul oras si actualizam costul total. */ cost += d[drum[n-1]][0]; /* Afisam drumul parcurs. */ printf("\nDrumul are costul %d si este:\n", cost); for (i=0; i<n; i++) printf("%d ", drum[i]); printf("0\n"); return 0; }

Greedy
Problema comis-voiajorului (TSP)
Rezolvarea problemei comis-voiajorului, prin metoda prezentat, poate fi mbuntit dac se repet algoritmul dat i pentru alte puncte iniiale (eventual pentru toate n), reinndu-se varianta de drum cu lungime minim. Mai mult, dac pentru un anumit punct iniial se ajunge n situaia ca lungimea drumului s depeasc lungimea minim de pn atunci, se poate abandona tratarea acelui lan, trecndu-se la alt punct iniial. Astfel, timpul cerut de aceast nou variant este cel mult O(n3). Metoda prezentat pentru rezolvarea problemei comis-voiajorului se ncadreaz n categoria algoritmilor euristici. Un algoritm este euristic dac furnizeaz soluii suficient de bune, dar nu neaprat optime, ntr-un timp rezonabil, mai mic dect cel necesar oricrui algoritm exact, cunoscut.

Greedy
Problema conectrii oraelor cu cost minim
Se dau un numr de n orae. Pentru diferite perechi de orae (i, j) i=1, 2, ... n i j=1, 2, ... n, nu neaprat pentru toate perechile posibile, se furnizeaz, ca date de intrare, costurile legrii lor directe (lungimea drumului, lungimea sau costul liniei telefonice de legtur, durata drumului dintre cele dou orae etc.). Se cere s se construiasc o reea prin care fiecare ora s fie legat cu toate celelalte direct sau indirect, astfel nct costul legturilor s fie minim. Se poate demonstra c soluia cerut este un arbore (eliminnd ciclurile se pstreaz condiiile problemei i costul este, evident, mai mic). Problema se mai numete i problema arborelui parial de cost minim (Minimum Spanning Tree) i are un grad mare de generalitate : orice problem n care soluia este un arbore i funcia de cost este ataat tranziiilor (muchiilor) n arbore.

Greedy
Problema conectrii oraelor cu cost minim
O soluie pentru construirea arborelui parial de cost minim, care se bazeaz pe metoda Greedy, este algoritmul lui Prim. Ideea algoritmului lui Prim : se pornete dintr-un ora oarecare sau de la muchia de cost minim care leag, de exemplu, cele mai apropiate dou orae. Apoi, se adaug n mod repetat, muchia de cost minim dintre cele rmase (nealese anterior) i care nu formeaz cu precedentele un ciclu, adic se adaug la reea acel ora, dintre cele nelegate, situat la cea mai mic distan fa de unul (oarecare) dintre oraele legate anterior. Se poate demonstra c arborele construit prin aceast strategie este ntr-adevr de cost minim.

Greedy
Problema conectrii oraelor cu cost minim
S considerm spre exemplu o reea de 7 orae numerotate de la 0 la 6. Costurile de conectare a oraelor sunt redate n figur.

Arborele minim este redat cu linii ngroate. El a fost construit pas cu pas, conform procedeului descris mai sus. Iniial arborele a fost vid. La primul pas s-a adugat un nod arbitrar, i anume nodul 0.

Metoda Divide and Conquer


Explicarea numelui
Numele acestei metode de rezolvare a problemelor de programare provine din dictonul latin divide et impera. Semnificaia este urmtoarea: atunci cnd avem de rezolvat o problem pe care, din diverse motive, o considerm dificil, o mprim n mai multe subprobleme care s se poat rezolva mai uor. Dup rezolvarea subproblemelor vom combina soluiile lor pentru a obine soluia ntregii probleme. Metoda de rezolvare Divide and Conquer se poate aplica la acele probleme care se pot descompune n subprobleme de aceeai natur cu problema principal, dar de dimensiuni mai mici. Se poate pune ntrebarea : Cum rezolvm subproblemele?. Rspunsul este: n acelai mod n care am rezolvat problema principal. => Metoda Divide and Conquer se preteaz foarte bine la implementri recursive.

Metoda Divide and Conquer


Tehnica divizrii - principii
Este o metod fundamental de proiectare a algoritmilor care poate s conduc la soluii deosebit de eficiente. Principiul de baz al acestei tehnici este acela de a descompune n mod repetat o problem complex n dou sau mai multe subprobleme de acelai tip, urmat de combinarea soluiilor acestor subprobleme pentru a obine soluia problemei iniiale. ntruct subproblemele rezultate din descompunere sunt de acelai tip cu problema iniial, metoda se exprim n mod natural printr-o funcie recursiv. Apelul recursiv se continu pn n momentul n care subproblemele devin banale i soluiile lor evidente.

Metoda Divide and Conquer


Funcia recursiv - pseudocod
void Divide(* parametri care definesc o problema) { if (* problema este una triviala) { * rezolva problema in mod direct; * returneaza rezultatele;} else{ * imparte problema in subprobleme; for( fiecare subproblema ) * apeleaza Divide(subproblema); * combina rezultatele subproblemelor; * returneaza rezultatele pentru problema; } }

Metoda Divide and Conquer


Determinarea recursiv a elementelor min i max Se d un ir de n numere reale {x0, x1, ..., xn-1}. S se determine valoarea minim i valoarea maxim din acest ir de numere. Putem aplica tehnica Divide and Conquer, mprind irul de numere n dou pri. Determinm minimul i maximul pentru fiecare din cele dou pri, iar pe urm determinm maximul global prin compararea celor dou maxime pariale i minimul global prin compararea celor dou minime pariale. Pentru implementare vom defini o funcie recursiv care va cuta minimul i maximul ntr-o secven a irului. Iniial vom apela aceast funcie pentru ntregul ir. Funcia se va apela pe ea nsi recursiv, pentru jumtatea stng i pentru jumtatea dreapt a secvenei .

Metoda Divide and Conquer


Determinarea recursiv a elementelor min i max #include <stdio.h> /* Declaram sirul de numere direct in cod. Alternativ, el poate fi citit de la tastatura sau din fisier. */ #define N 10 int x[ ] = {10, 5, 23, -11, 4, 2, 0, -6, 66, 40}; int comp = 0; /*Numara cate comparatii se fac in total. */ /* Functie care determina minimul si maximul dintr-o secventa a sirului de numere. Secventa este delimitata de indicii "st" si "dr". Valorile minima si maxima gasite vor fi returnate prin pointerii "min" si respectiv "max" primiti ca si parametri. */

Metoda Divide and Conquer


Determinarea recursiv a elementelor min i max void minmax(int st, int dr, int *min, int *max) { int mijloc, min_st, max_st, min_dr, max_dr; printf("Caut in secventa [%d..%d].\n", st, dr); /* Daca secventa contine un singur numar, atunci el este atat minim cat si maxim. */ if (st == dr) { *min = x[ st ]; *max = x[ st ]; } /* Daca secventa contine doua numere, atunci facem o comparatie pentru a gasi minimul si maximul. */

Metoda Divide and Conquer


Determinarea recursiv a elementelor min i max
else if (st == dr - 1) { comp++; if (x[ st ] < x[ dr ]){ *min = x[ st ]; *max = x[ dr ]; } else { *min = x[ dr ]; *max = x[ st ]; } } /* Daca avem mai multe numere, atunci divizam problema in subprobleme. */ else { /* Divizare. */ mijloc = (st + dr) / 2; minmax(st, mijloc, &min_st, &max_st); minmax(mijloc+1, dr, &min_dr, &max_dr);

Metoda Divide and Conquer


Determinarea recursiv a elementelor min i max
/* Combinarea rezultatelor partiale. Comparam minimele partiale intre ele si maximele partiale intre ele. */ comp++; if (min_st < min_dr) *min = min_st; else *min = min_dr; comp++; if (max_st > max_dr) *max = max_st; else *max = max_dr; } }

Metoda Divide and Conquer


Determinarea recursiv a elementelor min i max
int main(void) { int i, min, max; printf("Avem %d numere.\n", N); for (i=0; i<N; i++) /* Afisam sirul de numere. */ printf("%d ", x[i]); printf("\n\n"); minmax(0, N-1, &min, &max);/* Apelam functia recursiva.*/ printf("\nMinimul este %d.\n", min); printf("Maximul este %d.\n", max); printf("Comparatii facute: %d.\n", comp); return 0; }

Metoda Divide and Conquer


Problema Turnurilor din Hanoi
Se dau trei tije notate cu a, b, c. Pe tija a sunt ordonate discuri n ordine cresctoare. S se realizeze programul C care mut toate cele n discuri de pe tija a pe tija b folosind tija c ca auxiliar, astfel nct, n final, discurile s fie ordonate tot cresctor. n timpul operaiilor de mutare este interzis plasarea unui disc mai mare peste unul mai mic. Problema se poate codifica prin urmtorul cvartet : (n,a,b,c). Dac am gsi o modalitate de a muta n-1 discuri de pe tija a pe tija intermediar c, atunci am putea s mutm discul cel mai mare de pe tija a pe tija b. Pe urm ar trebui s aducem cele n-1 discuri de pe tija c pe tija b i problema ar fi rezolvat. Pentru a muta n-1 discuri de pe tija a pe tija c, putem folosi ca tij intermediar tija b. La fel, pentru a muta napoi cele n-1 discuri de pe tija c pe tija b, putem folosi ca tij intermediar, tija a. Putem reformula cele de mai sus n felul urmtor: problema (n,a,b,c) se rezum la problema (n-1,a,c,b), urmat de mutarea discului de diametru maxim de pe a pe b, urmat de problema (n-1,c,b,a).

Metoda Divide and Conquer


Problema Turnurilor din Hanoi
void hanoi (int n, char t_initial, char t_final, char t_intermediar) { /* Daca avem mai mult de un disc de mutat, atunci descompunem problema in subprobleme. */ if (n > 1) { hanoi(n-1, t_initial, t_intermediar, t_final); printf("%c -> %c\n", t_initial, t_final); hanoi(n-1, t_intermediar, t_final, t_initial); } /* Daca avem un singur disc de mutat, atunci il mutam direct. La acest nivel problema are o rezolvare triviala. */ else { printf("%c -> %c\n", t_initial, t_final); }}

Metoda Backtracking
Algoritmi cu revenire explicarea numelui
Pentru a nelege semnificaia cuvntului backtracking vom reda definiia din Cambridge Online Dictionary, http://dictionary.cambridge.org/ : to go back along a path you have just followed Ideea de baz este aceea de revenire pe calea parcurs. Algoritmii de tip backtracking exploreaz spaiul soluiilor n mod exhaustiv, pe toate cile posibile. Atunci cnd pe calea curent de explorare se constat c nu mai sunt anse s se ajung la o soluie valid, se revine cu un pas napoi i se abordeaz o alt cale de explorare. n concluzie, metoda Backtracking const n efectuarea unor ncercri repetate, n vederea gsirii soluiilor, cu posibilitatea revenirii n caz de eec.

Metoda Backtracking
Algoritmi cu revenire - principii
Soluia se poate reprezenta sub forma unui vector X=(x0,x1, , xn-1), XS = S0 xS1x x Sn-1, unde mulimile S0, , Sn-1 sunt mulimi finite. Pentru fiecare problem concret sunt date anumite relaii ntre componentele x0,x1, , xn-1 ale vectorului X, numite condiii interne. Mulimea S reprezint spaiul soluiilor posibile. Soluiile posibile care satisfac condiiile interne se numesc soluii rezultat. n continuare, exprimm condiiile care trebuie satisfcute sub forma unei funcii logice notat: 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.

Metoda Backtracking
Algoritmi cu revenire - principii
Scopul algoritmului concret poate s fie determinarea unei soluii rezultat sau a tuturor soluiilor rezultat, fie n scopul afirii lor, fie pentru a alege una optim din punctul de vedere al unor criterii de optimizare (minimizare sau maximizare). O metod simpl de selectare a soluiilor rezultat este aceea de a genera toate soluiile posibile i de a verifica satisfacerea condiiilor interne (cutare exhaustiv). Aceast metod necesit ns un timp de execuie foarte mare i nu se aplic dect rar n practic.

Metoda Backtracking
Algoritmi cu revenire - principii
Un algoritm backtracking performant, ca i n cazul Greedy de altfel, evit generarea tuturor soluiilor posibile. n acest scop, elementele vectorului X primesc, pe rnd i n ordine, valori. Dup asocierea unei valori lui xk se verific ndeplinirea unor condiii de continuare pentru secvena (x0, , xk) i numai apoi se trece la ncercarea de asociere a unei valori pentru xk+1: funcia Continuare(x0, x1, ..., xk) . Nendeplinirea acestor condiii ne arat, nc din aceast faz, c soluia final nu poate fi o soluie rezultat. n cazul unui eec la aceast verificare, se alege o alt valoare pentru xk Sk sau, dac Sk a fost epuizat, se micoreaz k cu o unitate i procesul de alegere se repet.

Metoda Backtracking
Procedura general - pseudocod
k = 0; while (k >= 0){ do { * alege urmatorul x[k] din multimea S[k]; * evalueaza Continuare(x[0], x[1], ..., x[k]); } while ( !Continuare(x[0], x[1], ..., x[k]) && (* mai sunt elemente de ales din multimea S[k]) ); if (Continuare(x[0], x[1], ..., x[k])) { if (k == n-1) { if (Solutie(x[0], x[1], ..., x[n-1])) * afiseaza solutie; } else { k = k + 1; } } else { k = k 1; } }

Metoda Backtracking
Algoritmi cu revenire - caracteristici
n concluzie: Numele metodei (algoritmi cu revenire) provine de la revenirile care se efectueaz n caz de eec. Condiiile de continuare deriv din condiiile interne. Alegerea optim a condiiilor de continuare poate determina reducerea numrului de calcule (ncercri) care urmeaz s fie efectuate (viteza programului). Acest proces de ncercri repetate i revenire n caz de eec (cu reluarea altei valori i continuare) se exprim n mod natural i n manier recursiv.

Metoda Backtracking
Soluia recursiv
Se consider c numrul elementelor care se pot examina la fiecare apel (cardinalitatea multimilor Sk) n vederea adugrii lor la soluie, este fix (m), iar numrul componentelor soluiei este de asemenea fix (n) : void Incearca (int k) { int i; for(i=0; i<m; i++) { * selecteaz elementul nr.i; if (* este acceptabil) { * nregistreaza elementul selectat; if (k<n-1) Incearca (k+1); else * tiparete soluie; * terge nregistrarea;} } }

Metoda Backtracking
Problema celor 8 regine (Eight Queens)
Se cere s se realizeze programul care s plaseze opt regine pe o tabl de ah, astfel nct nici una dintre ele s nu le amenine pe celelalte. La jocul de ah, o regin amenin pe linii, coloane i diagonale, pe orice distan. Aceast problem a fost investigat de Carl Friedrich Gauss n 1850 (care ns nu a rezolvat-o complet). Nici pn n prezent problema nu are o soluie analitic satisfctoare. n schimb ea poate fi rezolvat prin ncercri, necesitnd o mare cantitate de munc, rbdare i acuratee (condiii n care calculatorul se descurc excelent). Problema are 92 de soluii din care, din motive de simetrie, doar 12 sunt diferite. Problema poate fi uor extins pentru n regine plasate pe o tabl ptrat cu n linii i n coloane.

Metoda Backtracking
8 regine rezolvare
Pe fiecare linie sau coloan de pe tabl se va afla o singur regin. Se va parcurge tabla de ah linie cu linie (k=0..7), iar n cadrul unei linii coloan cu coloan (i=0..7) i se vor plasa reginele n acele ptrate care nu sunt n priza reginelor plasate anterior. Pentru parcurgerea tablei se va utiliza tehnica backtracking. Deoarece pe fiecare linie a tablei de ah se poate gsi exact o regin, o soluie rezultat se poate reprezenta sub forma unui vector C = (c0,..., c7) unde ck reprezint coloana pe care se afl regina de pe linia k (ck{0, ,7}). Spaiul soluiilor posibile este produsul cartezian S = C C C C C C C C. Condiiile interne, rezult din regulile ahului i sunt reprezentate de faptul c dou dame nu se pot afla pe o aceeai coloan sau o aceeai diagonal.

Metoda Backtracking
8 regine organizarea tablei de ah
Tabla de ah:
0 0 1 1

i
2 3 4 5 6 7

2 3 4 5 6 7

Metoda Backtracking
8 regine condiiile interne
Funcia Solutie trebuie s verifice dac nu exist regine care se afl pe aceeai coloan sau dac nu cumva exist regine care se atac pe diagonal. Verificarea este simpl. Trebuie s verificm c ntre elementele (c0, c1, c2, c3, c4, c5, c6, c7) nu exist dou care au aceeai valoare. Aceasta ar nsemna c avem dou regine pe aceeai coloan. Apoi mai trebuie s verificm c i,k {0, 1, 2, 3, 4, 5, 6, 7}, |i-k| |ci-ck|. Aceasta este condiia ca s nu existe dou regine care se atac pe diagonal. Verificri similare vor fi efectuate pe parcurs de funcia Continuare.

8 Regine
#include <stdio.h> #include <conio.h> #include <stdlib.h> #define N 8 #define INVALID -1 int main(void) { int c[N]; int k, i; int continuare, ataca; int count = 0; // Numaram si cate solutii sunt gasite for (i=0; i<N; i++) c[i] = INVALID; //Initializam toate elem. din vectorul c

Implementarea programului

8 Regine
Implementarea programului
k = 0; while (k >= 0) { /* Ne vom opri atunci cand am epuizat elem. din multimea C[k] sau atunci cand intalnim un element pentru care functia Continuare returneaza true. */ do { if (c[k] == INVALID) // nu s-a incercat plasarea reginei de pe linia k c[k] = 0; // plasam regina de pe linia "k" pe coloana 0 else // incercam sa plasam regina pe urmatoarea col. c[k]++; /* Daca nu am depasit numarul de coloane de pe linie, atunci trecem la evaluarea functiei Continuare. */

8 Regine
Implementarea programului
if (c[k] < N) { /* Ne asiguram ca regina pe care vrem sa o plasam pe linia "k" si coloana "c[k]" nu ataca nici una din reginele deja plasate*/ ataca = 0; for (i=0; !ataca && (i<k); i++) { /* Verificam daca mai exista o regina pe aceeasi coloana*/ if (c[i] == c[k]) ataca = 1; /*Verificam daca noua regina ataca alta, pe diagonala*/ else if (abs(i-k) == abs(c[i]-c[k])) ataca = 1; }

8 Regine
Implementarea programului
/* Daca noua regina nu ataca nici una din reginele plasate anterior, atunci functia Continuare returneaza true(1), altfel returneaza false (0) */ if (!ataca) continuare = 1; else continuare = 0; } /* Daca s-a depasit numarul coloanelor de pe o linie, functia Continuare returneaza automat false. */ else continuare = 0; } while (!continuare && (c[k] < N));

8 Regine
Implementarea programului
/* Daca am obtinut true (1), din functia Continuare, atunci consideram regina plasata si continuam algoritmul*/ if (continuare) { /* Daca am plasat toate N reginele, atunci afisam solutia gasita. */ if (k == N-1) { for (i=0; i<N; i++) printf("%d ", c[i]); printf("\n"); count++; } else k++; }

8 Regine
Implementarea programului
/* S-au epuizat toate variantele de plasare a reginei de pe linia k. Marcam cu INVALID elementul c[k] si revenim cu un pas inapoi pe calea de cautare, la regina k-1. */ else { c[k] = INVALID; k--; } } printf("%d solutii\n", count); return 0; }

Metoda Backtracking
8 regine algoritmul recursiv
void Incearca(int k){ int i= -1; do { i++; Succes =regina poate fi pusa in pozitia (k,i) if (Succes){ Ocupa; /*pune regina*/ C[k] = i; /*memoreaza coloana*/ if (k<7) Incearca(k+1); else Tipareste; Elibereaza; /*ia regina*/ }} while (i<7); }

Metoda Backtracking
Circuitului calului (Knights Tour)
Fiind dat o tabl ptrat cu n X n elemente, se cere s se realizeze programul C pentru gsirea traseului care trece prin toate elementele tablei, ncepnd cu cel de coordonate (i,j), utiliznd sritura calului dat de regulile ahului. Prin fiecare element se va trece o singur dat. Operaia esenial pe care o are de rezolvat algoritmul este executarea unei micri urmtoare sau constatarea c nu mai este posibil o astfel de micare i revenirea la micarea anterioar. Caracteristica esenial a acestui algoritm este aceea c nainteaz spre soluia final pas cu pas, ncercnd i nregistrnd drumul parcurs. Dac la un moment dat se observ c drumul ales nu conduce la soluia dorit i se blocheaz, se revine tergnd nregistrrile pailor pn la proximul punct care permite o nou alternativ de drum.

Metoda Backtracking
Circuitului calului structurile de date Pentru o tabl de dimensiune N vom memora soluiile ntr-un vector, c, de lungime N*N. Fiecare element din vector va fi la rndul lui un vector cu dou elemente, primul element va memora linia de pe tabl, iar al doilea element va memora coloana de pe tabl: int c[N*N][2]; count numrul de soluii gsite

Metoda Backtracking
Circuitului calului sritura
Noile coordonate, pentru o sritur, se calculeaz din cele curente adunnd nite valori fixe 1, 2, nregistrate n tablourile dx i dy, i care definesc cele opt micri viitoare (k=0..7) posibile dintr-un punct oarecare (x,y) :
6 7 5 4 C 0 1 2 3

u = x + dx[k] v = y + dy[k]

Metoda Backtracking
Circuitului calului programul
#include <stdio.h> #include <conio.h> #define N 5 int dx[8] = {-1, -2, -2, -1, 1, 2, 2, 1}; int dy[8] = {-2, -1, 1, 2, 2, 1, -1, -2}; int c[N*N][2]; int count = 0;

Metoda Backtracking
Circuitului calului programul

void Incearca(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++; }

Metoda Backtracking
Circuitului calului programul
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) Incearca(pas+1); } } }

Metoda Backtracking
Circuitului calului programul
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; Incearca(1); } printf("%d solutii\n", count); return 0; }

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