Sunteți pe pagina 1din 33

Programarea calculatoarelor

Limbajul C

CURS 12

Funcii recursive

Ce este recursivitatea ?
Un obiect sau un fenomen se definete n mod recursiv dac n definiia sa exist o referire la el nsui. Recursivitatea este folosit cu mult eficien n matematic. Exemple de definiii matematice recursive: 1. definiia numerelor naturale: 0N dac i N, atunci succesorul lui i N 2. definiia funciei factorial fact : N -> N fact(n) = 1, dac n=0 = n * fact (n-1), dac n>0 3. definiia funciei Fibonacci fib : N -> N fib(n) = 1, dac n=0 sau n=1 = fib(n-2) + fib(n-1), dac n>1
Programarea calculatoarelor

Recursivitate
Utilitatea practic a recursivitii: posibilitatea de a defini un set infinit de obiecte printr-o singur relaie sau printr-un set finit de relaii. Recursivitatea s-a impus n programare odat cu apariia unor limbaje de nivel nalt, ce permit scrierea de module ce se autoapeleaz PASCAL, LISP, ADA, ALGOL, C - limbaje recursive FORTRAN,BASIC,COBOL - limbaje nerecursive Un program recursiv poate fi exprimat: P=M(Si,P) , unde M este mulimea ce conine instruciunile Si i pe P nsui.
Programarea calculatoarelor

Recursivitate versus iterativitate


Iteraia
execuia repetat a unei poriuni de program, pn la ndeplinirea unei condiii (while, do-while , for din C).

Recursivitatea
execuia repetat a unui modul n cursul execuiei lui se verific o condiie nesatisfacerea condiiei implic reluarea execuiei modulului de la nceput, fr ca execuia curent s se fi terminat n momentul satisfacerii condiiei se revine n ordine invers n lanul de apeluri, relundu-se i ncheindu-se apelurile suspendate.

Programarea calculatoarelor

Funcii recursive n C
Exprimarea recursivitii n C : O funcie recursiv este o funcie care se apeleaz pe ea nsi. Recursivitatea poate fi: direct - o funcie P conine o referin la ea nsi indirect - o funcie P conine o referin la o funcie Q ce include o referin la P. Se pot deosebi dou feluri de funcii recursive directe:
Funcii cu un singur apel recursiv, ca ultim instruciune se pot rescrie uor sub form nerecursiv (iterativ). Funcii cu unul sau mai multe apeluri recursive forma lor iterativ trebuie s foloseasc o stiv pentru memorarea unor rezultate intermediare.
Programarea calculatoarelor

Exemplu
#include<stdio.h> //Funcia recursiv de calcul a factorialului: int fact (int n) { if (n==1 || n==0 ) return 1; return n*fact(n-1); //apel recursiv } //Varianta nerecursiv, iterativ int fact_it (int n) { int i, f; for( i=f=1; i<=n; i++ ) f *= i; return f; }
Programarea calculatoarelor

Exemplu - continuare
int main(){ int n; scanf("%d",&n); for( int i=1; i<=n; i++ ) printf("%d %d %d\n",i, fact(i),fact_it(i)); fflush(stdin); getchar(); return 0; }
Observaie: tipul funciei factorial trebuie s devina long pentru n>=16!
Programarea calculatoarelor

Exemplu de funcie recursiv de tip void


#include<stdio.h> void binar (int n) { // afiseaza n n baza 2 if (n>0) { binar (n/2); // scrie echiv. binar al lui n/2 printf ("%d",n%2); // i restul impartirii lui n la 2 } } int main(){ int n; scanf ("%d",&n); printf ("%d = ", n); if (n) binar(n); else printf("0"); fflush (stdin); getchar (); return 0; }
Programarea calculatoarelor

Verificarea i simularea programelor recursive


