44
LICEUL COLEGIUL NATIONAL “GEORGE COSBUC” MOTRU PROIECT ATESTAT INFORMATICA ELEV: SOARE AZAELA TEMA: RECURSIVITATE CLASA: A XII-A C

Recurs IV It Ate

  • Upload
    deekydz

  • View
    10

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Recurs IV It Ate

LICEUL COLEGIUL NATIONAL “GEORGE COSBUC” MOTRU

PROIECT ATESTAT INFORMATICA

ELEV: SOARE AZAELATEMA: RECURSIVITATE CLASA: A XII-A C

PROFESOR COORDONATOR: CIOCAN NATALIA

Page 2: Recurs IV It Ate

TEMA:RECURSIVITATE

2

Page 3: Recurs IV It Ate

CUPRINS

1. Despre recursivitate …………………………………………………………………………………………… pag. 42. Apelul recursiv ………………..………………………………………………………………………………… pag. 5

3. Clasificarea recursivitatii …………………………………………………….…………………………..... pag. 20 4. Divide et impera ……………..……………………………………………………………………………..... pag. 21 5. Exerciti si probleme ………………………………………………………………………………………….. pag. 22 6. Bibliografie ………………………………………………………………………………………………………. pag. 33

3

Page 4: Recurs IV It Ate

Despre RecursivitateˆIn limbajele de programare, recursivitatea e un concept fundamental care le extinde ˆın

mod esen¸tial puterea de exprimare (expresivitatea ): recursivitatea permite scrierea unor

programe care nu s-ar putea exprima doar cu no¸tiunile fundamentale de secven¸tiere ¸si

decizie prezentate pˆan˘a acum. Pentru rezolvarea practic˘a a problemelor, recursivitatea e

foarte important˘a deoarece permite s˘a descriem solu¸tia unei probleme complexe

folosind una sau mai multe probleme de acela¸si tip, dar mai simple. Ea e astfel strˆans

legat˘a de principiul descompunerii ˆın subprobleme (divide et impera ) ˆın proiectarea

solu¸tiilor.

Defini¸tie ¸si exemple

O no¸tiune e recursiv˘a dac˘a e folosit˘a ˆın propria sa defini¸tie. Cunoa¸stem un exemplu

din matematic˘a:

¸sirurile recurente , unde un termen al ¸sirului e definit printr-o rela¸tie ˆın raport cu

termenii anteriori. Exemple sunt:

• progresia aritmetic˘a: x0 = a, xn = xn−1 + p pentru n > 0.

• progresia geometric˘a: x0 = b, xn = q · xn−1 pentru n > 0.

Acestea sunt recuren¸te de ordinul I, ˆın care termenul definit depinde doar de

termenul imediat anterior. Alte recuren¸te mai complexe sunt:

• ¸sirul lui Fibonacci: F0 = F1 = 1, Fn = Fn−1 + Fn−2 pentru n ≥ 2 (un ¸sir recurent

de ordinul II)

• coeficien¸tii binomiali: C 0 = C n = 1 pentru n ≥ 0,

C k = C k

+ C k−1 pentru 0 < k < n.

Acest

exemplu define¸ste o m˘arime ˆın func¸tie de doi parametri (indici), ˆıns˘a de asemenea

printr-o rela¸tie

de recuren¸t˘a ˆın raport cu termeni de indici mai mici.

O functie recursiv˘a ˆın CˆIn toate exemplele date de ¸siruri recurente, defini¸tia con¸tine dou˘a p˘ar¸ti: cazul de

baz˘a pentru primul termen (sau primii cˆa¸tiva), ¸si rela¸tia recursiv˘a propriu-zis˘a pentru

termenul general. Aceasta sugereaz˘a c˘a putem folosi operatorul condi¸tional pentru a

exprima o defini¸tie recursiv˘a, similar cu func¸tiile definite anterior pe mai multe cazuri.

Consider˘am ca exemplu func¸tia putere (cu baz˘a real˘a ¸si exponent natural), care

poate fi privit˘a recursiv ca progresie geometric˘a, indicele fiind exponentul: puterea de

exponent n (termenul de ordin n) este baza (ra¸tia) ˆınmul¸tit˘a cu puterea de exponent n−1.

4

Page 5: Recurs IV It Ate

Grup˘am ˆın defini¸tie cazul de baz˘a (termenul ini¸tial, 1) ¸si termenul general ˆın felul

urm˘ator:

x · xn−1 altfel (n > 0)

Av˘and o defini¸tie pe dou˘a variante, func¸tia se poate exprima ˆın C cu operatorul

condi¸tional ca ¸si exemplele nerecursive dinainte:

float pwr(float x, unsigned n)

{

return n==0 ? 1

: x * pwr(x, n-1);

}

Pentru exponent am folosit tipul unsigned (cuvˆant cheie ˆın limbajul C), corespunzˆand

numerelor

naturale (nenegative); orice valoare diferit˘a de zero e deci pozitiv˘a ¸si tratat˘a corect pe

ramura ”altfel”.

Pentru scrierea func¸tiei pwr nu au fost necesare facilit˘a¸ti noi de limbaj. Esen¸tial e

doar ca limbajul s˘a permit˘a ca ˆın corpul unei func¸tii s˘a fie apelat˘a chiar aceast˘a

func¸tie (¸stim c˘a e permis˘a apelarea unei func¸tii care e deja declarat˘a ). ˆIn limbajul C,

dup˘a ce a fost scris antetul func¸tiei ca parte a defini¸tiei ei complete se cunosc deja numele

functiei, tipul ¸si parametrii ei. Antetul reprezint˘a deci o declara¸tie a func¸tiei (chiar

ˆınainte de a fi fost scris corpul ei), ceea ce e suficient pentru a permite apelul recursiv.

Mecanismul apelului de func¸tie. Apelul recursiv

De¸si scrierea acestui prim exemplu recursiv nu a necesitat elemente noi de limbaj, pentru

a ˆın¸telege corect recursivitatea sunt necesare mai multe detalii despre mecanismul apelului

de func¸tie. ˆIncepem cu

5

Page 6: Recurs IV It Ate

2

un exemplu nerecursiv: func¸tia int sqr(int x) { return x * x; } ¸si expresia sqr(3 *

sqr(2)) .

Expresia e un apel la func¸tia sqr. ˆInainte de apel, trebuie evaluat argumentul

