Comunicarea prin variabile globale. Efecte colaterale. Proceduri recursive. Variabile dinamice. Tipul referin. Structuri de date. Liste unidirecionale. Prelucrarea lor. Stiva. Arbori binari. Parcurgerea arborilor binari. Analiza algoritmilor. Iterativitate sau recursivitate. Metoda trierii (tehnica Greedy). Metoda relurii (tehnica backtracking). Metoda desparte i stpnete (tehnica divide et impera). Programarea modular. Testarea i depanarea programelor. Metodele de realizare a programelor mari. 1. 2.
lit 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 4 3 1 1 1
pag 144 144 158 161 163 167 176 176 180 181 197 206 213 129 54 139 123 97 238 245 249
Gremalschi A., Mocanu Iu., Spinei Ion. Informatica. Limbajul de programare PASCAL. Manual pentru clasele IX-XI., tiina, Chiinu, 2000 Cerchez Emanuela, erban Marinel. Informatica. Manual pentru clasa a X-a. Filiera teoretic, profilul matematic-informatic. Iai: Editura POLIROM, 2000. 199 p. Sorin T. Tehnici de programare. Bucureti Editura Teora. 1996.
3.
Ne propunem s lmurim modul n care se estimeaz timpul de calcul necesar unui program pentru a fumiza rezultatul. S considerm una din cele mai simple probleme. Se d un vector cu n componente. Se cere s se calculeze maximul dintre componentele sale. Reamintim, pe scurt, algoritmul: o variabil (numit MAX) va lua valoarea primei componente; fiecare din cele n-1 componente rmase, se compar pe rnd cu valoarea variabilei MAX, iar n cazul n care coninutul este mai mare dect al variabilei MAX, valabilei MAX i se atribuie valoarea acelei componente. Timpul de calcul depinde, n primul rnd, de lungimea (mrimea) datelor de intrare, pe care o vom nota w n. Exemple: => pentru aflarea maximului (minimului) n reprezint numrul de componente al vectorului; => pentru sortare, n reprezint numrul valorilor care se sorteaz; => pentru generarea permutrilor, n reprezint numrul de elemente ale mulimii pentru care se genereaz permutrile; => pentru testul unui numr natural dac este prim (sau nu) n este chiar numrul; => pentru problema colorrii hrilor, n este numrul arilor. Pentru a calcula timpul de calcul ar trebui sa inventariem toate instruciunile programului i s tim de cte ori se execut fiecare din ele (n funcie de n). Mai mult, ar trebui s cunoatem ct dureaz execuia fiecrui tip de instruciune. Observaie 1. => nu ntotdeauna putem ti de cte ori se execut o instruciune. chiar dac cunoatem valoarea lui n. Exemplu. Pentru calculul maximului, nu putem ti de cte ori se execut atribuirea max:=v[i] . Cu toate acestea, putem considera ca exist o proporionalitate ntre valoarea n i numrul de execuii. Observaie 2. => Viteza de execuie a unei instruciuni este dependent de calculatorul utilizat. Datorit acestor considerente vom proceda altfel. Se alege o operaie numit operaie de baz, i se vede de cte ori se execut aceasta. Cerina pentru operaia de baz este ca aceasta s se execute de un numr de ori, numr care se poate calcula de la nceput pornind de la n. Exemple. => Pentru problema aflrii maximului, operaia de baza va fi comparaia (fiind dat n se fac n-1 comparaii pentru calculul maximului). => Pentru generarea permutrilor, alegerea operaiei de baz este greu de fcut. De exemplu, dac aceasta este comparaia, este greu de calculat numrul comparaiilor efectuate pentru a genera toate permutrile. Din acest motiv putem considera ca operaie de baz generarea unei permutrii. Vom avea astfel n! operaii de baz (un numr foarte mare). Pentru generarea permutrilor exist foarte muli algoritmi. Alegerea ca operaie de baz a comparaiei permite ca acetia sa fie comparai ntre ei, dar alegerea ca operaie de baz a generrii unei permutri nu permite aceasta. n astfel de cazuri (cnd avem de ales ntre mai multe operaii de baz), vom alege acea operaie care corespunde ct mai bine scopului propus. Astfel, dac analizm un algoritm de generare a permutrilor, comparativ cu altele de aceeai natur vom alege ca operaie de baz comparaia. n cazul n care analizm un algoritm oarecare, n care soluia este sub form de permutare, putem considera ca operaie de baz permutarea. Problema se pune n felul urmtor: daca cutm soluia printre toate permutrile pe care se pot genera vom avea n! cutri (un numr imens), oare nu se poate altfel? Exemple de probleme la care se folosete un astfel de raionament: problema comis voiajorului, problema celor n dame. Timpul estimat de calcul se trece sub form aproximativ astfel: O (numr estimat de execuii ale operaiei de baz). Exemplu de exprimare a timpului. 2
Dac un algoritm efectueaz 3n2+7n+5 operaii de baz, vom spune c acesta este un algoritm cu O(n2). Exemple. => Pentru aflarea maximului dintre n valori timpul estimat de calcul este O(n). n cazul de fa, operaia aleas este cea de comparare. Pentru un vector cu n componente se fac n-1 comparaii. Aceasta nseamn c dac vectorul are 100 de componente se fac 99 de comparaii .a.m.d.. Se poate demonstra faptul c un algoritm mai bun nu exist. => Pentru generarea permutrilor (n cazul n care se consider ca operaie de baz generarea unei permutri) timpul estimat de calcul este O(n!). n anumite cazuri nu putem preciza nici mcar numrul de execuii ale operaiei da baza. Exemplu. Sortarea prin interschimbare (vezi 6.2.1). n cazul n care vectorul este sortat se exercit n-1 comparaii (se parcurge vectorul o singur dat). Dac vectorul este sortat invers se execut n(n-1)/2 operaii de baz (se parcurge vectorul de n ori). n astfel de cazuri se poate considera: timp minim de calcul: timp mediu de calcul; timp maxim de calcul. De fiecare dat cnd se trece timpul estimat se precizeaz despre care este vorba (minim mediu, maxim). 1n practic, prezint interes timpul mediu i maxim. Dac timpul estimat este sub forma O(nk), kN, spunem c algoritmul este n timp polinomial. Exemplu. Sortarea prin interschimbare are timpul (mediu, maxim) polinomial si anume O(n2). Un algoritm n O(n) se numete algoritm liniar. Dac un algoritm are un timp estimat O(2n), O(3n) spunem c algoritmul este n timp exponenial.
Un algoritm n timp O(n!) este asimilat unui algoritm n timp exponenial. n!=1x2x...n<2x2x..x2=2n-1. n practic nu sunt admii dect algoritmii n timp polinomial, dei nu este deloc indiferent gradul polinomului. i totui pentru ce tot acest efort de estimare a timpului de calcul? Oare viteza de lucru a. unui calculator nu este att de mare, nct abstracie fcnd de cteva minute de calcul, n plus sau n minus, sa renunm la estimarea lui? Rspunsul la aceasta ntrebare este categoric negativ. Mai mult, timpul de calcul este esenial, dac este prea mare algoritmul nu are nici un fel de valoare practic. S ne imaginm un algoritm care are un timp de calcul O(2 n). Dac n este 10, calculatorul va efectua aproximativ 1024 operaii elementare. Dac n este 100 acesta va trebui s efectueze 1024*1024*..-*1024 operaii elementare, adic un numr mai mare dect 1000*1000*...*1000 adic 1000000...0000 (de treizeci de ori). Acesta este un numr imens. Dar dac n este 1000? Pentru un n nu foarte mare, nici cel mai modern calculator din lume nu scoate rezultatul dect n sute sau mii de ani). De altfel ce nseamn n asemenea cazuri un calculator modern? Sa presupunem c un sistem PENTIUM are p vitez de circa 25 de ori mai mare dect a unul sistem XT. S presupunem c avem un program care are un timp de calcul de ordinul O(2 n). Se cere s rezolvm o problem pentru n=30. Fie t timpul necesar rulrii acestei probleme pe un XT. Aceeai problem va fi rulat pe PENTIUM n timpul t/25. Dorim s rezolvm problema pe PENTIUM pentru n=35 (n a crescut numai cu 5). Dar 2 35=230*25=32*230. Deci dac n a crescut cu 5, vom avea nevoie de mai mult timp de lucru sa rezolvm problema pe PENTIUM dect timpul pentru rezolvarea ei pentru n=30 pe XT. V dai seama ce nseamn un timp de calcul de ordinul O(2 n)? Sporul lui n cu o singur unitate duce la dublarea timpului de calcul. Ce concluzie tragem de aici? De cte ori avem de rezolvat o problem cutm pentru aceasta un algoritm n timp polinomial (polinomul va avea gradul minim). Mai mult, cutm un algoritm care s rezolve problema n timp polinomial de grad minim. 3
Cum estimm timpul de calcul n astfel de situaii? Considerm ca operaie de baz comparaia (a dou resturi). Revenim la problem. Dispunem de n resturi. Cutm un rest nul. Pentru aceasta se fac cel mult n comparaii. Presupunem c nici un rest nu este nul. Urmeaz s identificm dou resturi egale. Pentru aceasta se compar primul rest cu resturile 2 ... n (n-1 comparaii), al doilea cu resturile 3 ... n (n-2 comparaii), ... penultimul rest cu ultimul (o singura comparaie). n concluzie, se efectueaz cel mult n(n-1)/2 comparaii, n total s-au efectuat n+n(n+1)/2 comparaii. Avem un algoritm cu O(n 2) timp maxim de calcul. Este recomandabil s realizai acest program n ambele variante i s comparai timpul de calcul pentru n=30 ( o valoare mic pentru n).
S presupunem c fiecare nivel al stivei ia valori ntre 1 si n. S presupunem, de asemenea, c stiva are n nivele. O explorare sistematic presupune a investiga n n posibiliti. Mecanismul tehnicii evit (ct poate) investigarea tuturor soluiilor. Dar de aici nu se poate trage concluzia ca timpul de calcul mediu nu este exponenial. Dei este dificil de demonstrat timpul de calcul necesar tehnicii Backtracking este asimilat timpului exponenial. n cazul n care problema nu are soluie, se exploreaz toate posibilitile, caz n care se ajunge la un timp de calcul exponenial. Nu este exclus ca prin aceasta tehnic s se ajung imediat la soluie, dar pentru aceasta trebuie s avem "ans. Oricum, timpul maxim cerut de aceasta tehnic este exponenial. Din acest motiv, se va evita folosirea ei n rezolvarea problemelor. n situaia n care se cere soluia optim la o problem pentru care nu se cunosc algoritmi polinomiali, i nu se renun sub o nici o form la optimalitate (n favoarea unei soluii suficient de bune) putem folosi tehnica Backtracking, altfel aceasta nu se va folosi.
Acest arbore are k niveluri (k=log2(n)). Timpul de calcul va fi: T(8)=2T(4)+b8=2(2T(2)+b4)+b8=4T(2)+2b4+b8=4(2T(1)+b2)+2b4+b8 =8T(1)+4b2+2b4+b8=8a+4b2+2b4+b8. Dup cum am vzut, bk reprezint timpul unei interclasri n care vectorul rezultat are k componente. n acest caz, aproximnd superior, putem consider bk=k. Rezult: T(8)=8a+8+8+8=8a+8*3=8a+8log2(8). Generaliznd problema (propunem demonstraie ca exerciiu) vom avea: T(n)=na+nxlog2(n). Rezult c algoritmul de sortare prin interclasare necesit un timp de calcul O(n xlog2(n)). La algebr am nvat c log2(n)<n, aceast inegalitate se demonstreaz prin inducie. n cadrul acestui capitol s-a artat faptul c, sortarea prin interschimbare (metoda bulelor) necesit un timp de calcul O(n2). innd cont de ultima inegalitate, rezult ca algoritmul de sortare prin interclasare este mai bun. Mai mult, se demonstreaz i faptul c timpul cerut de acest algoritm este cel mai mic timp necesar unei operaii de sortare care au acelai timp estimat de calcul. Pn acum am prezentat modul n care se estimeaz timpul de calcul al unui algoritm elaborat cu ajutorul tehnicii ntr-un caz simplu. De multe ori, problemele nu se descompun n alte dou probleme cu aceeai "lungime" ca aici, ci n trei, patru .a.m.d. n acest caz nu dispunem de un mecanism general de estimare a timpului, se studiaz fiecare caz n parte.
DIVIDE ET IMPERA,
Concluzii
Estimarea timpului necesar unui algoritm nu este simplu de realizat. S ne gndim la faptul ca i datele de intrare pot aprea cu o anumit probabilitate. Cum pentru un set de date se poate obine, un anumit timp (de multe ori inferior timpului estimat), rezult c timpul estimat trebuie judecat si prin prisma TEORIEI PROBABILITILOR i STATISTICII MATEMATICE. Toate acestea depesc cu mult nivelul de cunotine cerut n licee sau n majoritatea facultilor. Tehnicile care urmeaz s le nvm (PROGRAMARE DINAMIC i GREEDY) conduc, n general la un timp polinomial, iar tehnica Branch and Bound la unul exponenial.
Probleme propuse
1. Estimai timpul de calcul necesar aflrii valorii maxime i a celei minime dintr-un vector cu n componente numere reale (numerele nu sunt sortate). 2. Estimai timpul de calcul necesar problemei cutrii binare unui anumit numr natural ntr-un vector cu n componente, ce conin numere naturale ordonate. Comparai timpul de calcul cu metoda clasic (parcurgerea sistematic a vectorului). 3. Estimai timpul de calcul necesar generrii produsului cartezian a n mulimi finite. 4. Estimai timpul de calcul necesar pentru obinerea recursiv a lui fib(n) unde: fib(n)=fib(n-1)+fib(n-2) fib(0)=a; fib(1)=b. Comparai acest timp cu cel estimat pentru aceeai problem, n cazul rezolvrii iterative.
pentru orice i = l, n tot obinem 2 n soluii posibile. E mult? S evalum cu aproximaie timpul de execuie a unui program bazat pe un astfel de algoritm, presupunnd c dispunem de un calculator care execut un miliard de operaii pe secund (109 operaii). n 40 50 100 Timp de execuie 109 secunde 31 ore 4.1013 ani
E puin probabil s putem atepta att! Prin urmare trebuie s abordm n alt mod astfel de probleme ! 0 idee ar fi s procedm ca n multe situaii din viaa de zi cu zi. S ne gndim la modul n care un copil rezolv un puzzle. Copilul nu face toate combinaiile posibile de piese pentru ca apoi s le compare cu modelul pe care vrea s l obin. El va lua mai nti o pies. Va cuta apoi n mulimea de piese rmase una care s se potriveasc la cea pe care o are, apoi nc una .a.m.d. Dac la un moment dat se blocheaz" (nu mai gsete nici o pies n cele rmase care s se potriveasc), el nu distruge tot ce a construit ca s o ia de la nceput. Va ndeprta mai nti ultima pies pe care a pus-o i va cuta o alternativ" (o alt pies care s-ar potrivi n locul ei). Dac gsete, continu construcia cu noua pies aleas. Dac nu gsete, se mai ntoarce un pas i ndeprteaz i penultima pies pe care a pus-o, cutnd apoi o alt variant. Procedeul continu pn cnd obine modelul dorit. Observai c pe parcursul construciei, copilul face anumite verificri (se potrivete piesa aici? m poate conduce la modelul dorit?), eliminnd astfel foarte multe dintre soluiile posibile. Cu alte cuvinte, cutarea nu este exhaustiv. Un alt aspect semnificativ este faptul c se fac reveniri. Dac la un moment dat nu mai poate continua construcia, copilul revine la pasul precedent, ndeprteaz piesa utilizat i ncearc s o nlocuiasc, dac este posibil. Dac nu este posibil, face reveniri succesive, pn cnd gsete o pies pe care o poate nlocui i apoi continu construcia. Acest mod de abordare se bazeaz pe principiul ncerc aa, dac nu merge m ntorc i ncerc o alt variant ! " Fr s tie, copilul din exemplu a aplicat metoda backtracking. Numele metodei este semnificativ i s-ar putea traduce prin a o lua napoi pe urme" sau, cu aproximaie, prin cutare cu revenire" S descriem acum ntr-un mod mai general metoda backtracking. n variant elementar, considerm c soluia problemei pe care trebuie s o rezolvm se poate reprezenta ca un vector x = (x1,x2,...xn) . Fiecare component xi a vectorului poate lua valori ntr-o anumit mulime Si (i=l , 2 ,..., n). Produsul cartezian S1xS2x . . . xSn se numete spaiul soluiilor posibile. Problemele care se rezolv prin backtracking nu impun generarea tuturor soluiilor posibile (generare exhaustiv), ci doar generarea acelor soluii care ndeplinesc anumite condiii, specifice problemei, denumite condiii interne. Soluiile posibile care respect condiiile interne sunt denumite soluii rezultat. Unele probleme impun obinerea unei singure soluii rezultat, i anume cea care ndeplinete o anumit condiie de optim. Aceasta este denumit soluie optim. Pentru a evita generarea tuturor soluiilor posibile, metoda backtracking atribuie pe rnd valori elementelor vectorului x. Mai exact, componenta xk primete o valoare numai n cazul n care componentele x1,x2, . . . xk-1 au primit deja valori. n acest caz, componentei xk i se atribuie pe rnd acele valori posibile (valori din mulimea Sk) care ndeplinesc condiiile de continuare. Condiiile de continuare sunt condiii derivate din condiiile interne, care stabilesc dac pentru o anumit valoare pentru xk are sau nu sens s continum construcia soluiei. Spunem c o anumit valoare pentru xk nu ndeplinete condiiile interne dac oricum am atribui valori componentelor xk+1 , . . . , xn nu obinem o soluie rezultat (soluie care s respecte condiiile interne). Dup atribuirea unei valori posibile care respect condiiile interne componentei xi se poate continua construcia soluiei n mod recursiv (de data aceasta sunt fixate k poziii). Pentru a descrie formatul general al procedurii backtracking vom utiliza o procedur BKT. Considerm c n, numrul de componente ale vectorului, i vectorul x sunt variabile globale. procedure BKT (k: integer); {cnd apelam procedura BKT cu parametrul k presupunem ca) {poziiile 1,2,...,k-l din vectorul x sunt fixate} var MC: Mulime Elemente; a: TipElement; {tipul elementelor vectorului depinde de problema concret} begin if k-1 = n then {soluia este completa} Prelucrare_Solutie else begin 8
Candidat(MC, k); {procedura Candidat memoreaz in mulimea MC elementele} {din mulimea Sk care respecta condiiile de continuare) while MC nu este vid do begin {nu am epuizat candidaii pentru poziia k) x[k]:= Extrage(MC); {extrag un candidat din MC} BKT(k + 1) {apel recursiv} end; end end; Din descrierea general a metodei backtracking nu reiese explicit unde intervine revenirea. Pentru aceasta trebuie s ne gndim la modul de realizare a recursivitii. La fiecare apel recursiv se memoreaz pe stiv valorile variabilelor locale i valoarea parametrului. La ncheierea unui apel recursiv, se elibereaz zona de memorie alocat pe stiv i se revine la apelul precedent. Pentru a nelege mai bine modul de funcionare a acestei metode s analizm urmtoarea problem.
Problema reginelor
S se plaseze n regine pe o tabl de ah de dimensiune nxn astfel nct oricare dou regine s nu se atace. Reprezentarea informaiilor Pentru ca dou regine s nu se atace, ele nu trebuie s fie situate pe aceeai linie, pe aceeai coloan sau pe aceeai diagonal. Cum numrul de regine este egal cu dimensiunea tablei de ah, deducem c pe fiecare linie trebuie plasat o regin. Deci, pentru ca poziia reginelor s fie complet determinat, este suficient s reinem pentru fiecare linie coloana n care este plasat regina. Pentru aceasta vom utiliza un vector C, cu n componente avnd urmtoarea semnificaie: C [ i ] reprezint coloana n care este plasat regina de pe linia i. Condiii interne
1. C[i] aparine {l,2,...,n}, pentru I aparine {l,2...,n} (elementele vectorului C sunt indici de linii) 2.C[i] diferit C[j], i diferit j, i, j aparine {l,2,...,n} (dou regine nu pot fi plasate pe aceeai coloan) 3. |C[i]-C[j] | diferit | i-j | , Vi diferit j, i, j aparine {l,2,...,n} (dou regine nu pot fi plasate pe aceeai diagonal).
program Regine; const NrMaxRegine = 30; type Indice = 0 .. NrMaxRegine; Solutie = array[Indice] of 0..NrMaxRegine; var C: Solutie; n: Indice; NrSol: word;
begin inc(NrSol); writeln('Solutia nr. ', NrSol); for i := 1 to n do begin for j := 1 to n do if j = C[i] then write(' * ') else write(' o '); writeln end; writeln; readln; end;
procedure Plaseaza_Regina(k: Indice); {cand apelam procedura Plaseaza__Regina cu parametrul k am plasat deja regine pe liniile 1, 2, ...,k-l} var i, j: Indice; ok: boolean; begin if k-1 = n then Afisare else {trebuie sa mai plasam regine pe liniile k,k+l,...,n} for i := 1 to n do begin ok := true; for j := 1 to k-1 do if (C[j]=i) or (abs(C[j]-i)=(k-j)) then ok := false; {regina s-ar gasi pe aceeasi coloana sau aceeasi diagonala cu o regina deja plasata} if ok then {valoarea i respecta conditiile interne} begin {determin multimea MC a candidatilor pentru pozitia k} {verific daca pot plasa regina de pe linia k in coloana i} {am obtinut o solutie}
C[k] := i;{i este un candidat, il extrag imediat} Plaseaza_Regina(k+1) ; end; end; end; 10
{program principal}
Observai c am marcat poziiile libere cu ' o', iar poziiile pe care sunt plasate regine cu ' * '.
S analizm modul n care programul genereaz aceste soluii. Pentru aceasta vom urmri execuia programului pas cu pas.
11
12
13
14
(clasele partiiei sunt nevide) (clasele partiiei sunt disjuncte) (reuniunea claselor este egal cu ntreaga mulime). De exemplu, pentru n=3 programul va afia:
15
Reprezentarea informaiilor Vom reprezenta o partiie printr-un vector P, de dimensiune n, n care pentru fiecare element din mulimea {1,2,...,n} rein indicele clasei din care face parte. Aceast reprezentare asigur respectarea tuturor condiiilor din definiia partiiei. program Partitii; const NMax = 20; type Element = 1..NMax; Clasa = 1..NMax; Partitie = array[Element] of Clasa; var N: Element; {numarul de elemente din multime} NC: Clasa; {numarul de clase} P: Partitie; NrP: word; {numarul de partitii} procedure Afisare; var i: Element; j: Clasa; begin inc(NrP); write('Partitia ', NrP, ': '); for j := 1 to NC do begin {afisez clasa nr. j} write(' {'); for i := 1 to N do if P[i]=j then write(i, ' '); write(#8'} '); end; writeln; end; procedure GenPartitie(k: Element); {cand apelam GenPartitie(k), in vectorul P pe primele k-1 pozitii se afla o partitie a multimii 1,2,...,k-l formata din NC clase} var j: Clasa; begin if k=N+1 then Afisare {partitia este complet construita} else begin {plasam elementul k in una din clasele existente} for j := 1 to NC do begin P[k] := j; GenPartitie(k + 1); end; {sau elementul k reprezinta o clasa separata} inc(NC) ; {maresc numarul de clase} P[k] := NC; GenPartitie(k+1); dec(NC); {restaurez numarul de clase} end; end; begin {program principal} write('n= '); readln(n); GenPartitie(1); end.
Partiile unui numr natural Fie n aparine N*. Scriei un program care s afieze n fiierul de ieire ' partnr. out' toate partiiile numrului natural n. Definiie Numim partiie a unui numr natural nenul n un sistem de numere naturale nenule {p1,p2, . . . ,pk} astfel nct p1+p2+. . . +pk=n De exemplu, pentru n=5 programul va afia: 5=1 +1+1+1+1 5=1 +1+1+2 5=1 +1+3 5=1 +2+2 5=1 + 4 5=2 + 3 5=5 Reprezentarea informaiilor 16
Vom reprezenta o partiie printr-un vector p cu maxim n componente, n care vom reine elementele partiiei. Pentru a nu genera de dou ori o aceeai partiie (de exemplu, 5=1+4 i 5=4+1), convenim s memoram n vectorul p elementele partiiei n ordine cresctoare. Condiii interne (elementele partiiei sunt numere naturale nenule, cel mult egale cu n) (elementele partiiei sunt n ordine cresctoare); (suma elementelor partiiei trebuie s fie egala cu n). Vom utiliza o variabil global ValP n care vom reine permanent suma valorilor elementelor fixate n partiie. La adugarea unui element n partiie, valoarea acestuia va fi adugat la ValP, respectiv la eliminarea unui element din partiie, valoarea acestuia va fi sczut din ValP. Conform condiiei interne 2, candidaii pentru o poziie k din partiie sunt numerele naturale cel puin egale cu ultimul element plasat n partiie (p [ k-1 ]). Pentru ca aceast relaie s fie valabil pentru orice k (inclusiv pentru k=l), vom utiliza o poziie suplimentar n vectorul p, poziia 0, pe care vom plasa iniial valoarea 1 (p [ 0 ] =1). Conform condiiei interne 3, candidaii pentru o poziie k trebuie s fie numere naturale care, adugate la suma valorilor fixate deja n partiie, s nu depeasc pe n, deci trebuie s fie cel mult egale cu n-ValP. Utiliznd aceste dou observaii, deducem c mulimea candidailor posibili pentru poziia k este {p [k-1 ],..., n-ValP}. program Partitii_Numar_Natural; const NMax = 100; type Element = 0 .. NMax; Partitie = array[Element] of Element; var n, ValP: Element; {ValP - reprezinta suma valorilor elementelor partitiei} p: Partitie; fout: text; procedure Afisare(lg: Element) ; var i: Element; begin write({fout,} n, '= '); for i :=1 to lg-1 do write({fout,} p[i], ' + '); writeln({fout,} p[lg]); end; procedure ConstrPart(k: Element); var i: Element; begin if ValP=n then Afisare(k-1) inc(ValP,i); ConstrPart (k+1) ; dec(ValP, i); end; begin write('n= '); readln(n); assign(fout, 'partnr.out'); rewrite(fout); p[0] := 1; 17 {am obtinut o solutie, o afisez} {maresc ValP cu valoarea noului element} {apel recursiv} {restaurez valoarea variabilei ValP} end; else for i := p[k-1] to n-ValP do begin p[k] := i; {cand apelam ConstrPart(k) am fixat p[1],p[2],...,p[k-1]}
Aplicaii
Plata unei sume cu monede de valori date. Avnd la dispoziie n sculee cu monede, fiecare scule coninnd monede de aceeai valoare, s se afieze toate modalitile de a plti o sum dat S folosind numai monedele din sculee. De exemplu, s presupunem c trebuie s achitm suma S=100 i avem n=3 sculee de monede. n primul scule se gsesc 6 monede cu valoarea 3, n al doilea scule se gsesc 4 monede cu valoarea 7, iar n ultimul scule sunt 14 monede cu valoarea 5. Programul va afia n fiierul de ieire (denumit ' suma. out') cele dou soluii posibile de plat astfel: Soluia nr. 1 3 monede cu valoarea 3 3 monede cu valoarea 7 14 monede cu valoarea 5 Soluia nr. 2 4 monede cu valoarea 3 4 monede cu valoarea 7 12 monede cu valoarea 5 Reprezentarea informaiilor Vom reine ntr-un vector V cu n componente valorile monedelor din cei n sculee, iar ntr-un alt vector M vom reine multiplicitile, adic numrul de monede din fiecare scule n parte.
program Plata_Suma; const NrMaxMonede = 20; type Moneda = 0..NrMaxMonede; Valori = array[Moneda] of word; Multiplicitati = array[Moneda] of byte; var n: Moneda; V: Valori; M, P: Multiplicitati; S, Sum, NrSol: word; fout: text; procedure Citire; {citesc datele de intrare din fisierul suma.in} var fin: text; i: Moneda; begin {assign(fin,'c:\tp\bin\suma.in'); reset(fin);} readln({fin,} n); readln({fin,} S); for i := 1 to n do readln({fin,} V[i], M[i]); { close(fin);} end; procedure Afisare; {afisez o solutie in fisierul de iesire} 18
var i: Moneda; begin inc(NrSol); writeln({fout,} 'Solutia nr. ', NrSol); for i := 1 to n do if P[i]<>0 then writeln({fout,} P[i], ' monede cu valoarea ', V[i]); writeln{(fout)}; end; procedure Plata(k: Moneda); {cand apelam procedura Plata cu parametrul k am selectat deja monede din saculetii 1,2,...,k-l} var j: Moneda; begin if k=n+1 then {am selectat monede de toate tipurile} if Sum = S then Afisare {Sum - valoarea monedelor selectate este egala cu S} else else {mai selectam monede din saculetii k,k+l,...,n} for j := 0 to M[k] do begin P[k] := j; {selectez j monede din saculetul k} if Sum+j*V[k]<=S then {valoarea monedelor selectate din saculetul k adaugata la valoarea monedelor deja selectate} {nu depaseste S, suma care trebuie platita} begin inc(Sum, j*V[k]); {adaug valoarea monedelor selectate din saculetul k} {la valoarea totala a monedelor selectate memorata in Sum} Plata(k+1); {apel recursiv} dec(Sum, j * V[k]); {restaurez dupa apel valoarea variabilei globale Sum} end else break {valoarea curenta a monedelor selectate depaseste suma S} {iesire fortata din for, deoarece nu are sens sa selectez mai multe monede din saculetul k, oricum am depasit deja S} end; end; begin {program principal} Citire; assign(fout, 'suma.out'); rewrite(fout); Plata(1); close(fout); end.
Observai c, i n acest program am utilizat tehnica variabilei globale: pentru a nu calcula la fiecare pas valoarea total a monedelor deja selectate, am reinut aceast valoare n variabila global Sum. De fiecare dat cnd selectm monede dintr-un scule, adugm valoarea monedelor selectate la Sum, apoi apelm recursiv procedura Plata care selecteaz n continuare monede din ceilali sculee pentru plata sumei. Cnd revenim din apelul recursiv, trebuie s restaurm valoarea variabilei globale Sum (deci trebuie s scdem din Sum valoarea monedelor selectate la pasul precedent din sculeul k, pentru a putea aduga ulterior, dac este cazul, valoarea corespunztoare monedelor selectate din sculeul k la pasul urmtor). Observai, de asemenea, c am optimizat programul: dac la un moment dat valoarea monedelor deja selectate depete suma S care trebuie pltit, nu continum generarea.
Paranteze
Fie N aparine N*, N numr par. S se scrie un program care s genereze toate irurile de n paranteze rotunde care se nchid corect. De exemplu, pentru N=4, programul afieaz: (()) ()() Reprezentarea informaiilor Vom memora un ir de paranteze ntr-un vector s cu N componente care pot avea doar valorile ' ( ' sau ' ) '. La fiecare moment vom reine numrul total de paranteze deschise (n variabila global ND) i numrul total de paranteze nchise (n variabila global NI). 19
Condiii interne n final: ND=NI Numrul total de paranteze nchise trebuie s fie egal cu numrul total de paranteze deschise i egal cu N div 2. Aceast condiie nu este suficient. Dac am considera numai aceast condiie, irul ) ) ( ( ar trebui s fie corect i nu e! Pentru ca parantezele s se nchid corect, trebuie ca la fiecare moment numrul de paranteze deschise s fie cel puin egal cu numrul de paranteze nchise. Pe parcurs : ND>=NI. program Paranteze; const NMax = 20; type Indice = 1..NMax; Sir = array[Indice] of char; var s: Sir; N, ND, NI: Indice; {ND - numarul total de paranteze deschise} {NI - numarul total de paranteze inchise} fout: text; procedure Afisare; var i: Indice; begin for i := 1 to N do write({fout,} s[i]); writeln{(fout)} end; procedure Constructie(k: Indice); {cand apelam Constructie(k), am fixat in sir k-l paranteze} begin if k-1 = N then {sirul de paranteze este complet} Afisare else begin if ND < N div 2 then {se mai pot deschide paranteze} begin s[k]:='('; inc(ND); Constructie(k+1); dec(ND); end; if NI < ND then {se poate inchide o paranteza} begin s[k] := ')' ; inc(NI); Constructie(k+1); dec(NI); end end end; begin {program principal} write('N= '); readln(N); assign(fout, 'sir.out'); rewrite(fout); Constructie(1); close(fout); end.
Comis-voiajor
Un comis-voiajor plec din oraul n care locuiete (s-1 notm 1) s prezinte produsele unei firme n toate cele n orae din regiunea sa. El are la dispoziie harta regiunii, pe care sunt marcate legturile directe dintre orae i distanele dintre acestea. Scriei un program care s determine un traseu ct mai scurt, astfel nct comis-voiajorul s viziteze toate oraele din regiune, s nu treac de mai multe ori prin acelai ora i s se ntoarc n oraul n care locuiete! De exemplu, s presupunem c exist n=5 orae, numerotate de la 1 la 5. S presupunem c harta regiunii indic urmtoarele legturi directe :
Pentru acest exemplu, programul va afia: Traseul cel mai scurt are lungimea 815.00 Traseul este 1,3,4,2,5,1 Reprezentarea informaiilor Vom reprezenta harta regiunii printr-o matrice ptratic A, cu nxn componente avnd semnificaia: A [ i, j ] = 0, dac ntre oraele i i j nu exista legtur direct, respectiv distana dintre oraele i i j, dac ntre i i j exist legtur direct. 20
Traseul, mai exact ordinea n care comis-voiajorul viziteaz cele n orae, l vom reine ntr-un vector T, cu n componente. Pentru a nu trece de mai multe ori prin acelai ora, vom mai utiliza un vector v, cu n componente, n care pentru fiecare ora vom reine dac a fost sau nu vizitat: v [ i ] = 1 dac oraul i a fost vizitat i 0 n caz contrar. Cnd obinem un traseu soluie nu l afim, ci l comparm cu traseul de lungime minim obinut pn la momentul respectiv. Prin urmare, vom utiliza TMin, un vector cu n componente, n care reinem un traseu de lungime minim i o variabil global LgMin n care reinem lungimea acestui traseu.
Pentru a optimiza procedura de generare a traseelor, vom utiliza o variabil global Lg m care reinem lungimea traseului curent. Astfel, nu va mai fi nevoie s calculm la fiecare pas lungimea traseului curent: cnd ne deplasm n alt ora adunm distana pn la oraul respectiv la Lg. Cnd eliminm un ora de pe traseu, scdem distana pn la acesta din Lg. Dac la un moment dat
program Comis_Voiajor; const NMaxOrase = 20; type Oras = 1 .. NMaxOrase; Harta = array[oras, Oras] of real; Traseu = array[oras] of Oras; var A: Harta; n: Oras; T, TMin: Traseu; Lg, LgMin: real; v: array[oras] of 0..1; procedure Citire; var i, j: Oras; f: text; d: real; begin assign(f, 'comis.in'); reset(f); readln(f, n); while not seekeof(f) do begin {de pe fiecare linie citesc doua orase intre care exista legatura directa, precum si distanta dintre ele} readln(f, i, j, d) ; A[i,j] := d; A[j,i] := d; end; close(f) end; function Infinit: real; {intoarce un numar suficient de mare, pentru a putea fi utilizat ca valoare de initializare pentru LgMin} var s: real; i, j: Oras; begin s := 0; for i := 1 to n do end; procedure Afisare; var i: Oras; begin if LgMin = Infinit then writeln('Nu exista solutii') else begin 21 for j := 1 to n do s:=s+A[i,j]; Infinit := s+1;
writeln('Traseul cel mai scurt are lungimea ',LgMin:10:2);writeln; write('Traseul este '); for i := 1 to n do write (TMin[i] , ','); writeln(TMin[1]);writeln; end; readln; end; procedure ConstrTraseu(k: Oras); var i: Oras; begin if k-1 = n then if A[1, T[n]] <> 0 then if Lg+A[1,T[n]]<LgMin then begin TMin := T; LgMin := Lg+A[1,T[n]]; end else else else for i := 2 to n do {construiesc in continuare traseul} {verific daca se poate deplasa in orasul i} {traseul este complet} {poate reveni in orasul 1} {am obtinut un traseu mai scurt}
if (A[i, T[k-1]] <> 0) and (v[i] = 0) then begin T[k] := i; v[i] := 1; Lg := Lg+A[i,T[k-1] ] ; if Lg <= LgMin then ConstrTraseu(k+1); v[i] := 0; Lg := Lg-A[i,T[k-1]]; end end; begin {program principal} Citire; T[1] := 1; v[1] := 1; LgMin := Infinit; ConstrTraseu (2); Afisare; end.
Backtracking in plan
n variant elementar, aplicm metoda backtracking pentru rezolvarea problemelor n care soluia era reprezentat ca vector. Putem generaliza ideea cutrii cu revenire i pentru probleme n care cutarea se face n plan". Pentru noi, planul va fi reprezentat ca un tablou bidimensional. Pentru a intui modul de funcionare a metodei backtracking n plan, s ne imaginm explorarea unei peteri. Speologul pornete de la intrarea n peter i trebuie s exploreze n mod sistematic toate culoarele peterii. Ce nseamn n mod sistematic" ? n primul rnd, i stabilete o ordine pentru toate direciile posibile de micare (de exemplu, N, NE, E, SE, S, SV, V, NV) i ntotdeauna cnd se gsete ntr-un punct din care are mai multe culoare de explorat, alege direciile de deplasare n ordinea prestabilit. n al doilea rnd, speologul va plasa marcaje pe culoarele pe care le-a explorat, pentru ca nu cumva s se rtceasc i s parcurg de mai multe ori acelai culoar (ceea ce ar conduce la determinarea eronat a lungimii peterii). n ce const explorarea ? Speologul exploreaz un culoar pn cnd ntlnete o intersecie sau pn cnd culoarul se nfund. Dac a ajuns la o intersecie, exploreaz succesiv toate culoarele care pornesc din intersecia respectiv, n ordinea prestabilit a direciilor. Cnd un culoar se nfund, revine la intersecia precedent i alege un alt culoar, de pe urmtoarea direcie (dac exist; dac nu exist, revine la intersecia precedent .a.m.d.). S descriem ntr-o form mai general aceast metod. Vom nota prin NrDirectii o constant care reprezint numrul de direcii de deplasare, iar dx, respectiv dy sunt doi vectori constani care reprezint deplasrile relative pe direcia Ox, respectiv pe direcia Oy, urmnd n ordine cele NrDirectii de deplasare. procedure Bkt_Plan(x,y: integer); (x, y reprezinta coordonatele pozitiei curente} begin Explorare(x,y); {exploram pozitia curenta} if EPinal(x,y) then Prelucrare_Solutie {pozitia x,y este un punct final} else {continuam cautarea} for i : = 1 to NrDirectii do {ma deplasez succesiv pe directiile posibile de miscare} if Nevizitat(x+dx[i], y+dy[i]) then {nu am mai trecut prin aceasta pozitie} Bkt_Plan(x+dx[i], y+dy[i]); end; 22
Labirint
ntr-un labirint, reprezentat ca o matrice L, cu n linii i m coloane, cu componente 0 sau 1 (1 semnificnd perete, 0 culoar), se gsete o bucat de brnz pe poziia (xb, yb) i un oricel pe poziia (xs, ys). Afiai toate posibilitile oricelului de a ajunge la brnz, tiind c el se poate deplasa numai pe culoar, iar direciile posibile de micare sunt N, NE, E, SE, S, SV, V, NV. De exemplu, pentru un labirint cu 4 linii i 4 coloane de forma urmtoare, n care oricelul se gsete pe poziia 1 1, iar brnza pe poziia 4 4 0 0 0 1 1 1 1 0 1 1 0 1 1 1 0 0
programul va afia: Soluia nr. *111 *111 *1** 1*1* Soluia nr. *111 *111 *1*0 1*1* 1
Reprezentarea informaiilor
Labirintul este reprezentat ca o matrice L, cu nxm elemente. Elementele labirintului sunt iniial 0 (semnificnd culoar) i 1 (semnificnd perete). Pentru ca oricelul s nu treac de mai multe ori prin aceeai poziie, existnd riscul de a intra n bucl, vom marca n labirint poziiile prin care trece oricelul cu 2. Pentru a determina poziiile n care se poate deplasa oricelul, vom utiliza dou constante cu tip Dx i Dy, pe care le iniializm cu deplasrile pe linie, respectiv pe coloan pentru toate cele 8 direcii posibile de micare.
Pentru a nu verifica permanent daca oricelul nu a ajuns cumva la marginea labirintului, bordm labirintul cu perete (dou linii i dou coloane cu valoarea 1). Condiii interne Din poziia (x, y) oricelul se poate deplasa pe direcia dir, deci n poziia (x+Dx[dir] , y+Dy[dir] ) dac i numai dac L[x+Dx[dir],y+Dx[dir]]=0 (aceast poziie reprezint un culoar prin care oricelul nu a mai trecut).
program Cautare_in_Labirint; const DimMax = 20; {dimensiunea maxima a labirintului} Dx: array[1..8] of integer = (-1,-1,0,1,1,1,0,-1 ); 23
Dy: array[1..8] of integer = (0,1, 1,1,0,-1,-1,-1); type Indice = 0 .. DimMax +1; var L: Labirint; Labirint = array[Indice, Indice] of 0 .. 2;
procedure Citire; var f: text; i, j: Indice; begin assign (f, 'labirint.in'); reset(f); readln(f, n, m); readln(f, xs, ys, xb, yb);
for i := 1 to n do begin for j := 1 to m do read (f, L [ i, j ] ) ; end; procedure Bordare; {bordam labirintul cu cate un perete} var i: Indice; {perete la stanga si la dreapta} readln(f) end; close(f);
begin L[0, i] := 1; L[n+1, i] := 1 end end; function Final(x, y: Indice): boolean; {intoarce true daca in pozitia x y se afla branza} begin Final := false; if (x = xb) and (y = yb) then Final := true end; procedure Afisare; var i, j: Indice; begin inc(NrSol); writeln(fout, 'Solutia nr. ', NrSol); for i := 1 to n do begin for j := 1 to m do
if L[i, j] = 2 then write(fout, '*') else write(fout, L[i, j]); writeln(fout); end; end; procedure Cauta(x, y: Indice); var dir: 1..8; begin L[x,y] := 2; {marchez pozitia x y}
if Final(x, y) then Afisare else for dir := 1 to 8 do if L[x+Dx[dir], y+Dy[dir]]=0 then {culoar nevizitat} Cauta(x+Dx[dir], y+Dy[dir]); L[x, y] := 0; {la intoarcere sterg marcajul, pentru a putea explora} 24
{program principal}
assign(fout, 'labirint.out'); rewrite(fout); Cauta(xs, ys); if NrSol = 0 then writeln(fout, 'Nu exista solutii!'); close(fout);end.
Fotografie
Fotografia alb-negru a unui obiect este reprezentat sub forma unei matrice cu n linii i m coloane, ale crei elemente sunt 0 sau 1. Elementele egale cu 1 reprezint punctele ce aparin unui obiect. Dou elemente de valoare 1 fac parte din acelai obiect dac sunt adiacente pe linie sau pe coloan. Se cere s se determine numrul obiectelor din fotografie. De exemplu, pentru matricea:
Soluie Pentru a numra obiectele din fotografie, vom parcurge matricea care reprezint fotografia, cutnd un element cu valoarea 1, deci care aparine unui obiect. Vom numra noul obiect depistat, apoi vom terge" obiectul din fotografie, colorndu-1 n culoarea de fond (valoarea 0 n matrice). Pentru a terge" un obiect vom folosi procedura recursiv Sterge_0biect (x, y), care n cazul n care punctul de coordonate (x, y) aparine obiectului (a [x, yj =1), l terge (a [x,y] =0), apoi se apeleaz recursiv procedura pentru toate punctele adiacente cu (x,y) (pe linie sau pe coloan).
Pentru a nu testa permanent dac n timpul cutrii am depit marginile fotografiei, am bordat matricea care reprezint fotografia cu cte o linie i o coloan sus, jos, stnga, dreapta iniializate cu valoarea 0 (am nrmat" fotografia). Observaie Acest tip de algoritm, prin care plecnd de la un element sunt atinse" succesiv toate elementele care au o legtur (direct sau indirect) cu elementul respectiv, poart numele de algoritm defll (umplere). program Fotografie; const DimMax = 50; Dx: array[l .. 4] of integer=(-1,0,1,0); Dy: array[l .. 4] of integer=(0,1,0,-1); type Indice = 0 .. DimMax+1; var a: array[Indice, Indice] of 0 .. 1; m, n, i, j, NrObiecte: Indice; procedure Citire; var fin: text; begin assign(fin, 'foto.in'); reset(fin); readln(fin, n, m) ; for i : = 1 to n do begin for j := 1 to m do read(fin, a[i, j]); readln(fin); close(fin); end;
end;
25
procedure Sterge_0biect(x ,y: Indice); var dir: 1 .. 4; begin if a[x, y] = 1 then begin a[x, y] := 0;
{sterg acest element de imagine} {cautarea continua in cele 4 directii posibile} for dir:= 1 to 4 do Sterge_0biect(x+Dx[dir], y+Dy[dir]); end; end; begin Citire; for i : = 1 to n do for j : = 1 to m do if a[i, j] = 1 then begin inc(NrObiecte); Sterge_0biect(i, j); end; writeln('Nr. obiecte = ', NrObiecte); readln end.
Dac ns datele de intrare au dimensiuni mari, ceea ce n practic este inevitabil, o abordare backtracking este inacceptabil. Principiul de baz poate fi rezumat astfel: dect un algoritm teoretic perfect, dar care s nu furnizeze soluii n timpul disponibil, mai bine un algoritm bun, care s ofere soluii aproape" optime n timp scurt. Un astfel de algoritm este denumit algoritm euristic. Studiul algoritmilor euristici este o problem fierbinte" n informatic la ora actual.
Soluie Reprezentarea informaiilor Permutrile de ordin n reprezint toate posibilitile de a aranja elementele unei mulimi de n elemente. Definite riguros, permutrile de ordin n sunt funcii bijective de forma: p:{1,2,...,n}->{1,2,...,n}. 26
Reprezentm o astfel de funcie printr-un vector p, cu n componente, avnd semnificaia: p [ i ] este elementul asociat prin intermediul funciei p elementului i (i aparine {1, 2 ,..., n}). Condiii interne 1. p[i] aparine {1,2,...,n} pentru i aparine {1,2,...,n} (domeniul de valori este {1,2,...,n}) 2. p [ i ] diferit p [ j ] , pentru i diferit j , i, j aparine {1, 2 ,..., n}
program Permutari; const NMax = 20; type Indice = 1 .. NMax; Permutare = array[Indice] of Indice; var n: Indice; p: Permutare; ImF: set of Indice; {imaginea functiei} fout: text; {fisierul de iesire} procedure Afisare; var i: Indice; begin for i := 1 to n do write(fout, p[i], ' '); writeln(fout); end; procedure GenPermutari(k: Indice); {cand apelam procedura GenPermutari cu parametrul k) {pozitiile 1,2,...,k-1 din vectorul p sunt fixate} var i: Indice; begin if k-1 = n then {solutia este completa} Afisare {prelucrarea solutiei consta in afisare} else {continuam generarea} for i:=1 to n do {determin candidatii pentru pozitia k} if not (i in Imf) then begin {i este un candidat, deoarece nu este imaginea nici unui alt element fixat} p[k] := i; Imf := Imf + [ i]; {i este imaginea lui k, deci il retin in Imf} GenPermutari (k + 1); {apel recursiv} Imf := Imf-[i]; {restaurez valoarea variabilei Imf dinaintea apelului} end; end; begin write(' n = '); readln(n); assign(fout, 'perm.out'); rewrite(fout); GenPermutari(1); close(fout); end. Observatie Pentru generarea permutrilor am utilizat o tehnic interesant. n loc sa verificam pentru fiecare element i daca este sau nu un candidat pentru pozitia k} procedure GenAranjamente(k: Indice); var i: Indice; begin if k-l=m then {numarul de elemente din vector este m} Afisare else for i : = 1 to n do if not (i in Imf) then begin Imf := Imf + [i]; f[k] := i; GenAranjamente(k+1) ; Imf := Imf - [i]; end; end;
Generarea combinrilor. Fie nN* i mN, m<n. Scriei un program recursiv care s genereze combinrile de n elemente luate cte m. De exemplu, pentru n=4 i m=2, programul va genera: l 2 l 3 l 4 2 3 2 4 3 4 Soluie 27
Combinrile de n elemente luate cte m reprezint toate submulimile de m elemente ale unei mulimi cu n elemente. Observai c, spre deosebire de aranjamente, ordinea elementelor din submulime nu conteaz. Din acest motiv, pentru a nu genera de mai multe ori aceeai submulime (de exemplu, 1 2 i 2 1) vom stabili o ordine convenabil pentru elementele submulimii - ordinea cresctoare. Reprezentarea informaiilor Reprezentm o submulime printr-un vector C, care conine cele m elemente ale submulimii, ordonate cresctor. Condiii interne (elementele aparin mulimii {1,2, ... ,n}) (elementele sunt ordonate cresctor).
Conform celei de a doua condiii interne pe poziia k putem plasa doar elemente mai mari dect elementul plasat pe poziia k-1 (C[k-l]). Deci, valoarea minim care poate fi plasat pe poziia k este C [k-1] +1. Pentru ca aceast formul s fie valabil pentru orice poziie k (inclusiv pentru k=l) vom declara indicii vectorului care reprezint submulimea ncepnd de la 0 i vom considera C [ 0 ] = 0. Deoarece dup C[k] trebuie plasate nc m-k elemente mai mari dect C [k], s determinm valoarea maxim care poate fi plasat pe poziia k.
Valoarea maxim care poate fi plasat pe poziia m este n, pe poziia m-1 este n-1 .a.m.d. Observm c, daca poziia scade cu 1, i valoarea maxim care poate fi plasat pe poziia respectiv scade cu 1. Prin urmare, diferena dintre poziie i valoarea maxim care poate fi plasat pe poziia respectiv este constant (m-n). Deducem c valoarea maxim care poate fi plasat pe poziia k este n-m+k. Prin urmare, mulimea candidailor pentru poziia k este {C[k-l]+l,...,n-m+k}. program Combinari; const NMax = 30; type Indice = 0..NMax; Submultime = array[Indice] of Indice; var C : Submultime; m, n: Indice; procedure Afisare; var i : Indice; begin for i := 1 to m do write(C[i],' '); writeln; end; procedure GenCombinari(k : Indice); var i : Indice; begin if k-1=m then Afisare else for i := C[k-1]+1 to n-m+k do begin C[k] := i; GenCombinari(k+1); end; end; begin write('n = '); readln(n); write('m = '); readln(m); GenCombinari(1); end.
uoare), care se rezolv, iar soluia pentru problema iniial se obine combinnd soluiile problemelor n care a fost descompus. Se presupune c fiecare din problemele n care a fost descompus problema iniial, se poate descompune n alte subprobleme, la fel cum a fost descompus problema iniial. Procedeul se reia pn cnd (n urma descompunerilor repetate) se ajunge la probleme care admit rezolvare imediat. Evident, nu toate problemele pot fi rezolvate prin utilizarea acestei tehnici. Fr teama de a grei, putem afirma c numrul lor este relativ mic, tocmai datorit cerinei ca problema s admit o descompunere repetat. Divide et impera este o tehnic ce admite o implementare recursiv. Am nvat principiul general prin care se elaboreaz algoritmii recursivi: ce se ntmpl la un nivel, se ntmpl la orice nivel (avnd grij s asigurm condiiile de terminare). Tot aa, se elaboreaz un algoritm prin divide et impera: la un anumit nivel avem dou posibiliti: 1) am ajuns la o problem care admite o rezolvare imediat, caz n care se rezolv i se revine din apel (condiia de terminare); 2) nu am ajuns n situaia de la punctul 1, caz n care descompunem problema n dou sau mai multe subprobleme, pentru fiecare din ele reapelm procedura (funcia), combinm rezultatele si revenim din apel.
Cutare binar
Se citete un vector cu n componente numere ntregi, unde numerele se presupun ordonate cresctor i o valoare ntreag (nr). S se decid dac nr se gsete sau nu printre numerele citite, iar n caz afirmativ s se tipreasc indicele componentei care conine acea valoare. 29
O rezolvare n care nr se compar pe rnd cu cele n valori, este lipsit de valoare (nu exploateaz faptul c cele n valori sunt n secven cresctoare). Algoritmul care va fi propus este mult mai performant i face parte din algoritmii clasici. program ackermann; type stiva=array [1..100,1..2] of integer; var st: stiva; begin write ('m=') ; readln (m); write ('n='); readln (n); k:=1; st[k,1]:=m; st[k,2]:=n; while k>0 do if (st[k,1]<>0) and (st[k,2]<>0) then begin m,n,k:integer;
k:=k+1; st[k,1]:= st[k-1, 1]; st[k,2]:=st[k-1,2]-1 end else if st[k,2]=0 then begin st[k,1]:=st[k,1]-1; st[k,2]:=1 end else begin k:=k-1; if k>0 then begin st[k,1]:=st[k,1]-1; st[k,2]:=st[k+1,2]+1 end end; writeln('ac(',m, ', ',n,')=',st[1,2]+1) end. { m=2, n=3}
Ca i la problema anterioar, este interesant de urmrit modul n care a fost ridicat recursivitatea din definiie. Cele dou variante sunt echivalente din punct de vedere al timpului de calcul, dar cea recursiv este de preferat pentru simplitatea cu care a fost scris programul. write('nr='); readln(nr); caut(1,n); end.
mutarea a n-1 discuri de pe tija a pe tija c, utiliznd ca tij intermediar tija b; mutarea discului rmas pe tija b; mutarea a n-1 discuri de pe tija c pe tija b, utiliznd ca tij intermediar tija a. Parcurgerea celor trei etape permite definirea recursiv a irului H(n,a,b,c) astfel:
Sortare rapid
Fie vectorul a cu n componente numere ntregi (sau reale). Se cere ca vectorul s fie sortat cresctor. Pentru rezolvare este necesar o procedur POZ care trateaz o poriune din vector cuprins ntre indicii dai de li (limita inferioar) i ls (limita superioar). Rolul acestei proceduri este de a poziiona prima component a[li] pe o poziie k cuprinsa ntre li i ls, astfel nct toate componentele vectorului cuprinse ntre li i k-1 s fie mai mic sau egale dect a[k] si toate componentele vectorului cuprinse ntre h+1 i ls s fie mai mari sau egale dect a[k]. n aceast procedur exist dou moduri de lucru: a) i rmne constant, j scade cu 1; b) i creste cu 1, j rmne constant. Procedura este conceput astfel: iniial, i va lua valoarea li, iar j va lua valoarea ls (elementul care iniial se afla pe poziia li se va gsi mereu pe o poziie dat de i sau de j); se trece n modul de lucru a); att timp ct i<j se execut: dac a[J] este strict mai mare dect a[j], atunci se inverseaz cele dou numere si se schimb modul de lucru; i i j se modific corespunztor modului de lucru n care se afl programul: k ia valoarea comun a lui i i j. 31
Exemplu: a=(6,9,3,1,2), li=1, ls=5; modul de lucru a); i=1,j=5; a[1]>a[5], deci se inverseaz elementele aflate pe poziiile 1 si 5, deci a=(2,9,3,1,6) i programul trece la modul de lucru b); i=2, j=5; a[2]>a[5], deci a=(2,6,3,1,9) i se revine la modul de lucru a); 1=2, j=4; a[2]>a[4], deci a=(2,1,3,6,9); se trece la modul de lucru b); i=3, j=4: procedura se ncheie, elementul aflat iniial pe poziia 1 se gsete acum pe poziia 4, toate elementele din stnga lui fiind mai mici dect el, totodat toate elementele din dreapta lui fiind mai mari dect el (k=4). Alternana modurilor de lucru se explica prin faptul c elementul care trebuie poziional se compar cu un element aflat n dreapta sau n stnga lui, ceea ce impune o modificare corespunztoare a indicilor i i j. Observaie. Dup aplicarea procedurii POZ, este evident c elementul care se afl iniial n poziia li va ajunge pe o poziie k i va rmne pe acea poziie n cadrul vectorului deja sortat, fapt care reprezint esena algoritmului.
DIVIDE ET IMPERA,
Procedura QUICK are parametrii li i ls (limita inferioar i limita superioar). n cadrul ei se utilizeaz metoda dup cum urmeaz: se apeleaz POZ; se apeleaz QUICK pentru li i k-1; se apeleaz QUICK pentru k+1 i ls. program quickl; type vector=array[1..100] of integer; var i,n,k:integer; a:vector; procedure poz (li,ls:integer;var k:integer;var a:vector); var i,j,c,i1,j1:integer; begin i1:=0;j1:=-1; i:=li;j:=ls; while i<j do begin if a[i]>a[j] then begin c:=a[j]; a[j]:=a[i]; a[i]:=c; c:=i1; i1:=-j1; j1:=-c; end; i:=i+i1; j:=j+j1; end; k:=i ; end; procedure quick (li,ls:integer); begin if li<ls then begin poz(li,ls,k,a); quick(li,k-1); quick(k+1,ls) end; end; begin write('n=');readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]); end; quick(1,n); for i:=1 to n do writeln (a[i]) end.
dac a[i]>b[j], atunci c[k] va lua valoarea lui a[i], iar valorile variabilelor i i k vor crete cu 1; altfel, c[k] va lua valoarea lui b[j], n timp ce valorile variabilelor j i k vor creste cu 1. Dup ce unul din cei doi vectori a fost n totalitate parcurs i scris n c, vectorul rmas se va copia n c. Exemplu: a=(1,3,5,9), b=(2,4): i=1,j=1,k=1;. a[1]<b[1], deci c[1]=1, i=2, k=2; a[2]>b[1], deci c[2]=2, j=2, k=3; a[2]<b[2], deci c[3]=3, i=3, k=4; a[3]>b[2], deci c[4]=4, j=3, k=5; vectorul b a fost trecut n c n ntregime, n continuare urmnd a se copia ceea ce a rmas neparcurs din vectorul a n vectorul . c[5]=5, c[6]=9. Propunem ca exerciiu scrierea acestui program. n cele ce urmeaz vom utiliza algoritmul de interclasare n vederea sortrii unui vector prin interclasare. Observaie: un vector cu o singur component este un vector sortat; pentru a sorta (cresctor) un vector cu dou componente, acestea se compara ntre ele i, daca prima este mai mare dect cea de-a doua, locurile lor se inverseaz. Algoritmul de sortare prin interclasare se bazeaz pe urmtoarea idee: pentru a sorta un vector cu n elemente l mprim n doi vectori care, odat sortai, se interclaseaz. Conform strategiei generale DIVIDE ET IMPERIA, problema este descompus n alte dou subprobleme de acelai tip i, dup rezolvarea lor, rezultatele se combin (n particular se interclaseaz). Descompunerea unui vector n ali doi vectori care urmeaz a fi sortai are loc pn cnd avem de sortat vectori de una sau dou componente n aplicaie, procedura SORT sorteaz un vector de maxim dou elemente; INTERC interclaseaz rezultatele; DIVIMP implementeaz strategia general a metodei studiate.
program sinter; type vector=array [1..10] of integer; var a:vector; n,i:integer; procedure sort (p,q:integer;var a:vector); var m:integer; begin if a[p]>a[q] then begin m:=a[p]; a[p]:=a[q]; a[q]:=m end; end; procedure interc (p,q,m:integer;var a:vector); var b:vector; i,j,k:integer; begin i:=p; j:=m+1; k:=1; while (i<=m) and (j<=q) do if a[i]<=a[j] then begin b[k]:=a[i]; i:=i+1; k:=k+1 end 33
else begin b[k]:=a[j]; j:=j+1; k:=k+1 end; if i<=m then for j:=i to q do begin b[k]:=a[j]; k:=k+1 end else for i:=j to q do begin b[k]:=a[i]; k:=k+1 end; k:=1; for i:=p to q do begin a[i]:=b[k]; k:=k+1 end; end; procedure divimp (p,q:integer;var a:vector); var m:integer; begin if (q-p)<=1 then sort(p,q,a) else begin m:=(p+q) div 2; divimp (p,m,a); divimp(m+1,q,a); interc(p,q,m,a); end ; end; begin write('n=');readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]) end; divimp(1,n,a); for i:=1 to n do writeln (a[i]); end.
Problema tieturilor
Se d o bucat dreptunghiular de tabl cu lungimea I i nlimea h, avnd pe suprafaa ei n guri de coordonare numere ntregi. Se cere sa se decupeze din ea o bucat de arie maxim care nu prezint guri. Sunt permise numai tieturi verticale i orizontale. Coordonatele gurilor sunt reinute n doi vectori XV si YV. Dreptunghiul iniial, precum i dreptunghiurile care apar n procesul tierii sunt memorate n program prin coordonatele colului din stnga-sus (X,Y), prin lungime l nlime (L,H). Pentru un dreptunghi (iniial pornim cu toat bucata de tabl), verificm dac avem sau nu o gaur n el (se caut practic prima din cele n guri). n situaia cnd acesta prezint o gaur, problema se descompune n alte patru probleme de acelai tip. Dac bucata nu prezint guri, se compar arta ei cu aria unei alte buci fr gaur, gsit n fazele precedente. Menionm c dreptunghiul de arie maxim fr guri este reinut prin aceiai parametri ca i dreptunghiul cu guri. n zonele XF, YF, LF, HF (variabile transmise prin referin de la o faz la alta). n concluzie, problema iniial am descompus-o n alte patru probleme de acelai tip, mai uoare, ntruct fiecare nou dreptunghi are cel mult n-1 guri, dac dreptunghiul iniial avea n guri. La aceast problem compararea soluiilor const n a reine dreptunghiul cu aria maxim dintre cele fr guri. Fie dreptunghiul cu o gaur: Pentru a se afla n interiorul dreptunghiului, gaura trebuie sa ndeplineasc simultan condiiile: 1) xv(i)>x: 2) xv(i)<x+l; 3) yv(i)>y: 4) yv(t)<y+h. Dac facem o tietur vertical prin aceast gaur, obinem dou dreptunghiuri: 1) x, y, xv(i)-x, h; . 2) xv(i), y, l+x-xv(i), h. n urma unei tieturi pe orizontal se obin cele dou dreptunghiuri: 1) x, y ,t ,yv(i)-y; 2) x, yv(i), I, h+y-yv(i). program tai; 34
type vect=array [1..9] of integer; var l,h,i,n,xf,yf,lf,hf:integer; xv,yv:vect; procedure dimp (x,y,l,h:integer;var xf,yf,lf,hf:integer; var xv,yv:vect); var gasit:boolean; i:integer; begin i:=l; gasit:=false; while (i<=n) and (not gasit) do if (xv[i]>x) and (xv[i]<l) and (yv[i]>y) and (yv[i]<y+h) then gasit:=true else i:=i+1; if gasit then begin dimp(x,y,xv[i]-x,h,xf,yf,lf,hf,xv,yv); dimp(xv[i] ,y,l+x-xv[i] ,h,xf,yf,lf,hf,xv,yv); dimp(x,y,l,yv[i]-y,xf,yf,lf,hf,xv,yv); dimp(x,yv[i],l,h+y-yv[i],xf,yf,lf,hf,xv,yv); end else if (l*h)>(lf*hf) then begin xf:=x; yf:=y; lf:=l;hf:=h end; end; begin write('n='); readln(n); for i:=1 to n do begin write('x[',i,']=');readln(xv[i]); write('y[',i,']=');readln(yv[i]); end; write('l=');readln(l); write ('h=') ; readln(h); lf:=0; hf:=0; dimp(0,0,l,h,xf,yf,lf,hf,xv,yv); writeln('x=',xf,' y=',yf,' l=',lf,' h=',hf) end.
Probleme propuse 1) Scriei un program n care calculatorul sa ghiceasc un numr natural ascuns de dumneavoastr (numrul este cuprins ntre 1 si 30000). Atunci cnd calculatorul propune un numr i se va rspunde prin: 1, dac numrul este prea mare; 2, dac numrul este prea mic; 0, dac numrul a fost ghicit. 2) Se consider un vector cu o componente, numere naturale. Definim plierea vectorului ,ca fiind suprapunerea unei jumti, numit donatoare, peste o alta, numit receptoare. n cazul n care vectorul are un numr impar de componente, cea din mijloc este eliminata. In acest fel se ajunge la un vector ale crui elemente au numerotarea jumtii receptoare. Exemplu: vectorul 1,2,3,4,5 se poate plia n dou moduri 1,2 si 4,5. Plierea se aplic n mod repetat pn se ajunge la un vector cu o singur component, iar coninutul ei se numete element final. S se precizeze care sunt elementele finale i care este irul de plieri prin care se poate ajunge la ele. O.N.I. 1992 (enun modificat). 3) Se da un vector cu n componente la nceput nule. O seciune pe poziia K va incrementa toate elementele aflate ntre o alt zon de secionare anterioara i K. Exemplu: 0000000 se secioneaz pe poziia 4: 1111000 se secioneaz pe poziia 1; 2111000 se secioneaz pe poziia 3; 2221000 etc. Sa se determine o ordine de secionare a unui vector cu n elemente astfel nct suma elementelor sale s fie S. Se citesc N i S.
Pentru rezolvare, vom alege un prim element al mulimii de numere reale. Dac este posibil, acesta va fi adugat soluiei, iniial vide. Posibilitatea ca acesta s fie adugat este dat de semnul numrului (acesta trebuie s fie mai mare ca O). Se alege un al doilea numr, cu care se procedeaz n mod asemntor. Algoritmul se ncheie cnd au fost alese l eventual adugate toate elementele mulimii. Pentru a rezolva o problem cu Greedy, soluia se construiete, dup regula: Pentru fiecare element care urmeaz s fie adugat soluiei finale, se efectueaz o alegere a sa din elementele mulimii A (dup un mecanism specific fiecrei probleme n parte), iar dac este posibil, acesta este adugat. Algoritmul se termin fie cnd a fost gsit soluia cerut, fie cnd s-a constatat inexistena acesteia. Intuitiv, alegem un element, al doilea,....pn cnd obinem ce dorim sau pn cnd au fost testate toate elementele mulimii. De aici provine si numele metodei (greedy=lacom).
Greedy pare att de simpl nct, la nceput, ne mir faptul c a fost evideniat ca tehnic. La o analiz atent, vom observa c lucrurile nu stau chiar aa. Exemplul prezentat este didactic (l rezolvam i fr s tim c exist aceast tehnic), el nu are alt rol dect de a evidenia caracteristicile tehnicii.
Tehnica Greedy poate fi privita ca o particularizare a tehnicii Backtracking, n care se renuna la mecanismul de ntoarcere. S analizm n paralel, cele dou tehnici, pentru a putea stabili asemnrile i diferenele existente ntre ele: => ambele tehnici ofer soluii sub form de vector; => tehnica Backtracking poate oferi toate soluiile problemei, n timp ce tehnica Greedy ofer o singur soluie => tehnica Greedy nu dispune de mecanismul ntoarcerii, specific tehnicii backtracking. : Aceasta este diferena esenial dintre cele doua tehnici, diferen care are consecine uriae n ce privete aplicabilitatea lor. Consecina 1. Este necesar ca cel care elaboreaz un algoritm Greedy s tie faptul ca, procednd n modul ales de el, ajunge la rezultatul dorit. Pentru fiecare problem n parte, dup ce se identifica un algoritm, este obligatoriu s se demonstreze c acesta conduce la soluia optima. Demonstraia faptului ca se ajunge la soluia optim este specific fiecrei probleme n parte. Consecina 2. Tehnica Greedy conduce la timp de calcul polinomial. Motivul care conduce la acest timp de calcul, tine de mecanismul tehnicii. S presupunem c mulimea din care se face alegerea are n elemente si c soluia are tot n elemente (caz maxim). Se fac n alegeri, la fiecare alegere se fac n teste, rezulta un algoritm cu timp O(n2). De multe ori, este necesar ca elementele mulimii. A s fie sortate, pentru ca apoi s alegem din acestea, iar sortarea necesita un timp minim O(n x log 2n). Ins sortarea se efectueaz la nceput. Prin urmare, acest timp se adun, deci nu influeneaz rezultatul. 0 ntrebare fireasc: fiind dat o problem, exist ntotdeauna un algoritm de tip Greedy care gsete soluia optim? Evident, NU. Exist probleme pentru care nu se cunosc astfel de algoritmi. Mai mult, pentru cele mai multe probleme nu se cunosc algoritmi Greedy. Avantajul timpului polinomial, conduce la necesitatea utilizrii tehnicii Greedy. Din alt punct de vedere, nu tuturor problemelor li se pot aplica algoritmi de acest tip. Ce este de fcut? 36
=> Pentru problemele pentru care nu se cunosc algoritmi care necesit timp polinomial, se caut soluii, chiar dac nu obine, dar apropiate de acestea, dar care au fost obinute n timp util. Multe din aceste soluii sunt obinute cu Greedy. Astfel de algoritmi se numesc algoritmi euristici. n continuare, vom da mai multe exemple de probleme care se rezolv cu Greedy.
sfrit a ultimului spectacol programat) este adugat soluiei. Odat adugat (programat) un spectacol, acesta rmne in cadrul soluiei. program SPECT; type spectacol=array[1..2,1..10] of integer; ordine=array[1..10] of integer; var s:spectacol; o:ordine; n,i,h1,m1,h2,m2:integer;
procedure sortare; var gata:boolean; m,i :integer; begin repeat gata:=true; for i:=1 to n-1 do if s[2,o[i]]>s[2,o[i+1]] then begin m:=o[i]; o[i]:=o[i+1]; o[i+1]:=m; gata:=false end until gata; end;
begin write ('n=') ; readln (n); for i:=1 to n do begin o[i]:=i; write('ora de inceput pentru .spectacolul ',i, ' (hh min)=') ; readln(h1,m1); s[1,i] :=h1*60+m1; write('ora de sfirsit pentru spectacolul ', i , ' (hh min =') ;
Problema rucsacului
O persoan are un rucsac cu care poate transporta o greutate maxim G. Persoana are la dispoziie n obiecte si cunoate pentru fiecare obiect greutatea si ctigul care se obine n urma transportului su la destinaie. Se cere s se precizeze ce obiecte trebuie s transporte persoana n aa fel nct ctigul sa fie maxim. O precizare n plus transform aceast problema n alte dou probleme distincte. Aceast precizare se refer la faptul c obiectele pot fi sau nu tiate pentru transportul la destinaie. In prima situaie, problema poart numele de problema continu a rucsacului, iar n a doua avem problema discreta a rucsacului. Aceste dou probleme se rezolv diferit, motiv pentru care ele sunt prezentate separat. Varianta continu a problemei rucsacului este tratat n acest paragraf iar cea discret va fi tratat cu ajutorul programrii dinamice. Problema continu a rucsacului, n care persoana are posibilitatea s taie obiectele. n acest fel, se poate obine o ncrcare mai eficient a rucsacului. Algoritmul este urmtorul: se calculeaz, pentru fiecare obiect n parte, eficiena de transport rezultat prin mprirea ctigului la greutate (de fapt, acesta reprezint ctigul obtinut prin transportul unitii de greutate); obiectele se sorteaz n ordine descresctoare a eficienei de transport si se preiau n calcul n aceast ordine; ctigul iniial va fi 0, iar greutatea rmas de ncrcat va fi greutatea rucsacului; 38
att timp ct nu a fost completat greutatea maxim a rucsacului si nu au fost luate in considerare toate obiectele, se procedeaz astfel: dintre obiectele nencrcate se selecteaz acela cu cea mai ridicat eficien de transport i avem dou posibiliti: acesta ncape n totalitate n rucsac, deci se scade din greutatea rmas de ncrcat greutatea obiectului, la ctig se cumuleaz ctigul datorat transportului acestui obiect; se tiprete 1, n sensul c ntregul obiect a fost ncrcat; obiectul nu ncape n totalitate n rucsac, caz n care se calculeaz ce parte din el poate fi transportat, se cumuleaz ctigul obinut cu transportul acestei pri din obiect, se tiprete procentul care s-a ncrcat din obiect, iar greutatea rmas de ncrcat devine 0. Demonstraia este asemntoare celei de la problema anterioar i o propunem ca tem. Vom considera un exemplu numeric. Greutatea care poate fi transportat cu ajutorul rucsacului aste 3 Avem la dispoziie 3 obiecte. Greutatea i ctigul pentru fiecare obiect sunt prezentate mai jos:
Eficiena de transport este 1 pentru primul obiect, 4 pentru al doilea si 2 pentru al treilea. In concluzie, obiectul 2 se ncarc n ntregime n rucsac, obinnd un ctig de 4 i rmne o capacitate de transport de dou uniti de greutate. Se ncarc 2/3 din obiectul 3 (care este al doilea n ordinea eficienei de transport) pentru care se obine ctigul 4. Ctigul obinut n total este 8. Se remarca strategia GREEDY prin alegerea obiectului care va fi transportat, alegere asupra creia nu se revine. program rucsac; type vector=array [1..9] of real; var c,g,ef:vector; n,i,man1:integer; gv,man,castig:real; inv:boolean; ordine:array [1..9] of integer;
begin write('Greutatea ce poate fi transportata='); readln(gv); write('Numar de obiecte='); readln(n); for i:=1 to n do begin write('c[',i,']='); readln(c[i]); write('g[',i,']='); readln(g[i]); ordine[i]:=i; repeat inv:=false; for i:=1 to n-1 do if ef[i]<ef[i+1] then begin man:=ef[i]; ef[i]:=ef[i+1]; ef[i+1]:=man; man:=c[i]; c[i]:=c[i+1]; c[i+1] :=man; man:=g [ i ]; g[i]:=g[i+1]; g[i+1]:=man; inv:=true; ef[i]:=c[i]/g[i] end;
man1:=ordine[i]; ordine[i]:=ordine[i+1]; ordine[i+1]:=man1 end until not inv; castig:=0; i:=1; while (gv>0) and (i<=n) do begin if gv>g[i] then begin writeln('Obiectul ',ordine[i],' ', 1); gv:=gv-g[i]; castig:=castig+c[i] end else begin writeln('Obiectul ',ordine[i],' ',gv/g[i]:1:2); castig:=castig+c[i]*gv/g[i]; gv:=0; end;
39
unde i1, i2,... im sunt tot numerele 1, 2,, m, luate n alt ordine. Pentru permutarea considerat suma va fi:
Vom arta, c dintre toate permutrile, permutarea identica este cea care maximizeaz suma (Atenie! Numerele sunt sortate b1<b2...<bm caz n care permutrii identice i corespunde irul b1, b2,...bm). Fie o permutare oarecare. diferit de cea identica. Privim permutarea ca un ir de numere naturale. Da exemplu, permutarea
o privim ca pe un sir de 5 numere naturale: 3, 1, 2, 5, 4. Sortam cresctor sirul, reinnd in acelai timp rezultatele pariale: Astfel, obinem:
Evident, cu orice permutare a mulimii primelor m numere naturale, putem proceda asemntor. Se obine astfel un sir de permutri, ir caracterizat de faptul c ultimul su element este permutarea identica: ******************=e (prin e am notat permutarea identic. Pe de alt parte dac * si *** sunt dou permutri vecine din sir, ele ofer printr-o singur inversare de elemente (corect inversiune). Fie **** si **** sumele asociate celor dou permutri, Avem **************** Cele dou sume sunt identice, excepie fcnd doi termeni (cei care au fost inversai cnd s-a obinut permutarea). Dup simplificri, rmne: akbk+ak+1bk+1<akbl+1+ak+1bl,+ak+1bl, unde bl>bi+1, ak<ak+1 inegalitatea se transform n: ak(bl-bl+1)-ak+1(bl-bl+1)<0; i apoi n (ak-ak+1)(bl-bl+1)<0, inegalitate evident. Reconsidernd sumele avem: 40
********************** q.e.d. Exemplu n=3, m=3, a1=7, a2=-5, a3=2; b1=-1, b2=2, b3=-3. Sortm a si b i obinem n urma sortrii: a1=-5, a2=2. a3=7; b1=-3, b2=-1, b3=2; Emax=-5x(-3)+2x(-1)+7x2=27. Prob. Scriem b n celelalte moduri cu putin: -1, 2, -3 => E=-5x(-1)+2x(2)+7x(-3)=-12<27; 2, -1, -3 => E=-5x2+2x(-1)+7x(-3)=-33<27; 2, -3, -1 => E=-5x2+2x(-3)+7x(-1)=-23<27; -1, -3, 2 => E=-5x(-1)+2x(-3)+7x2=23<27; -3, 2, -1 => E=-5x(-3)+2x2+7x(-1)=12<27. Trecem la cazul general, n>m. Sortm elementele celor dou mulimi. 1) S presupunem c elementele mulimii A sunt numere ntregi pozitive (naturale). Considerm elementele ordonate ale mulimii B ca un sir de numere ntregi n secven strict cresctoare. Din start, trebuie selectat un subir de m elemente. Conform rezultatului obtinut anterior, subirul trebuie s fie strict cresctor ( contrar, prin sortarea sa am obine un rezultat mai bun). Cum elementele mulimii B sunt ordonate cresctor, rmne condiia ca alegerea s fie un subir cu m componente. Evident, putem alege mai multe subiruri care ndeplinesc condiia ceruta. Din ipotez, elementele mulimii A, ordonate sunt pozitive. Dup cum se tie, produsul unui numr natural, cu un numr ntreg este cu att mai mare, cu ct acesta din urm este mai mare. n aceste condiii, subirul ales va fi alctuit din ultimele m elemente ale irului de n elemente ale mulimii B, ordonate cresctor. 2) S presupunem c elementele mulimii A sunt negative. Produsul dintre un numr negativ i altul, este cu att mai mare, cu ct acesta din urm este mai mic. Prin urmare, subirul ales este subirul format din primele m elemente ale irului B. 3) n cazul general, cnd mulimea A are k elemente negative i p elemente pozitive (k+p=m) vom alege primele k elemente ale subirului A si ultimele p elemente ale subirului B. Exemplu: a1=-3, a2=-1, a3=2; b1=1, b2=2. b3=3, b4=4; Emax= -3x1+(-1)x2+2x4=3. Alte posibiliti: E=-3x1+(-1)x2+2x3=1<3; E=-3x2+(-1)x3+2x4=-1<3; E=-3x1+(-1)x3+2x4=2<3. Conform algoritmului propus, prezentm programul de mai jos: program IoanMaxim; type vector=array[1..9] of integer; var a,b:vector; m,n,i,ind,Maxim:integer; 41
procedure sortare(var v:vector; n:integer); var gata:boolean; i,m:integer; begin repeat gata:=true; for i:=1 to n-1 do if v[i]>v[i+1] then begin m:=v[i];
v[i]:=v[i+1]; v[i+1]:=m; gata:=false end until gata; end; begin write('m='); readln(m); for i:=1 to m do begin write (' a [', i,']='); readln (a [i ] ); end;
write ('n=') ; readln(n); for i:=1 to n do begin write('b[',i,']='); readln(b[i]); end; sortare(a,m); sortare(b,n); ind:=0; while a[ind+1]<0 do ind:=ind+1; {Greedy}
Maxim:=0; for i:=1 to ind do begin writeln (a[i],'*',b[i]); Maxim:=Maxim+a[i]*b[i]; end; for i:=ind+1 to m do begin writeln(a[i],'*',b[n-m+i]); Maxim:=Maxim+a[i]*b[n-m+i]; end; writeln( 'Maxim=' ,Maxim) ; end. Eficiena algoritmului Greedy este evident. Prima idee ar fi fost s selectm toate submulimile de m elemente ale mulimii de n elemente i pe acestea s le permutm, reinnd valoarea maxim. In acest fel, aveam de probat A* mulimi. Pentru aceast problem mecanismul de alegere a fost prezentat si orice element ales este si posibil de adugat.
42
program colorare; var a:array[1..50,1..50] of integer; c:array[1..50] of integer; n,i,j,cl:integer; gasit:boolean; begin write ( 'nurnar tari: '); readln (n); for i:=1 to n do for j:=1 to i-1 do begin write('a[',i,',',j,']='); readln(a[i,j]); a[j,i]:=a[i,j] end; c[1]:=1; for i:=2 to n do begin cl:=1; repeat gasit:=false; for j:=1 to i-1 do if (a[i,j]=1) and (cl=c[j]) then gasit:=true; if not gasit then c[i]:=cl else cl:=cl+1; until not gasit; end; for i:=1 to n do writeln ('tara ',i,' culoarea ',c[i]); end.
Sritura calului
Problema, este binecunoscut, nu o mai enunm aici. Am vzut ca pentru o valoare a lui n, nu foarte mare (de exemplu 8) timpul de calcul este foarte mare. n acest paragraf vom propune o rezolvare Greedy, cu rezultate excelente (care circul ntre rezolvitori). ntruct nu cunosc demonstraia (presupunnd c ar exista), am introdus aceasta problem la Greedy euristic. n ce const algoritmul? Calul pornete din poziia 1,1. La fiecare pas alegem acea mutare care plaseaz calul ntr-o pozitie din care la urmtoarea mutare, avem ct mai puine posibiliti de a muta din nou. O explicaie (nu demonstraie) ar putea fi urmtoarea: calul ocup poziii ct mai puin accesibile (care cu alt ocazie ar fi greu de atins). Principiul este extrem de interesant i merit ncercat i pentru alte probleme.
program cal; type tabla=array[-1..25,-1..25] of integer; var t:tabla; i,j,n,min,nr,nr1,l,c,l1,c1:integer; procedure poz(l,c:integer; var nr:integer); begin nr:=0; if t[l-1,c+2]=0 then nr :=nr+1; if t[l+1,c+2]=0 then nr :=nr+1; if t[l+2,c+1]=0 then nr :=nr+1; if t[l+2,c-1]=0 then nr :=nr+1; 43
if t[l+1,c-2]=0 then nr :=nr+1; if t[l-1,c-2]=0 then nr :=nr+1; if t[l-2,c-1]=0 then nr :=nr+1; if t[l-2,c+1]=0 then nr :=nr+1; end; begin write ('n='); readln(n) ; for i:=1 to n do for j:=1 to n do t[i,j]:=0; for i:=0 to n+1 do begin t[0,i]:=1; t[-1,i]:=1; t[n+1,i]:=1; t[n+2,i]:=1;
t[i,0]:=1; t[i,-1]:=1; t[i,n+1]:=1; t[i,n+2]:=1; end; l:=l; c:=l; t[l,c]:=1; repeat min:=9; writeln(l, ' ' ,c); nr1:=nr1+1; if t[l-1,c+2]=0 then begin poz(l-1,c+2,nr); if min>nr then begin l1:=l-1; c1:=c+2; min:=nr end; end; if t[i+l,c+2]=0 then begin poz(i+l,c+2,nr); if min>nr then begin l1:=l+1; c1:=c+2; min:=nr end; end; if t[l+2,c+1]=0 then begin poz(l+2,c+1,nr); if min>nr then begin l1:=l+2; c1:=c+1; min:=nr end; end; if t[l+2,c-1]=0 then begin poz(l+2,c-1,nr); if min>nr then begin l1:=l+2; c1:=c-1; min:=nr; end; end; if t[l+1,c-2]=0 then begin poz(l+1,c-2,nr); if min>nr then begin l1:=l+1; c1:=c-2; min:=nr; end; end; if t[l-1,c-2]=0 then begin poz(l-1,c-2,nr); if min>nr then begin l1:=l-1; c1:=c-2; min:=nr end; end; if t[l-2,c-1]=0 then begin poz(l-2,c-1,nr); if min>nr then begin l1:=l-2; c1:=c-1; min:=nr; end; end; if t[l-2,c+1]=0 then begin poz(l-2,c+1,nr) ; 44
if min>nr then begin l1:=l-2; c1:=c+1; min:=nr end; end; l:=l1; c:=c1; t[l,c]:=1; until min=9; if nr1=n*n then writeln ('tentativa reusita') else writeln ('tentativa esuata') end.
Dintre toate muchiile care au extremitatea n nodul 1 se alege muchia (1,2) pentru c aceasta are costul minim (1). Dintre toate cu extremitatea n nodul 2 se alege muchia (2,3). In continuare, se aleg muchiile (3,5), (5,4), (4,1) si astfel se obine graful din figura urmtoare.
45
Acest graf (de cost 18) nu are costul minim. De exemplu, graful din figura de mai jos are costul 15.
n program, vectorul S reine nodurile selectate (S(i)=1, dac nodul a fost selectat si S(i)=0 Tn caz contrar).
program cv; type matrice=array [1..9,1..9] of integer; vector=array [1..9] of integer; var a:matrice; s:vector; n, i, j, v, vl, v2,min, cost:integer; begin write('n='); readln(n); for i:=1 to n-1 do for j:=i+1 to n do begin write ('a[',i,',',j,']='); readln(a[i,j]); a[j,i]:=a[i,j] end; for i:=1 to n do begin s[i]:=0; a[i,i]:=0 end; write('Nodul de pornire este='); readln(v); s[V]:=1; v2:=v; cost:=0; writeln(v); for i:=1 to n-1 do begin min:=30000; for j:=1 to n do if (a[v2,j]<>0) and (s[j]=0) and (min>a[v2,j]) then begin min:=a[i,j]; vl:=j end; v2:=vl; s[v2]:=1; cost:=cost+min; writeln(vl) end; cost:=cost+a[v2,v]; writeln(v); writeln('Cost total=',cost) end. O mbuntire a algoritmului se poate aduce dac se reiau calculele considernd, pe rnd, ca nod de pornire {1,2,-.,n} modificarea n acest sens a programului se propune ca tem.
Algoritmi genetici
Utilizarea acestor algoritmi este o tehnic de ultim ora, aplicabil cu succes problemelor pentru care nu se cunosc algoritmi n timp polinomial. Algoritmii generici nu sunt specifici tehnicii Greedy. Motivul pentru care au fost introdui n acest capitol este dat de faptul c se ncadreaz n categoria algoritmilor euristici. S ne imaginm situaia (din afara informaticii) n care se d un cmp de 1 km 2 presrat cu tot felul de obiecte cu diverse valori i se cere ca n timp de o or s selectm obiectul cel mai valoros. O explorare sistematica a cmpului necesit un timp cu mult mai mare. Avem o singura ans: ca dup o or s prezentm cel mai valoros obiect gsit in acest timp. Este puin probabil ca acesta s fie cel mai valoros de pe cmp, dar este cel mai valoros pe care l-am gsit. Aceeasi problem dac o rezolvm din nou, poate duce la o soluie mai bun sau...mai proast. Reinem faptul c, n cazul problemelor de acest tip, calitatea soluiei finale depinde i de ans, ns nu numai de ea. Cred c suntei de acord cu faptul c o soluie pentru o problem de acest tip depinde i de modul n care organizm cutarea n timpul pe car il avem la dispoziie. De multe ori aceasta se face respectnd criterii matematice riguroase, care sunt mprumutate din teoria probabilitilor. Revenind la exemplul nostru (didactic), am putea selecta eantioane de obiecte din diversele locuri de pe cmp, pentru ca apoi s organizm cutarea acolo unde densitatea obiectelor valoroase este mai mare. Nimeni nu ne asigur c n acest fel vom da peste obiectul cel mai valoros, dar ansele de gsi la sfritul timpului de cutare a un obiect cu a valoare apropiat de acesta sunt mai mari. 46
Renunnd la exemplul de mai sus, care nu are alt rol dect de a uura nelegerea necesitii existenei unor tehnici euristice de cutare n spaiul soluiilor, vom prezenta, in continuare algoritmii genetici, care abordeaz probleme de tipul celei de mai sus Prezentarea acestora se va face pe un exemplu (deja clasic) i anume de a calcula maximul unei funcii definite pe mulimea numerelor reale. Acesta nu este un exemplu semnificativ, ns l preferm pentru simplitatea sa. Alegerea exemplului de mai sus poate indica urmtoarea ntrebare: care este motivul pentru care nu calculm maximul functiei aa cum am fost obinuii si anume prin a da variabilei x un numr suficient de mare de valori din domeniul de definiie considerat? S nu uitm faptul ca o funcie poate avea salturi semnificative pentru variaii mici ale variabilei x (este posibil s pierdem punctele semnificative). Pe de afl parte, problema devine mult mai complicata dac funcia este definit pe R (funcie de m variabile reale). Pentru exemplul considerat nu vom scrie programul. Motivele care ne determin s procedm astfel sunt date pe de o parte de simplitatea unui astfel de program, pe de alta parte de faptul c noi considerm c o persoana dornic s neleag funcionarea acestor algoritmi are pregtirea necesara pentru a scrie un program. Fie funcia f:[a,b]->R. Se cere s determinm valoarea maxim pe care o ia funcia pe domeniul de definiie considerat. Alegerea funciei o va face cititorul. Este bine s fie aleas o funcie pentru care se cunoate valoarea maxim (pentru a verifica dac ajungem la un rezultat apropiat de acesta). De asemenea se recomand ca funcia s conin mai multe puncte de maxim pe domeniul considerat, din care numai unul este maxim absolut (pentru a verifica daca algoritmul se blocheaz sau nu ntr-un optim local). n continuare vom descrie algoritmul de tip genetic. Etapa 1. Crearea unei populaii iniiale de cromozomi. Ideea de baza n aceast faz este de a considera mai multe valori pentru variabila x (evident, toate n domeniul de definiie considerat), alese arbitrar. Prima ntrebare care poate apare este urmtoarea: care este numrul acestor valori? Un numr mai mare de valori va spori ansele de a ajunge la o soluie apropiat de cea optim, dar va mri timpul de rulare. Din acest motiv vom primi ca parametru de intrare numrul acestor valori (fie el NR). Aceasta ne ofer posibilitatea de a rula programul n condiii diferite i de a compara rezultatele obinute (procedeu mult folosit n analiza algoritmilor euristici). Toate valorile pe care le vom alege vor fi cuantificate sub form de cromozomi. Pentru nceput, vom considera un cromozom o succesiune de k poziii binare. Care este valoarea lui k pentru problema noastr? Alegerea acestei valori depinde de mulimea valorilor posibile pe care dorim s le ia variabila x. S fim mai explicii n domeniul de definiie considerat exist o infinitate de valori pe care le poate lua variabila x. Din motive lesne de neles nu le putem considera pe toate. Totui, putem considera un numr suficient de mare. S presupunem c dorim s considerm 220 valori posibile pentru x. n acest caz vom considera k=20. Cunoatem ca pe 20 de poziii binare se pot reine numere ntre 0 si 2 30-1. In exemplul nostru, un cromozom va fi dat de o succesiune de 20 poziii binare si vom avea NR cromozomi. Observaii. n exemplul considerat spaiul soluiilor va conine 2 20 elemente. Se cere acel element care maximizeaz funcia. Orice cromozom va conine o valoare a variabilei x i avem NR cromozomi. Un cromozom va cuantifica o soluie admisibila (pentru ea funcia are o anumit valoare, chiar dac nu cea optima). Alegerea celor NR cromozomi se face aleator. Pentru fiecare dintre ei se genereaz aleator o succesiune de 20 de valori 0 sau 1. Se poate citi ca parametru de intrare si numarul k. O valoare de 0 sau 1 aflat pe o anumit poziie a unui cromozom o vom numi gen. Cum convertim un cromozom ntr-o variabil real din domeniul de definiie considerat? Vom considera intervalul [a, b] mprit 1n 2k-1 pri egale. Valoarea a va fi dat de cromozomul care are pe toate pozitiile 0, iar valoarea b de acela care are pe toate poziiile 1. Restul valorilor se distribuie proporional. Exemplu: k=2. Intervalul considerat va fi mprit in 4 pri egale. Fiecare punct al diviziunii (aa se numete in matematic o astfel de mprire) va fi numerotat ntre 0 i 3. Prin mecanismul artat mai sus, am stabilit o modalitate de conversie de la un cromozom la o valoare x din domeniul de definiie. 47
Caracteristic algoritmilor genetici este faptul c opereaz simultan cu mai multe soluii admisibile. Se numete cromozom o soluie admisibil scris sub form de vector. Componentele vectorilor pot lua diverse valori (nu neaprat 0 sau 1 ca n exemplu). O component a vectorului cromozom se numete gen. Observaie. Multimea valorilor pe care le poate lua o gen este finit. Etapa 2. Obinerea soluiei. De la bun nceput precizm faptul c un astfel de algoritm se ruleaz in limita timpului disponibil. Deci operaiile corespunztoare acestei etape se repet in limita timpului disponibil. Cei NR cromozomi obinui in etapa anterioar i vom nota prin C1,C2,...CNR. 2.1 Evaluarea populaiei. Pentru fiecare cromozom din populaie se evalueaz funcia obiectiv.
Fiecare cromozom va fi convertit ntr-o variabil real din domeniul de definiie, pentru care se calculeaz funcia al crei maxim se cere. 2.2 Pentru fiecare cromozom n parte se calculeaz probabilitatea cumulativ de selecie. 2.2.1. Se nsumeaz valorile funciilor obiectiv obinute pentru fiecare cromozom n parte.
Observaii. irul q1, q2,......qNR va fi cresctor, ultima valoare va fi 1, dup cum se poate observa cu uurin. Cu ct cromozomul Cl+1 conine o valoarea pentru care se obine o valoare mai mare pentru funcia obiectiv, cu att diferena ntre ql+1, i ql este mai mare. irul probabilitilor cumulative de selecie constituie o diviziune a intervalului [0,1].
48
Observaie: dac populaia supusa reproducerii conine un numr impar de cromozomi, se renun la ultimul. ncruciarea a doi cromozomi se realizeaz astfel: - se extrage un numr natural, aleator, t ntre 0 si numrul de gene (comun al celor doi cromozomi). - se obin doi noi cromozomi astfel: - primul va conine primele t gene ale primului cromozom si ultimile k-t ale celui de al doilea cromozom: - al doilea va conine primele t gene ale celui de-al doilea cromozom intrat in reproducere si ultimele k-t gene ale primului cromozom supus reproducerii. - cei doi cromozomi obinui vor nlocui n populaie pe cei care s-au reprodus.
Mutaii simple
Populaiei obinute i se aplic, cu o probabilitate mic p, (parametru de intrare, valoare apropiat de 0) mutaii simple n care se schimb coninutul unei gene. Astfel, pentru fiecare gena a fiecrui cromozom se extrage un numr aleator r aparine (0,1). Dac numrul este mal mic dect p, coninutul genei se schimb din 0 n 1 sau invers. n urma derulrii etapei 2 se obine o nou populaie de cromozomi. Etapa se repet in limita timpului disponibil. Observaii. 1) Este bine s se rein in permanen cromozomul cel mai valoros (dei probabilitatea de a obine o populaie mai proast de cromozomi este foarte mic). 2) Nu putem s nu observm uriaa asemnare ntre algoritmii genetici i viata de toate zilele. Dei cromozomii valoroi au toate ansele s ajung n noua populaie, exist si ansa ca unii dintre ei s se piard, important nu este att cromozomul ci populaia de cromozomi. Aceasta trebuie s evolueze. 3) Nu exist reete care s conduc cu certitudine la rezultate valoroase. Programul se va rula cu diveri parametri de intrare, reinnd cal mai bun rezultat. 4) O problem important care apare la utilizarea unui algoritm de acest tip este urmtoarea: in cadrul mutaiilor ncruciate sau simple se pot obine cromozomi care nu mai sunt soluii admisibile (pentru alte probleme). Din acest motiv, de multe ori sunt necesari algoritmi, de corectare (dintr-un cromozom care nu este soluie admisibil s obinem 49
unul care este). Aplicarea unui algoritm de corectare poate duce la pierderea eficienei algoritmilor genetici. Cine ne garanteaz c dup aplicarea coreciei se obine un cromozom valoros? Din acest motiv, corectarea se va face dup criterii care in de specificul fiecrei probleme.
Probleme propuse
1) O numrtoare de la 0 la 2n-1 n codul Gray are proprietatea c oricare dou numere consecutive difer, in reprezentarea lor binar, prin exact un bit (bineneles, numerele nu se repet). Exemplu: numram de la 0 la 7 (pe trei bii); 000, 001, 011, 010. 110, 111, 101, 100 S se scrie programul care numr pe 1*N*30 bii. 2) Se consider o mulime X cu n elemente. Sa se elaboreze un algoritm eficient i s se scrie un program pentru generarea irului tuturor submulimilor lui X, A1, A2, ... astfel nct Ai+1 se obine din AL prin scoaterea sau adugarea unui element. 3) Se dau n compozitori i pentru fiecare din ei, anii de viaa. S se tipreasc numrul maxim de compozitori contemporani. 4) Se citesc de la tastatur un numr natural N i un numr real A. S se genereze o mulime de N puncte n plan, P1, P2, ... Pn astfel nct: 1. P1P2=P2P3=...=PnP=A i 2. PIPJ<A oricare ar fi 1<i<j<N. 5) Se citesc numerele naturale k si n, k<n. a) Afiai toate variantele posibile de tablouri cu n linii i n coloane care ndeplinesc urmtoarele condiii simultan: 1) Conin toate numerele de la 1 la n2 o singur dat; 2) Pe fiecare linie numerele sunt aezate n ordine cresctoare; 3) Suma elementelor de pe coloana k este minim. b) Aceeai problema pentru tablourile ndeplinind condiiile 1 i 2 de mai sus i condiia: 3) Suma elementelor de pe coloana k este maxim. Observaie: Trebuie s se afieze mai nti numrul de variante posibile i apoi se vor afia tablourile. 6) Se consider primele N caractere din codul ASCII, ncepnd cu caracterul 'a'. Cu ele se formeaz cuvinte de lungime m; fiecare cuvnt este format din caractere distincte. Cuvintele se consider n ordine lexicografic (cea din cartea de telefon). Se cer urmtoarele: 1. Fiind dat un astfel de cuvnt, s se determine numrul su de ordine; 2. S se determine cuvntul cu numrul de ordine dat. 7) Rezolvai problema comis voiajorului utiliznd algoritmi genetici.
50