Printr-o demonstraie formal sau testnd toate cazurile posibile. Se verific nti dac toate cazurile particulare (ce se execut cnd se ndeplinete condiia de terminare a apelului recursiv) funcioneaz corect. Se face apoi o verificare formal a funciei recursive, pentru restul cazurilor, presupunnd c toate componentele din codul funciei funcioneaz corect. Verificare inductiv avantaj al programelor recursive, ce permite demonstrarea corectitudinii lor simplu i clar. Exemplu: factorial
Demonstrarea corectitudinii cuprinde doi pai: pentru n=1 valoarea 1 ce se atribuie factorialului este corect pentru n>1, presupunnd corect valoarea calculat pentru predecesorul lui n de ctre fact(n-1), prin nmulirea acesteia cu n se obine valoarea corect a factorialului lui n.
Programarea calculatoarelor

Observaii
Orice funcie recursiv trebuie s conin (cel puin) o instruciune if (de obicei chiar la nceput), prin care se verific dac (mai) este necesar un apel recursiv sau se iese din funcie! Absena instruciunii if conduce la o recursivitate infinit (la un ciclu fr condiie de terminare)! Pentru funciile de tip diferit de void apelul recursiv se face printr-o instruciune return, prin care fiecare apel preia rezultatul apelului anterior! Funciile recursive nu conin n general cicluri explicite (cu unele excepii), iar repetarea operatiilor este obinut prin apelul recursiv!
Programarea calculatoarelor