func¸tiei, pentru a cunoa¸ste valoarea a c˘arei p˘atrat trebuie calculat. Argumentul e un

produs ˆın care unul din factori e el ˆınsu¸si o expresie apel de func¸tie. Ca atare, din

ˆıntreaga expresie sa evalueaz˘a ˆıntˆai sqr(2), apoi se

ˆınmul¸te¸ste 3 cu rezultatul (4), iar cu valoarea 12 se efectueaz˘a al doilea apel la sqr, cu

rezultatul 144.

De¸si apelul exterior la sqr con¸tine o subexpresie cu un apel la aceea¸si func¸tie,

expresia nu are caracter recursiv, ˆıntrucˆat valoarea func¸tiei sqr e calculat˘a direct din

valoarea parametrului transmis. Valoarea lui sqr(2) e necesar˘a ca argument pentru apel, nu

ˆın corpul func¸tiei ca ˆın defini¸tiile recursive.

Apelul de func¸tie Evaluarea unui apel de func¸tie e declan¸sat˘a atunci cˆand

valoarea func¸tiei e necesar˘a ˆın evaluarea unei expresii (inclusiv ˆın execu¸tia unei

instruc¸tiuni de forma expresie ; ). Ea se efectueaz˘a ˆın urm˘atorii pa¸si:

• se evalueaz˘a toate expresiile care constituie argumentele func¸tiei. Deci orice apeluri de

func¸tii care apar ˆın argumente se efectueaz˘a ˆınainte de apelul func¸tiei considerate.

• valorile argumentelor se atribuie parametrilor formali din antetul func¸tiei, cu conversiile

necesare de tip (de exemplu ˆıntreg–real). Compatibilitatea de tip a expresiilor argument

e verificat˘a deja la compilare, dac˘a se cunosc tipurile parametrilor formali – un motiv ˆın

plus pentru care se cere ca o func¸tie s˘a fie declarat˘a ˆınainte de a fi folosit˘a.

• se execut˘a corpul func¸tiei, cu parametrii formali avˆand valori ini¸tiale ca mai sus.

La ˆıntˆalnirea instruc¸tiunii return, execu¸tia func¸tiei se ˆıncheie cu valoarea ob¸tinut˘a

prin evaluarea expresiei date.

• execu¸tia programului revine la locul de apel, unde valoarea returnat˘a de func¸tie e

folosit˘a.

Transmiterea parametrilor ˆ

In limbajul C transmiterea parametrilor la func¸tii se face prin valoare :

ˆIn momentul apelului, parametrii formali iau valoarea argumentelor (care au fost evaluate);

ei nu sunt substitui¸ti cu expresiile argumentelor. ˆIn consecin¸t˘a, pentru apelul discutat

mai sus, ˆın instruc¸tiunea return se va evalua expresia 12 * 12 ¸si nu (3 * sqr(2)) * (3

* sqr(2)). Altfel spus, o func¸tie lucreaz˘a cu valori (numerice), nu cu expresii simbolice.

Expresia 3 * sqr(2) se evalueaz˘a o singur˘a dat˘a, ˆınainte de apelul sintactic exterior la

sqr, ¸si deci apelul la sqr(2) e deja ˆıncheiat ˆın momentul celui de-al doilea apel, sqr(12).

Page 7: Recurs IV It Ate

2

Acest fapt poate fi vizualizat augmentˆand func¸tia sqr cu o instruc¸tiune de tip˘arire, o

practic˘a util˘a ˆın urm˘arirea execu¸tiei programelor.

#include

<stdio.h>

int sqr(int

x)

{

printf("calculam patratul lui %d\n", x);

return x * x;

}

int main(void)

{

printf("sqr(3 * sqr(2)) = %d\n", sqr(3*sqr(2)));

return 0;

}

calculam patratul lui

2 calculam patratul lui

12 sqr(3 * sqr(2)) =

144

Rezultatul rul˘arii programului (prezentat sub textul surs˘a) eviden¸tiaz˘a ¸si pentru

apelul printf din main evaluarea argumentelor ˆınainte de ˆınceperea execu¸tiei functiei.

Evaluarea argumentului al doilea produce cele dou˘a linii tip˘arite ˆın apelurile la sqr.

Aceste linii apar ˆınainte de a scrie chiar ¸si por¸tiunea de text obi¸snuit din primul argument

(formatul), scriere ˆın care const˘a tocmai execu¸tia func¸tiei printf.

Returnarea valorilor La ˆıntˆalnirea instruc¸tiunii return, execu¸tia func¸tiei se

ˆıncheie; orice alte instruc¸tiuni care urmeaz˘a ˆın corpul func¸tiei nu se mai execut˘a.

Dac˘a execu¸tia unei func¸tii se termin˘a prin atingerea ultimei acolade } f˘ar˘a a executa o

instruc¸tiune return, iar programul utilizeaz˘a valoarea func¸tiei, efectul e nedefinit

(programul se comport˘a imprevizibil). O func¸tie care nu prevede ˆın orice situa¸tie o

valoare returnat˘a e scris˘a eronat sub aspect logic.

Page 8: Recurs IV It Ate

3

Apelul recursiv Discut˘am mecanismul apelului recursiv luˆand ca exemplu calculul lui 53

cu func¸tia putere. ˆIn apelul pwr(5, 3) (x = 5, n = 3), expresia conditional˘a conduce la

evaluarea lui 5 * pwr(5,

2). Aceasta necesit˘a un nou apel la pwr, de data aceasta cu parametrii x = 5, n = 2.

Procesul se repet˘a cu pwr(5, 1) pˆan˘a la apelul pwr(5, 0) pentru care valoarea se

calculeaz˘a direct: 1. Din acest apel se revine ˆın locul unde a fost f˘acut: la evaluarea lui 5

* pwr(5, 0) care poate fi efectuat˘a acum. Apelul pwr(5, 1) returneaz˘a valoarea 5

folosit˘a ˆın calculul 5 * pwr(5, 1) pentru valoarea lui pwr(5,

2). ˆIn final, aceast˘a valoare, 25, e folosit˘a ˆın expresia 5 * pwr(5, 2) pentru a calcula

valoarea 125 a

lui pwr(5,

3).

pwr(5, 3)

apel ↓

↑125

5 * pwr(5,

2)

apel ↓

↑25

5 * pwr(5,

1)

apel ↓

