Pentru a evidenția cele de mai sus, rulați următorul program care apelează recursiv funcția f și tipărește pe parcurs mesaje pentru a indica unde a ajuns execuția.
#include <stdio.h>
void f(int n)
{
if (n == 0) return;
printf("Am intrat în funcția f cu parametrul %d\n", n);
f(n - 1);
printf("Ieșim din funcția f cu parametrul %d\n", n);
}
int main(void)
{
f(5);
return 0;
}
Recursivitatea și iterația
Există o legătură strânsă între recursivitate și iterație; de multe ori,
un program recursiv poate fi transformat foarte ușor într-unul iterativ
și invers. În ambele cazuri, aceeași prelucrare (corpul ciclului, respectiv
al funcției recursive) se repetă de mai multe ori. În cazul unui ciclu,
o iterație modifică valorile unor variabile, și iterația următoare se
execută pentru valorile noi; la fel, apelul recursiv se face în general
cu alte valori ale parametrilor față de cele inițiale. În ambele cazuri,
pentru un algoritm corect, prelucrarea trebuie să se încheie după un număr
finit de pași: acest lucru e asigurat de condiția de ieșire din ciclu,
respectiv de cazul de bază al recursivității, în care funcția returnează
direct o valoare, fără a se mai apela recursiv.
Un exemplu simplu prezentat la curs e cel al tipăririi unui șir:
void puts_nonrec(char *s)
{
while (*s) { /* condiția de oprire: *s = '\0' */
putchar(*s); /* prelucrarea: tiparirea unui caracter */
s = s+1; /* variabila modificata: s (incrementat) */
}
putchar('\n'); /* cazul de baza: la ieșirea din iterație */
}
void puts_rec(char *s)
{
if (*s) { /* condiția de oprire: *s = '\0' */
putchar(*s); /* prelucrarea: tiparirea unui caracter */
puts_rec(s+1); /* noul parametru de apel: s+1 in loc de s */
} else putchar('\n'); /* cazul de baza: la ieșirea din recursivitate */
}
Un alt exemplu este cel al cautarii binare al unei valori v
într-un tablou a, între indicii l și r inclusiv.
double *bsearch_nonrec(double v, double *a, int l, int r) {
while (l < r) { /* cât timp intervalul are >= 2 elemente */
int m = (l+r)/2; /* calculează punctul de mijloc */
if (v>a[m]) l = m+1;/* continuă căutarea în intervalul din dreapta */
else r = m; /* continuă căutarea în intervalul din stânga */
} /* iese când intervalul e degenerat, <= 1 element */
if (v==a[l]) return a+l; /* returnează adresa elementului gasit */
else return NULL; /* sau NULL dacă elementul nu a fost găsit */
}
double *bsearch_rec(double v, double *a, int l, int r) {
int m = (l+r)/2;
if (l < r) { /* continuă recursivitatea dacă >= 2 elemente */
if (v>a[m]) /* continuă căutarea în intervalul din dreapta */
return bsearch_rec(v, a, m+1, r); /* cu noul parametru l egal cu m + 1 */
else /* continuă căutarea în intervalul din stânga */
return bsearch_rec(v, a, l, m); /* cu noul parametru r egal cu m */
} else /* iese din recursivitate pentru interval <= 1 */
if (v==a[l]) return a+l; /* returnează adresa elementului gasit */
else return NULL; /* sau NULL dacă elementul nu a fost găsit */
}
De cele mai multe ori, varianta iterativă a unui algoritm e mai performantă
decât cea recursivă, pentru că evită costul suplimentar al apelurilor de
funcție, împreună cu salvarea pe stivă și transmiterea parametrilor.
Mai mult, în cazul unei adâncimi excesive a recursivității, se poate
produce depășirea stivei. Sunt însă multe cazuri în care recursivitatea e
modalitatea naturală de rezolvare, și alte soluții ar fi sensibil mai
complicat și neintuitiv de exprimat.
Problemă: Funcția putere
Scrieți o versiune recursivă pentru funcția putere cu bază reală și exponent
întreg, bazată pe definiția recursivă:
x la puterea n = x * x la puterea (n - 1)
pentru n > 0. Completați apoi funcția și pentru exponent negativ.
Problemă: Inversarea unui șir
Scrieți un program care citesțe de la intrare o linie de text și o afișează
în ordine inversă (de la coadă la cap). Folosiți o funcție recursivă.
Și în acest caz, programul se poate obține direct din formularea recursivă
mai întâi a structurii de date care trebuie prelucrată, și apoi a prelucrării:
- o linie de text este o linie vidă (caracterul '\n') sau
un caracter urmat de o linie de text
- dacă linia e formată din caracterulc și restul r,
inversa ei e formată din inversa lui r urmată de c.
Se observă că efectul unei funcții recursive depinde în mod critic de
locul unde e plasat apelul recursiv în cadrul prelucrării din corpul
funcției (la început, la mijloc sau la sfârșit).
Ca alternativă, scrieți o variantă recursivă pentru funcția strrev
care ia ca parametru un șir de caractere și returnează, alocat dinamic,
inversul lui (de la coadă la cap).
Problemă: Rezolvarea unei ecuații prin metoda înjumătățirii
În semestrul trecut, ați implementat la laborator găsirea rădăcinii unei
funcții continue și monotone pe un interval, cu o precizie dată, dacă
funcția are semne diferite la capetele intervalului. Se calculează valoarea
funcției la mijlocul intervalului și se continuă căutarea in acea jumătate
de interval pe care funcția schimbă semnul. Implementați recursiv algoritmul
descris mai sus.
Problemă: Minimul/maximul unui șir
Scrieți o funcție recursivă care ia ca parametri un tablou de numere și
respectiv lungimea lui și returnează valoarea numărului minim din tablou.
Pentru un tablou de lungime n > 1 se află întâi minimul primelor
n - 1 elemente, și apoi se ia minimul dintre această valoare și
ultimul element.
Problemă: Sortarea unui tablou
Sortarea prin selecție și cea prin metoda bubblesort au în comun faptul că
după prima iterație exterioară, elementul extrem al tabloului (minim sau maxim,
în funcție de sensul ales) este poziționat corect la capăt, după care procedura
se reia pentru al doilea element în mărime, etc. Folosind una din metode,
implementați recursiv procedura de sortare, dându-i ca parametri tabloul și
lungimea lui. Poziționați corect ultimul (cel mai mare) element, după care
apelați recursiv pentru restul tabloului (de la începutul său, dar de lungime
cu 1 mai mică).
Problemă: Șiruri binare de lungime dată
Scrieți un program care tipărește în ordine lexicografică (alfabetică)
toate șirurile de lungime dată N formate din cifrele 0 și 1.
Folosiți o funcție recursivă care pune pe rând 0 și respectiv 1 pe poziția
următoare în șir, iar dacă a ajuns la sfârșit tipărește șirul.
În acest caz, vor exista două apeluri recursive ale funcției în corpul său,
iar numărul total de apeluri e exponențial (ca și numărul de șiruri distincte,
2 la puterea n).
Marius Minea
Last modified: Mon Mar 22 08:27:50 EET 2004