Observaie
Apelul recursiv al unei funcii trebuie s fie condiionat de o decizie care s mpiedice apelul n cascad (la infinit); aceasta ar duce la o eroare de program - depirea stivei. funcie recursiva: void p( ){ p(); //apel infinit } apelul este de obicei condiionat astfel: void p( ){ if (condiie) p(); // apel finit condiionat }
Programarea calculatoarelor

Realizarea recursivitii n C
La fiecare apel al funciei sunt puse ntr-o stiv (gestionat de compilator) adresa de revenire variabilele locale argumentele formale acestea fiind referite i asupra lor fcndu-se modificrile n timpul execuiei curente a funciei La ieirea din funcie (prin return) se scot din stiv toate datele puse la intrarea n funcie (se "descarc" stiva) modificrile operate asupra parametrilor-valoare nu afecteaz parametrii efectivi de apel corespunztori
Programarea calculatoarelor

Observaii
Pe parcursul unui apel, sunt accesibile doar variabilele locale i parametrii pentru apelul respectiv, nu i cele pentru apelurile anterioare, chiar dac acestea poart acelai nume. Atenie! Pentru fiecare apel recursiv al unei funcii se creaz copii locale ale parametrilor valoare i ale variabilelor locale, ceea ce poate duce la risip de memorie!

Programarea calculatoarelor

Ce este mai indicat de utilizat: recursivitatea sau iteraia?


Algoritmii recursivi sunt potrivii pentru
probleme care utilizeaz formule recursive pentru prelucrarea structurilor de date definite recursiv (liste, arbori) mai elegani i mai simplu de neles i verificat uneori mai greu de dedus relaia de recuren

Iteraia este preferat (uneori) din cauza


vitezei mai mari de execuie memoriei mai reduse

Exemplu
varianta recursiv a funciei Fibonacci duce la 15 apeluri pentru n=5 varianta iterativ este mult mai performant
Programarea calculatoarelor

Exemplu: Fibonacci
// fib(n) = 1, dac n=0 sau n=1 // fib(n) = fib(n-2) + fib(n-1), dac n>1 #include<stdio.h> int fibo_it (int n){ int f1=1,f2=1,fn=1, i; if (n==0 || n==1) return 1; for (i=2; i<=n; i++) { fn=f1+f2; f2=f1; f1=fn; } return fn; }
Programarea calculatoarelor

Exemplu: Fibonacci
int fibo (int n) { if ( n==0 || n==1 ) return 1; return fibo (n-2) + fibo (n-1); } int main (){ int n; scanf ("%d", &n); printf ("%d %d", fibo(n), fibo_it(n)); fflush (stdin); getchar (); return 0; }
Programarea calculatoarelor

Tehnica eliminarii recursivitii


Orice program recursiv poate fi transformat n unul iterativ, dar algoritmul su poate deveni mai complicat i mai greu de neles. De multe ori, soluia unei probleme poate fi elaborat mult mai uor, mai clar i mai simplu de verificat, printr-un algoritm recursiv. pentru implementare, poate fi necesar transformarea algoritmului recursiv n unul nerecursiv, n situaiile:
soluia problemei trebuie scris ntr-un limbaj nerecursiv; un caz particular sunt compilatoarele ce "traduc" un program recursiv dintr-un limbaj de nivel nalt n cod main (nerecursiv) varianta recursiv ar duce la o vitez de execuie i spatiu de memorie prea mari, transformarea n una nerecursiv eliminnd dezavantajele.

n scrierea unei varianta nerecursive, trebuie parcuri toti paii implicai n varianta recursiv, prin tehnici nerecursive.
Programarea calculatoarelor

Eliminare recursivitate pentru funcii cu un singur apel recursiv, ca ultim instruciune


nlocuind instruciunea if cu o instruciune repetitiv (while, for, do)
Recursiv: double putere(double x, int n) { if (n==0) return 1; return n*putere(x, n-1); } Iterativ: double putere(double x, int n) { double p=1; if (n==0) return 1; while (n) { p=p*x; n--; } return p; }

// x^n=x*x^(n-1)

Programarea calculatoarelor

Eliminare recursivitate, caz general


Recursivitatea implic folosirea a cel puin unei stive. La fiecare apel recursiv sunt depuse n stiv date care sunt extrase la revenirea din acel apel. Funciile recursive cu mai multe apeluri sau cu un apel care nu este ultima instruciune pot fi rescrise iterativ numai prin folosirea unei stive.
Datele pentru un apel se organizeaz ntr-o structur Apel: introducerea n stiv a unei structuri Revenirea din funcie: extragerea structurii din stiv

Stiva: Last n First Out


Implementare utiliznd un vector Adugare i extragere de la sfrit vector

Aceast stiv poate fi un simplu vector local funciei


Programarea calculatoarelor

Exemplu: afiare n binar


void binar ( int n) { int c[32], i; // c este stiva de cifre // pune resturi n stiva c i=0; while ( n>0) { c[i++]=n%2; n=n/2; } // descarca stiva: scrie vector n ordine inversa while (i>0) printf ("%d",c[--i]); }
Programarea calculatoarelor

Exemplu
Program care citete caracterele tastate pn la un blanc, tiprindu-le apoi n ordine invers. Varianta recursiva: void invers_car(void){ char car; if ( (car=getche()) != ' ) invers_car(); putchar(car); } Varianta nerecursiva void invers_car(void){ char car; //initializeaza stiva while( (car=getche()) != ' ') push(car); while (!stiva_goala() ) { pop (car); putchar (car); } }
Programarea calculatoarelor

Algoritmi de traversare i inversare a unei structuri


Traversarea / inversarea unei structuri nseamn efectuarea unor operaii oarecare asupra tuturor elementelor unei structuri n ordine direct / invers Mai uzuale sunt variantele iterative, caz n care inversarea echivaleaz cu dou traversri directe (o salvare n stiv urmat de parcurgerea stivei) Variantele recursive sunt mai elegante i mai concise. Se pot aplica structurilor de tip tablou, lista, fiier i pot fi o soluie pentru diverse probleme (transformarea unui ntreg dintr-o baza n alta, etc).
Programarea calculatoarelor

Form general
Parcurgere direct:
apelul iniial al funciei se face cu primul element al structurii

void traversare ( tip_element element ) { prelucrare ( element ); if ( element != ultimul_din_structura ) traversare ( element_urmator ) ; } Parcurgere invers:
apelul iniial al funciei se face cu primul element al structurii

void inversare ( tip_element element ) { if ( element != ultimul_din_structura ) traversare ( element_urmator ); prelucrare (element); }
Programarea calculatoarelor

Exemplu
Funcie recursiv ce determin elementul maxim dintr-un vector // maxim dintre doua valori double max2 (double a, double b) { return a > b ? a:b; } // maxim dintr-un vector double maxim (double a[ ], int n) { if (n==1) return a[0]; else return max2 (maxim (a,n-1), a[n-1]); }
Programarea calculatoarelor

Algoritmi care implementeaza definiii recursive


O definiie recursiv e cea n care un obiect se definete prin el nsui. Definiia conine: o condiie de terminare, indicnd modul de prsire a definiiei o parte ce precizeaz definirea recursiv propriu-zis Exemple: algoritmul lui Euclid de aflare a c.m.m.d.c., calcul combinri ridicarea la o putere ntreag prin nmuliri repetate definirea recursiv a unei expresii aritmetice calculul valorii unui polinom

Programarea calculatoarelor

Exemple
Algoritmul lui Euclid: condiia de terminare: a%b == 0 relaia de recuren: cmmdc(a,b) = cmmdc(b,a%b) int cmmdc (int a, int b) { if ( a%b == 0 ) return b; return cmmdc(b, a%b); }

Programarea calculatoarelor

Algoritmi de divizare - "divide and conquer"


Const n: descompunerea unei probleme complexe n mai multe subprobleme subprobleme a cror rezolvare e mai simpl din soluiile subproblemelor se poate determina soluia problemei iniiale Exemple: determinarea minimului i maximului valorilor elementelor unui tablou cutarea binar sortare Quicksort turnurile din Hanoi
Programarea calculatoarelor

Algoritm de divizare general


void rezolva (problema x) { if (x e divizibil n subprobleme) { //divide pe x n parti x1,...,xk rezolva(x1); //... rezolva(xk); //combina solutiile partiale intr-o solutie } else //rezolva pe x direct }

Programarea calculatoarelor

Funcie recursiv de cutare binar ntr-un vector ordonat


reduce succesiv poriunea din vector n care se caut, poriune definit prin doi indici ntregi //caut b ntre a[i] i a[j]
int cautb (int b, int a[], int i, int j) { int m; // indice median intre i i j if ( i > j) // dac indice inferior mai mare ca indice superior return -1; // atunci b negasit n a m=(i+j)/2; // m = indice intre i i j (la mijloc) if (a[m]==b) return m; if (b < a[m]) // dac b n prima jumatate return cautb (b,a,i,m-1); // atunci se cauta intre a[i] i a[m-1] else // dac b n a doua jumatate return cautb (b,a,m+1,j); // atunci se cauta intre a[m+1] i a[j] }
Programarea calculatoarelor

Observaie
Varianta recursiv a unor funcii poate necesita un parametru n plus fa de varianta nerecursiv pentru aceeai funcie, dar diferena se poate elimina printr-o alt funcie: // funcie auxiliara cu mai putine argumente int caut (int b, int a[], int n) { // n este dimensiunea vectorului a return cautb (b,a,0,n-1); }
Programarea calculatoarelor

Recursivitate mutual sau indirect


ntre dou sau mai multe funcii f1 i f2, de forma f1 f2 f1 f2 Trebuie declarate funciile apelate, deoarece nu se pot evita declaraiile prin ordinea n care sunt definite funciile. Exemplu: void f1 ( ) { void f2 ( ); // funcie apelata de f1 f2( ); // apel f2 } void f2 ( ) { void f1 ( ); // funcie apelata de f2 f1( ); // apel f1 }
Programarea calculatoarelor

Recursivitate mutual sau indirect


Alt variant de scriere: // declaratii funcii void f1 ( ); void f2 ( ); // definiii funcii void f1 ( ) { f2( ); // apel f2 } void f2 ( ) { f1( ); // apel f1 }
Programarea calculatoarelor

Exerciii
S se scrie cte o funcie recursiv pentru: a) transformarea unui ntreg din baza 10 n alt baz b dat b) tiprirea elementelor unui tablou n ordine invers c) copierea n ordine invers a liniilor dintr-un fiier text n altul d) calculul valorii unui polinom cu coeficieni ntregi pentru o valoare x.

Programarea calculatoarelor