↑5

5 * pwr(5, 0)

apel ↓

↑1

1

Urm˘arind secven¸ta de apel, rezult˘a c˘a la un moment dat pot fi ˆın execu¸tie mai multe

apeluri diferite la aceea¸si func¸tie. Fiecare apel reprezint˘a o instan¸t˘a (copie) distinct˘a

a func¸tiei, cu propriile valori de parametri, cele primite ˆın momentul apelului. La fel se

ˆıntˆampl˘a ¸si ˆın calculul pe hˆartie, prin “desf˘a¸surarea” formulei de recuren¸t˘a:

pentru a calcula pe 53, ˆınlocuim pe x cu 5 ¸si n cu 3. Trebuie calculat 52: aplicˆand din

nou formula ˆınlocuim pe n cu 2; aceasta nu afecteaz˘a ˆıns˘a instan¸ta ini¸tial˘a a

problemei (53), unde n este ˆın continuare 3.

Page 9: Recurs IV It Ate

3

ˆIn exemplul dat, recursivitatea are o structur˘a liniar˘a: fiecare apel recursiv

genereaz˘a un singur nou apel, pˆan˘a la oprirea pentru cazul de baz˘a. ˆIn acel moment,

sunt active toate apelurile, ˆın num˘ar de 4. Revenirea se face ˆın ordine invers˘a fa¸t˘a de

cea de apel: din fiecare apel se revine ˆın instan¸ta care a efectuat apelul. Aici, execu¸tia

se reia ˆın contextul

dinainte de apel: adic˘a din locul ˆın care a fost f˘acut apelul (unde e folosit˘a valoarea

returnat˘a), ¸si cu acele valori ale parametrilor corespunzˆand instan¸tei respective.

Ca la orice apel de func¸tie, informa¸tia se transmite spre func¸tia apelat˘a prin

parametri, ¸si ˆınapoi spre locul de apel (func¸tia apelant˘a ) prin rezultat. ˆIn exemplul dat se

creeaz˘a un lan¸t ˆın care la revenire, valoarea returnat˘a de fiecare apel e folosit˘a ˆın

instan¸ta apelant˘a pentru calculul propriului rezultat, care e transmis mai departe ˆınapoi

spre locul de apel. Rezultatul final e astfel efectul unui calcul ˆın care cˆate un pas e efectuat

de fiecare din instan¸tele apelate. Aceasta e esenta recursivit˘a¸tii ¸si ˆın acela¸si timp

puterea ei ˆın rezolvarea de probleme: ea permite exprimarea indirect˘a a solu¸tiei prin pa¸si

simpli, din aproape ˆın aproape, f˘ar˘a a necesita formularea direct˘a a unei solu¸tii

complexe.

Elementele unei defini¸tii recursive

ˆIntr-o defini¸tie recursiv˘a corect˘a se pot identifica urm˘atoarele

componente:

• Cazul de baz˘a. Trateaz˘a situa¸tiile (cele mai simple) ˆın care no¸tiunea recursiv˘a e

definit˘a direct.

Exemple: pentru ¸sirurile recurente, primul termen (sau mai mul¸ti, la recuren¸tele de

ordin > 1)

pentru liste, cea vid˘a, sau cea cu un element; pentru expresii, constantele ¸si

identificatorii

• Rela¸tia de recuren¸t˘a : partea propriu-zis recursiv˘a a defini¸tiei, ˆın care no¸tiunea

definit˘a apare ¸si ˆın corpul defini¸tiei. Exemple: formulele de recuren¸t˘a pentru

¸siruri; ramura de defini¸tie ”o list˘a e un element urmat de o list˘a”; pentru expresii,

variantele de defini¸tie cu expresie ˆıntre paranteze, apel de func¸tii (cu parametri

expresii) ¸si cele cu expresii compuse cu operatori

• Terminarea recursivit˘a¸tii. Dac˘a defini¸tia urmeaz˘a o ramur˘a care con¸tine din nou

no¸tiunea definit˘a, atunci defini¸tia trebuie aplicat˘a din nou. O no¸tiune e corect

definit˘a recursiv dac˘a acest proces se opre¸ste ˆıntotdeauna. Pentru a fi riguroas˘a, o

Page 10: Recurs IV It Ate

3

defini¸tie recursiv˘a trebuie ˆınso¸tit˘a de o demonstratie c˘a aplicarea defini¸tiei se

opre¸ste dup˘a un num˘ar finit de pa¸si.

Rezult˘a c˘a o defini¸tie recursiv˘a nu poate fi corect˘a f˘ar˘a un caz de baz˘a, pentru

c˘a nu se ajunge niciodat˘a la un punct unde no¸tiunea poate fi definit˘a direct. Cazul de

baz˘a ¸si rela¸tia recursiv˘a sunt de fapt alternative ale aceleia¸si defini¸tii. Acest lucru e

explicit ˆın regulile sintactice care definesc

Page 11: Recurs IV It Ate

4

construc¸tii de limbaj cum ar fi expresiile. Pentru un ¸sir recurent putem eviden¸tia

aceasta folosind

. a n = 0

acolade: xn

= x

n−1

+ p n > 0 ¸si transcrie apoi u¸sor ˆın program.

Pentru terminarea recursivit˘a¸tii, cel mai uzual argument folose¸ste o m˘asur˘a

(cantitate) care de-

scre¸ste la fiecare aplicare a defini¸tiei, pˆan˘a atinge o valoare pentru care defini¸tia e

dat˘a direct. La

¸siruri recurente, aceast˘a cantitate e chiar indicele n al termenului

general xn .

Alte exemple de recursivitate

ˆIn¸siruirea v˘azut˘a recursiv S¸i notiuni din afara matematicii, uneori foarte simple,

se preteaz˘a la defini¸tii recursive. Un tipar de defini¸tii des ˆıntˆalnit se bazeaz˘a pe faptul

c˘a itera¸tia (repeti¸tia) poate fi definit˘a prin recursivitate. Putem defini astfel:

Un ¸sir (secven¸t˘a, list˘a) e fie un element, fie un ¸sir urmat de

un element.

Uneori e util s˘a includem ˆın defini¸tie ¸sirul vid, care apare natural ˆın diverse opera¸tii (ca

element neutru la concatenare; la ini¸tializarea sau dup˘a ¸stergerea tuturor elementelor unei

liste, etc.):

Un ¸sir e fie un ¸sir vid, fie un element urmat de

un ¸sir.

Cele dou˘a variante difer˘a atˆat prin cazul de baz˘a (un element sau zero), cˆat ¸si prin

pozi¸tia no¸tiunii recursive ˆın defini¸tie: prima variant˘a e recursiv˘a la stˆanga

(no¸tiunea definit˘a recursiv ”¸sir” e prima

ˆın rescrierea ”¸sir urmat de un element”), iar a doua e recursiv˘a la dreapta , deoarece

ˆın expandarea

”element urmat de un ¸sir” no¸tiunea definit˘a ”¸sir” e pe ultima pozi¸tie. Tiparele de

recursivitate la stˆanga

¸si la dreapta conduc la prelucr˘ari diferite ˆın program, pe care le studiem ¸si compar˘am

ˆın continuare.

Page 12: Recurs IV It Ate

4

Recursivitatea ˆın sintaxa limbajelor Recursivitatea apare natural ˆın definirea

precis˘a a sintaxei limbajelor de programare. Multe elemente de limbaj au ˆın componen¸t˘a

repeti¸tia, care poate fi exprimat˘a recursiv. Astfel, antetul unei func¸tii poate fi definit (ˆın

limita celor prezentate pˆan˘a acum) ca:

antet-func¸tie ::= tip identificator ( parametri )

parametri ::= void | lista-param

lista-param ::= tip identificator | tip identificator , lista-param

Am folosit conven¸tional simbolurile ::= pentru defini¸tie ¸si | pentru alternativ˘a .

Acest mod de a descrie regulile sintactice, adic˘a gramatica unui limbaj se nume¸ste form˘a

Backus-Naur (BNF).

Putem defini recursiv ¸si alt˘a no¸tiune fundamental˘a, expresia. Din cele prezentate

pˆan˘a acum:

expresie ::= constant˘a | identificator | identificator ( argumente )

| - expresie | expresie operator-binar expresie

| expresie ? expresie : expresie | ( expresie )

argumente ::= s | lista-argumente

lista-argumente ::= expresie | expresie , lista-argumente

unde s denot˘a conven¸tional alternativa cu con¸tinut vid (f˘ar˘a simboluri de limbaj), aici

pentru apeluri de forma functie(), f˘ar˘a argumente ˆıntre paranteze.

Pentru o defini¸tie riguroas˘a, trebuie s˘a preciz˘am c˘a orice construc¸tie de limbaj

trebuie definit˘a printr-un num˘ar finit de aplic˘ari ale regulilor. Astfel, -(2 + 3) e o

expresie: se pot aplica pe rˆand regulile expresie ::= - expresie, expresie ::= ( expresie ),

expresie ::= expresie + expresie, ¸si de dou˘a ori regula expresie ::= constant˘a .

Dou˘a tipare de calcul recursiv

S˘a examin˘am un alt exemplu tipic de calcul recursiv,

factorialul.

. 1 n = 0

Avem: n! =

¸si putem transcrie direct ˆın C: (n − 1)! · n altfel

(n > 0)

Page 13: Recurs IV It Ate

unsigned fact(unsigned n)

{

return n == 0 ? 1 : fact(n-1) * n;

}

ˆIn evaluarea lui fact pentru n > 0, ultima opera¸tie efectuat˘a e ˆınmul¸tirea cu n,

restul calculelor fiind efectuate anterior ˆın apelul fact(n-1) (¸si celelalte apeluri

recursive care rezult˘a din acesta). Ordinea calculelelor e indicat˘a de paranteze ˆın

expresia n! = ((((1 · 1) · 2) · . . .) · (n − 1)) · n.

Expandˆand defini¸tia pentru n ≥ 2, ob¸tinem n! = ((n − 2)! · (n − 1)) · n. ˆInainte de

evaluarea lui

fact(n-2) ¸stim c˘a ˆın produs apar atˆat n cˆat ¸si n-1, dar a¸sa cum e scris˘a func¸tia, nu

se efectueaz˘a direct

ˆınmul¸tirea lor: se apeleaz˘a ˆıntˆai fact(n-2), rezultatul e ˆınmul¸tit cu n-1 ˆın cadrul

apelului fact(n-1),

¸si doar ˆın final se face ˆınmul¸tirea cu n, pentru rezultatul lui

fact(n) .

Pornind de la aceast˘a observa¸tie, rescriem factorialul folosind asociativitatea

ˆınmul¸tirii, pentru a efectua cˆat mai multe ˆınmul¸tiri ˆındat˘a ce factorii devin

disponibili: n! = 1 · (2 · (. . . · ((n − 1) · n)))

Transcriind ˆın C, am dori s˘a efectu˘am ˆınmul¸tirea (n-1)*n ˆın cadrul apelului

func¸tiei pentru n-1,

ˆınainte de a calcula recursiv factorialul pentru n-2. ˆIn apelul pentru n-2 s-ar putea

ˆınmul¸ti apoi rezultatul lui (n-1)*n cu n-2, etc. Pentru aceasta, avem nevoie s˘a

transmitem la fiecare apel recursiv pe lˆang˘a valoarea lui n ¸si rezultatul ˆınmul¸tirilor deja

efectuate. Ob¸tinem astfel:

unsigned fact_r(unsigned n, unsigned

r)

{

return n == 0 ? r : fact_r(n-1, r * n);

}

13

Page 14: Recurs IV It Ate

Parametrul r reprezint˘a rezultatul par¸tial calculat. La fiecare apel recursiv, el e

ˆınmul¸tit cu valoarea

curent˘a a lui n ¸si rezultatul e transmis mai departe la apelul pentru n-1. Cˆand n==0, toate

ˆınmul¸tirile au fost deja efectuate; rezultatul se g˘ase¸ste acumulat ˆın r ¸si poate fi

returnat. Pe ramura recursiv˘a, la revenire nu se mai efectueaz˘a nici un calcul: valoarea

provenit˘a din apelul recursiv e deja rezultatul complet ¸si e returnat˘a direct mai departe

la apelant.

Din primul apel pentru n, rezultatul par¸tial pe care dorim s˘a-l transmitem spre apelul

pentru n-1 e tot valoarea n. Deci, ini¸tial, r ar trebui s˘a fie 1, ¸si pentru a calcula pe n!

vom apela fact_r(n, 1) . De fapt, am definit o func¸tie mai general˘a: fact_r(n, r)

calculeaz˘a pe r · n! .

Pentru a nu complica utilizatorul cu parametrul suplimentar datorat modului de calcul,

definim func¸tia fact2 cu un singur parametru. Aceasta doar “ˆımpacheteaz˘a” apelul

ini¸tial fact_r(n, 1) :

unsigned fact2(unsigned n) { return fact_r(n,

1); }

Pentru factorial, putem alege ˆıntre cele dou˘a variante de scriere. ˆIn prima, calculul

se face la

revenirea din apelurile recursive, iar acestea nu au nevoie de vreun rezultat par¸tial

calculat anterior.

ˆIn a doua, rezultatul par¸tial e transmis ˆın adˆancime ca parametru, ¸si actualizat ˆınainte

de fiecare apel recursiv.

Exist˘a ˆıns˘a situa¸tii ˆın care parte din prelucrare se efectueaz˘a ˆınainte de fiecare apel

recursiv, fiind

necesar s˘a transmitem “ˆın jos” la ˆınaintarea ˆın recursivitate valori ce vor fi folosite

ulterior ˆın calcule.

Inversarea cifrelor unui num˘ar Scriem o func¸tie care ia un ˆıntreg f˘ar˘a semn ¸si-

l transform˘a ˆın num˘arul cu acelea¸si cifre zecimale dar ˆın ordine invers˘a. Scriem solu¸tia

pornind de la un exemplu: 1472. Ultima cifr˘a, 2, devine prima cifr˘a a rezultatului. Punem

ultima cifr˘a r˘amas˘a din 147 dup˘a 2, obtinˆand

27 = 2 · 10 + 7. Din num˘arul r˘amas, 14, plas˘am ultima cifr˘a dup˘a 27, ob¸tinˆand 27 ·

14

Page 15: Recurs IV It Ate

10 + 4 = 274, etc.

ˆIn cuvinte, pasul recursiv de prelucrare poate fi exprimat: rezultatul invers˘arii, dac˘a

a mai r˘amas de inversat n, iar din inversarea ultimelor cifre s-a obtinut deja v, e acela¸si cu

rezultatul invers˘arii lui n/10, cu valoarea intermediar˘a 10 · v + n mod 10. De¸si enun¸tul

problemei are un singur parametru, solu¸tia recursiv˘a ob¸tinut˘a manipuleaz˘a dou˘a

cantit˘a¸ti, deci scriem o functie recursiv˘a cu doi parametri. Prelucrarea se opre¸ste cˆand n

e 0 (nu mai sunt cifre de inversat), iar ini¸tial, v e valoarea f˘ar˘a nici o cifr˘a, deci tot 0;

astfel, pentru prima cifr˘a c, expresia 10 · 0 + c d˘a valoarea dorit˘a c.

#include

<stdio.h>

unsigned revnum_r(unsigned n, unsigned

r)

{

return n == 0 ? r : revnum_r(n / 10, 10 * r + n % 10);

}

unsigned revnum(unsigned n) { return revnum_r(n, 0); }

int main(void)

{

printf("%u\n", revnum(1472)); // %u pt. tiparire

unsigned return 0;

}

Dorim ca solu¸tie o func¸tie cu un singur parametru, ca ˆın enun¸t, pentru a nu complica

utilizatorul cu

un parametru suplimentar pentru valoarea intermediar˘a. Am scris astfel functia revnum

care apeleaz˘a func¸tia recursiv˘a revnum r(n, 0) cu valoarea ini¸tial˘a necesar˘a pentru al

doilea parametru.

Cel mai mare divizor comun Algoritmul lui Euclid pentru calculul celui mai mare

divizor comun a doi ˆıntregi pozitivi e un exemplu clasic de algoritm exprimat recursiv, ˆın

care o problem˘a e rezolvat˘a prin reducerea la o instan¸t˘a mai simpl˘a a aceleia¸si

probleme. Exprimat informal, algoritmul e:

– dac˘a numerele sunt egale, rezultatul e chiar valoarea lor

15

Page 16: Recurs IV It Ate

comun˘a

– altfel, se scade cel mai mic num˘ar din cel mai mare, ¸si se repet˘a procedura cu

noile numere.

Exprimarea din urm˘a (“se repet˘a procedura”) indic˘a abordarea recursiv˘a: solu¸tia

se ob¸tine re- zolvˆand aceea¸si problem˘a pentru valori noi ale numerelor (mai mici, deci

dup˘a un num˘ar finit de pa¸si se ajunge la cazul de baz˘a).

Putem scrie deci pe cazuri:

cmmdc(a, b) =

a a = b

cmmdc(a − b, b) a > b

cmmdc(a, b − a) altfel (a

< b)

¸si transcrie direct ˆın C:

unsigned cmmdc(unsigned a, unsigned b)

{

return a == b ? a

: a > b ? cmmdc(a - b, b)

: cmmdc(a, b - a);

}

De¸si am transpus direct din cuvinte ˆın formula recursiv˘a ¸si apoi ˆın cod, a r˘amas

netratat un aspect:

enun¸tul ini¸tial e dat pentru numere pozitive, iar tipul unsigned permite ¸si valoarea 0.

Apelarea (chiar

¸si accidental˘a) a func¸tiei scrise mai sus cu un parametru nul va duce la o secven¸t˘a

infinit˘a de apeluri recursive, deoarece sc˘azˆand 0 cel˘alalt num˘ar nu se modific˘a ¸si

relu˘am acela¸si apel (pˆan˘a cˆand, ˆın func¸tie de mediul de rulare, programul se va

termina probabil for¸tat epuizˆand resursele de memorie).

Este important ca func¸tiile pe care le scriem s˘a fie robuste ¸si s˘a nu produc˘a erori

neprev˘azute ¸si catas- trofale. Ca atare, rescriem func¸tia ¸tinˆand cont c˘a 0 e divizibil cu

orice num˘ar, ¸si deci cmmdc(a, 0) = cmmdc(0, a) = a:

unsigned cmmdc(unsigned a, unsigned

b)

{

16

Page 17: Recurs IV It Ate

k=0

return b == 0 ? a : a == 0 ? : b

: a > b ? cmmdc(a - b, b)

: cmmdc(a, b - a);

}

ˆIn aceast˘a scriere, cazul a = b ƒ= 0 va intra pe ultima ramur˘a, avˆand ca efect apelul

cmmdc(a, 0)

care va returna a.

Calculul recursiv al seriilor

Calculul sumei par¸tiale a unei serii se preteaz˘a natural la o exprimare recursiv˘a. Not˘and

cu tn termenul

general, ¸si sn =

.n

tk , ob¸tinem imediat pentru termenul general:

sn = sn

−1 +

tn

(pentru n ≥ 1),

¸si

(n > 0)

Avˆand o formul˘a de calcul direct pentru termenul tn al seriei (exprimat deci ca func¸tie

de n), putem transforma direct formula de mai sus ˆıntr-o func¸tie recursiv˘a s, care

apeleaz˘a pentru calcul func¸tia t. Pentru exemplul simplu tn = 1/n (pentru n > 1, iar t0 =

0), putem scrie urm˘atorul program:

#include <math.h>

#include <stdio.h>

double t(unsigned n)

{

return n == 0 ? 0 : 1.0/n;

}

double s(unsigned n)

{

return n == 0 ? t(0) : s(n-1) + t(n);

}

17

Page 18: Recurs IV It Ate

int main(void)

{

printf("Constanta lui Euler e aprox. %f\n", s(1000)-log(1000));

return 0;

}

Din matematic˘a, ¸stim c˘a seria .

n 1/n are sum˘a infinit˘a, ¸si cre¸ste aproximativ ca

logaritmul natural

al lui n. Mai

precis, n

lim (.

1/k − ln n) = γ c 0.5772...

n→∞ k=1

Aproxima¸tia calculat˘a de program are primele 3 zecimale exacte.

Tipul double folosit ˆın program reprezint˘a, ca ¸si float, numere reale, dar cu precizie

mai bun˘a (de aici ¸si numele), ¸si e recomandabil ˆın calcule pentru a mic¸sora acumularea

erorilor de rotunjire. Este tipul standard folosit de func¸tiile matematice de bibliotec˘a

declarate ˆın math.h (cum e ¸si func¸tia log pentru logaritmul natural) ¸si tipul implicit

pentru constantele reale. Scrierea 1.0/n pentru termenul matematic 1/n e necesar˘a pentru

a ob¸tine ˆımp˘ar¸tire real˘a : operandul 1.0 fiind real, va fi convertit implicit ¸si

ˆıntregul n. Altfel, 1/n ar fi ˆınsemnat ˆımp˘ar¸tire ˆıntreag˘a, cu rest, ¸si valoarea 0 pentru

n > 1.

Desigur c˘a ˆın acest program s-ar fi putut scrie mai simplu, direct

double s(unsigned n)

{

return n == 0 ? 0 : s(n-1) + 1.0/n;

}

Varianta prezentat˘a eviden¸tiaz˘a ˆıns˘a forma general˘a a func¸tiei s pentru suma

exprimat˘a recursiv,

¸si programul poate fi adaptat la alt˘a serie prin simpla ˆınlocuire a func¸tiei t.

E valabil˘a aceea¸si observa¸tie general˘a f˘acut˘a ˆıntˆai pe exemplul factorialului despre

18

Page 19: Recurs IV It Ate

cele dou˘a variante de definire a calculului recursiv. Func¸tia s a¸sa cum a fost scris˘a mai

sus efectueaz˘a calculul la revenirea din recursivitate, ultima adunare fiind sn−1 + tn,

corespunzˆand unei grup˘ari a calculelor de forma: sn = (((t0 + t1) + t2) + . . .) + tn.

Cealalt˘a alternativ˘a o constituie transmiterea ca parametru suplimentar a unui

rezultat par¸tial

deja calculat (ini¸tial zero), la care se acumuleaz˘a ˆın fiecare pas termenul curent. Astfel,

termenii sunt aduna¸ti efectiv ˆın ordine invers˘a: sn = t0 + (t1 + (. . . + (tn−1 + tn))).

Func¸tia se scrie:

double s2(unsigned n, double res)

{

return n == 0 ? res + t(0) : s2(n-1, res + t(n));

}

19

Page 20: Recurs IV It Ate

Calculul de aproxim˘ari cu o precizie dat˘a

Majoritatea exemplelor de calcul numeric recursiv date pˆan˘a acum au aceea¸si structur˘a: se

calculeaz˘a termenul unui ¸sir definit printr-o rela¸tie de recuren¸t˘a, iar num˘arul de apeluri

recursive necesar pentru calcul e determinat de ordinul (indicele) termenului, dat la primul apel.

ˆIn matematic˘a ¸si practic˘a ˆıntˆalnim ˆıns˘a adesea cazuri se dore¸ste un calcul efectuat cu

o anumit˘a

precizie: se genereaz˘a o secven¸t˘a de aproxim˘ari, iar cˆand aproximarea curent˘a a atins

precizia dorit˘a, calculul se opre¸ste.

D˘am ca exemplu o metod˘a de aproxima¸tie pentru calculul r˘ad˘acinii p˘atrate √

x. O

cunoscut˘a

formul˘a matematic˘a (poate fi derivat˘a din metoda lui Newton, dar ˆın acest caz particular e

mult mai veche) ne d˘a secven¸ta de aproxim˘ari: an+1 = (an + x/an)/2, cu o aproximare

ini¸tial˘a arbitrar˘a (de exemplu a0 = 1).

Elementul cheie al solu¸tiei e formularea problemei pentru a-i identifica ¸si scoate ˆın

eviden¸t˘a car-

acterul recursiv: mai precis, parametrii ¸si cazul de baz˘a (oprirea din secven¸ta de apeluri

recursive). Condi¸tia de oprire nu e dat˘a de aproximarea ini¸tial˘a, ci, dup˘a cum rezult˘a din

enun¸t, de precizia atins˘a. Pentru a o calcula, avem nevoie de aproximarea curent˘a, care devine

astfel parametru al problemei (cu valoarea ini¸tial˘a dat˘a de a0).

Putem atunci enun¸ta solu¸tia ˆın cuvinte ˆın felul urm˘ator: func¸tia c˘autat˘a returneaz˘a o

aproxima¸tie de precizie dat˘a a lui √

x, ¸stiind o aproxima¸tie curent˘a an dat˘a de

asemenea ca parametru. Dac˘a

aproxima¸tia curent˘a e suficient de bun˘a, poate fi returnat˘a (cazul de baz˘a). Dac˘a nu,

rezultatul va fi dat de aceea¸si func¸tie de calcul, apelat˘a tot pentru x, dar cu o aproxima¸tie

curent˘a mai bun˘a, dat˘a de an+1.

#include <math.h>

#include <stdio.h>

double rad(double x, double a)

{

20

Page 21: Recurs IV It Ate

return fabs(a - x/a) < 1e-6 ? a : rad(x, .5*(a + x/a));

}

int main(void)

{

printf("%f\n", rad(2, 1));

return 0;

}

Func¸tia standard fabs, declarat˘a ˆın math.h returneaz˘a valoarea absolut˘a a unui num˘ar real

(double), spre deosebire de func¸tia abs (din stdlib.h) aplicabil˘a doar la numere ˆıntregi.

Logica compara¸tiei

este urm˘atoarea: a ¸si x/a se afl˘a ˆıntotdeauna de o parte ¸si de alta a valorii exacte √

x.

Deci, dac˘a

intervalul dintre ele e mai mic decˆat precizia dorit˘a (ˆın exemplul dat, 10−6), oricare din ele

va fi o aproxima¸tie de precizie suficient˘a.

Clasificarea recursivitatii Recursivitate liniară.

Este forma cea mai simplă de recursivitate şi constă dintrun singur apel recursiv. De

exemplu pentru calculul factorialului avem:

f(0)=1 cazul de bază

f(n)=n*f(n-1) cazul general

Se vede că dacă timpul necesar execuţiei cazului de bază este a şi a cazului general este b, atunci timpul

total de calcul este: a+b*(n1) = O(n), adică avem complexitate liniară.

Recursivitate binară.Recursivitatea binară presupune existenţa a două apeluri recursive. Exemplul tipic îl constituie

şirul lui Fibonacci:

long fibo(int n){

if(n<=2) return 1;return fibo(n1) +fibo(n2);

21

Page 22: Recurs IV It Ate

}

Funcţia este foarte ineficientă, având complexitate exponenţială, cu multe apeluri recursive repetate.

Se poate înlocui dublul apel recursiv printrun singur apel recursiv. Pentru aceasta ţinem seama că dacă a,

b şi c sunt primii 3 termeni din şirul Fibonacci, atunci calculul termenul situat la distanţia n în raport cu

a, este situat la distanţa n1 faţă de termenul următor b.

Necesitatea cunoaşterii termenului următor impune prezenţa a doi termeni în lista de parametri.

Metoda divizării (Divide et Impera).

Un algoritm recursiv obţine soluţia problemei prin rezolvarea unor instanţe mai mici ale aceleeaşi

probleme.

se împarte problema în subprobleme de aceeaşi natură, dar de dimensiune mai scăzută

se rezolvă subproblemele obţinânduse soluţiile acestora

se combină soluţiile subproblemelor obţinânduse soluţia problemei

Ultima etapă poate lipsi (în anumite cazuri), soluţia problemei rezultând direct din soluţiile

subproblemelor.

Procesul de divizare continuă şi pentru subprobleme, până când se ajunge la o dimensiune suficient

de redusă a problemei care să permită rezolvarea printro metodă directă.

Metoda divizării se exprimă natural în mod recursiv: rezolvarea problemei constă în rezolvarea unor

instanţe ale problemei de dimensiuni mai reduse

void DivImp(int dim, problema p, solutie *s){int dim1, dim2; problema p1, p2; solutie s1, s2; if(dim > prag){Separa(dim,p,&dim1,&p1,&dim2,&p2); DivImp(dim1, p1, &s1);

22

Page 23: Recurs IV It Ate

DivImp(dim2, p2, &s2); Combina(s1, s2, s);

}elseRezDirect(dim, p, s);

}

Pentru evaluarea complexităţii unui algoritm dezvoltat prin metoda divide et impera se rezolvă o ecuaţie

recurentă având forma generală: T(n)=a.T(n/b) + f(n), în care a >= 1, reprezintă numărul

subproblemelor, iar n/b este dimensiunea subproblemelor, b > 1.

f(n) reprezintă costul divizării problemei în subprobleme şi a combinării soluţiilor

subproblemelor pentru a forma soluţia problemei.

Soluţia acestei ecuaţii recurente este dată de teorema master

Probleme si exercitiiSe citeste un vector cu n elemente numere naturale (n<=100). Sa se inlocuiasca fiecare element al vectorului cu suma cifrelor cu aceeasi paritate ca si indicele elementului. Indexarea elementelor incepe de la 1. Se vor scrie si folosi functii recursive pentru: - citirea vectorului - afisarea vectorului - calculul sumei cifrelor de o anumita paritate - inlocuirea ceruta Exemplu: Pentru datele de mai jos 7 223 435 6667 24 55 662 122 Sirul rezultat este 3 4 7 6 10 14 1 REZOLVARE:#include <iostream>

using namespace std;

void citire(int A[], int n)23

Page 24: Recurs IV It Ate

{

if(n>0)

{

citire(A,n-1);

cin>>A[n];

}

}

int sumcifp(int n, int p)

{

if(n==0) return 0;

else if(n%2==p) return sumcifp(n/10,p)+n%10;

else return sumcifp(n/10,p);

}

void inlocuire(int A[], int n)

{

if(n>0)

{

inlocuire(A,n-1);

A[n]=sumcifp(A[n],n%2);

}

}

void afisare(int A[], int n)

{

if(n>0)

{

afisare(A,n-1);

cout<<A[n]<<" ";

}

}

int main()

{

int A[101],n;

24

Page 25: Recurs IV It Ate

cin>>n;

citire(A,n);

inlocuire(A,n);

afisare(A,n);

return 0;

}

Se citeste un numar natural n cu cel mult 9 cifre. Afisati numarul de cifre distincte ale lui n. Se vor folosi exclusiv subprograme recursive. Exemplu: Pentru n=38837 se afiseaza 3 (cifrele distinte sunt 3,7 si 8). REZOLVARE:#include <iostream>

using namespace std;

int apcif(int n, int c)

{//numarul de aparitii ale lui c in n

if(n<=9) return n==c;

else if(n%10==c) return apcif(n/10,c)+1;

else return apcif(n/10,c);

}

int dist(int n, int c)

{//numara cifrele distincte ale lui n

if(c==-1) return 0;

else if(apcif(n,c)) return dist(n,c-1)+1;

else return dist(n,c-1);

}

int main()

{

int n;

cin>>n;

cout<<dist(n,9);

25

Page 26: Recurs IV It Ate

return 0;

}

sau

#include <iostream>

using namespace std;

int apcif(int n, int c)

{//verific daca c apare in n

if(n<=9) return n==c;

else if(n%10==c) return 1;

else return apcif(n/10,c);

}

int nrdist(int n)

{//numara cifrele distincte ale lui n

if(n==0) return 0;

else if(apcif(n/10,n%10)==0) return nrdist(n/10)+1;

else return nrdist(n/10);

}

int main()

{

int n;

cin>>n;

cout<<nrdist(n);

return 0;

}

Se citeste un numar natural n (n<=20). Afisati un desen format din caracterul * ca in exemplul de mai jos. Se vor folosi exclusiv subprograme recursive. Exemplu: 

Pentru n=3 se afiseaza 26

Page 27: Recurs IV It Ate

    *   *** *****   ***     * REZOLVARE:#include <iostream>

using namespace std;

void linie(int n, char c)

{

if(n>0)

{

linie(n-1,c);

cout<<c;

}

}

void sus(int n, int r)

{

if(r<=n)

{

linie(n-r,' ');

linie(2*r-1,'*');

cout<<endl;

sus(n,r+1);

}

}

void jos(int n, int r)

{

if(r<=n)

{

jos(n,r+1);

linie(n-r+1,' ');

linie(2*r-1,'*');

27

Page 28: Recurs IV It Ate

cout<<endl;

}

}

int main()

{

int n;

cin>>n;

sus(n,1);

jos(n-1,1);

return 0;

}

Se citeste un numar natural n. Sa se descompuna ca suma de puteri crescatoare ale lui 2. Se vor folosi doar prelucrari/calcule realizate cu ajutorul functiilor implementate recursiv. Exemplu: Pentru n=84 va afisa 4 16 64 (84 se descompune ca 4+16+64) REZOLVARE:#include <iostream>

using namespace std;

unsigned int pm2(int n, unsigned int p)

{

if(p*2>n) return p;

else return pm2(n,p*2);

}

void puteri2(int n)

{

if(n>0)

{

unsigned int p=pm2(n,1);

puteri2(n-p);

cout<<p<<" ";

}

}

28

Page 29: Recurs IV It Ate

int main()

{

int n;

cin>>n;

puteri2(n);

return 0;

}

sau

#include <iostream>

using namespace std;

void puteri2(int n, int p)

{

if(n>0)

{

if(n%2==1)

cout<<p<<" ";

puteri2(n/2,p*2);

}

}

int main()

{

int n;

cin>>n;

puteri2(n,1);

return 0;

}

O harta este data intr-o matrice n*m in care valorile 1 reprezinta uscatul, iar valorile 0 reprezinta apa. Doua zone de uscat se considera ca fac parte din acelasi continent daca sunt vecine pe linie sau pe coloana. Determinati numarul de continente de pe

29

Page 30: Recurs IV It Ate

harta si care este aria (numarul de valori de 1) maxima dintre ariile continentelor. Exemplu: harta.in 6 6 1 1 1 0 1 0 0 0 1 0 1 1 1 1 1 0 0 0 0 1 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 harta.out 4 (numarul de continente) 8 (aria continentului din stanga-sus)REZOLVARE:#include <fstream>

using namespace std;

ifstream fin("harta.in");

ofstream fout("harta.out");

int a[102][102];

void fill(int a[102][102], int n, int m, int i, int j, int c)

{

a[i][j]=c;

if(a[i-1][j]==1) fill(a,n,m,i-1,j,c);//sus

if(a[i][j+1]==1) fill(a,n,m,i,j+1,c);//dreapta

if(a[i+1][j]==1) fill(a,n,m,i+1,j,c);//jos

if(a[i][j-1]==1) fill(a,n,m,i,j-1,c);//stanga

}

int main()

{

int n,m,c=0;

fin>>n>>m;

for(int i=1;i<=n;i++)

for(int j=1;j<=m;j++)30

Page 31: Recurs IV It Ate

fin>>a[i][j];

for(int i=1;i<=n;i++)

for(int j=1;j<=m;j++)

if(a[i][j]==1)

{

c++;

fill(a,n,m,i,j,c+1);

}

for(int i=1;i<=n;i++)

{

for(int j=1;j<=m;j++) fout<<a[i][j]<<" ";

fout<<endl;

}

fout<<c<<endl;

int max=0;

for(int cul=2;cul<=c+1;cul++)

{

int s=0;

for(int i=1;i<=n;i++)

for(int j=1;j<=m;j++)

if(a[i][j]==cul) s++;

if(s>max) max=s;

}

fout<<max;

fin.close();

fout.close();

return 0;

}

Scrieti o functie recursiva litera cu doi parametri s si c unde s e un cuvant, iar c este o litera si care returneaza de cate ori apare litera c in cuvantul s. b) Scrieti o functie recursiva litere cu trei parametri s, n si c unde s e un vector ce

31

Page 32: Recurs IV It Ate

memoreaza cel mult 20 de cuvinte, n e numar natural reprezentand numarul de cuvinte din vectorul s, iar c este o litera si care returneaza de cate ori apare litera c in total in cele n cuvinte din vectorul s (va folosi functia litera). c) Se citeste un numar n si un vector s de n cuvinte. Folosind functia litere, determinati si afisati literele care apar de un numar maxim de ori in cuvintele din vectorul s. Exemplu: n=3, s={"ana", "are", "mere"} => a e

REZOLVARE:

#include <iostream>

#include <cstring>

using namespace std;

int litera(char s[21], char c)

{

if(strlen(s)==0) return 0;

else if(s[0]==c) return 1+litera(s+1,c);

else return litera(s+1,c);

}

int litere(char s[21][21], int n, char c)

{

if(n==0) return 0;

else return litera(s[n],c)+litere(s,n-1,c);

}

int main()

{

char s[21][21],map=0;

int n;

cin>>n;

for(int i=1;i<=n;i++)

cin>>s[i];

for(char c='a';c<='z';c++)

if(litere(s,n,c)>map) map=litere(s,n,c);

for(char c='a';c<='z';c++)32

Page 33: Recurs IV It Ate

if(litere(s,n,c)==map) cout<<c<<" ";

return 0;

}

33