Sunteți pe pagina 1din 14

3 Subprograme recursive

Recursivitatea este tehnica de programare în care un subprogram se auto-


apelează. Limbajul C face parte din clasa limbajelor de programare care admit
scrierea de funcţii recursive. În continuare sunt prezentate câteva exemple simple
de subprograme C prin intermediul cărora sunt calculate recursiv valorile
n! , C nk , f ο g ο f , unde n , k ∈ N şi f, g funcţii, f , g : R → R. De asemenea, este
ilustrată maniera în care sunt efectuate apelurile recursive şi tratarea condiţiilor
terminale.

3.1 Calcul recursiv


Calculul valorii n! pentru n dat poate fi efectuat pe baza formulei recursive
⎧1, n = 0
n! = ⎨ .
⎩n(n − 1)! , n > 0
Fie fact(n) funcţia C care calculează n!. Dacă n ≥ 1, evaluarea lui fact(n)
rezultă prin multiplicarea cu n a valorii calculate de apelul fact(n-1), cu fact(0)=1.
Cu alte cuvinte, apelul funcţiei fact(n) realizează calculul „imediat” dacă n=0, altfel
presupune un nou apel al aceleiaşi funcţii pentru valoarea argumentului
decrementată. Cazurile în care este posibilă evaluarea „imediată” se numesc
condiţii terminale.
În limbajul C, funcţia fact este

long fact(unsigned n)
{ if (!n) return 1;
return n*fact(n-1);
}
n!
Utilizarea formulei C nk = pentru calculul combinărilor
k ! (n − k )!
( n , k ∈ N date) este ineficientă şi uneori imposibilă deoarece n!, pentru n ≥ 13 nu
Subprograme recursive

poate fi reprezentat în calculator ca dată de un tip întreg, chiar dacă numărul C nk


este relativ mic şi poate fi reprezentat prin intermediul unui tip întreg. Pe baza
relaţiei de recurenţă C nk = C nk−1 + C nk−−11 , valoarea C nk poate fi calculată astfel. Fie
comb(n,k) funcţia care calculează C nk . Conform relaţiei de recurenţă, dacă
n ≥ k ≥ 1, atunci evaluarea corespunzătoare apelului comb(n,k) revine la însumarea
rezultatelor obţinute prin apelurile comb(n-1,k) şi comb(n-1, k-1), unde
comb(n,0)=1, n ≥ 0. Dacă evaluările comb(n-1,k) şi comb(n-1, k-1) sunt realizate în
acelaşi mod, rezultă că apelul comb(n,k) va determina o secvenţă de apeluri ale
aceleiaşi funcţii pentru valori ale argumentelor din ce în ce mai mici, până când
este îndeplinită una din condiţiile terminale comb(n,0)=1, comb(k,k)=1.
Soluţia recursivă a evaluării C nk este:

long comb(unsigned n, unsigned k)


{ if (k>n) return 0;
if ((k==0)||(k=n)) return1;
return comb(n-1,k)+comb(n-1,k-1);
}

Funcţiile C recursive pentru calculul n! , C nk , unde n , k ∈ N realizează


apeluri recursive directe. Schema unui apel recursiv poate fi descrisă astfel: se
verifică dacă este îndeplinită cel puţin una din condiţiile terminale; dacă este
îndeplinită o condiţie terminală, atunci calculul este încheiat şi controlul este
returnat unităţii apelante, în caz contrar este iniţiat calculul pentru noile valori ale
parametrilor, calcul care presupune unul sau mai multe apeluri recursive.
Mecanismul prin care este efectuat apelul unui subprogram se bazează pe
utilizarea stivei memoriei calculatorului. Fiecare apel determină introducerea în
stivă a valorilor parametrilor formali, a adresei de revenire şi a variabilelor locale.
La momentul execuţiei, aceste informaţii sunt extrase cu eliminare din stivă,
eliberându-se spaţiul ocupat.
În cazul subprogramelor recursive, mecanismul funcţionează astfel: este
generat un număr de apeluri succesive cu ocuparea spaţiului din stivă necesar
efectuării apelurilor până la îndeplinirea unei condiţii terminale; apelurile sunt
executate în ordinea inversă celei în care au fost generate, iar operaţia de inserare
în stivă poate produce depăşirea spaţiul de memorie rezervat.
De exemplu, în cazul apelului fact(3), secvenţa de apeluri recursive este:
fact(2), fact(1), fact(0). În continuare execuţia determină fact(0)=1,
fact(1)=1*fact(0)=1, fact(2)=2*fact(1)=2, fact(3)=3*fact(2)=6.
Evoluţia determinată de apelul fact(3) în stivă este ilustrată în figurile 3.1.a
şi 3.1.b, unde (○) reprezintă adresa de revenire în punctul de unde a fost efectuat
apelul fact(3).
Apelurile recursive ale unui subprogram S1 pot fi şi indirecte, în sensul că
este efectuat un apel al unui alt subprogram S2 şi S2 iniţiază un apel al lui S1. De
Programarea calculatoarelor

exemplu, calculul valorilor funcţiei h=f◦g◦f , unde f,g:R→R sunt funcţii date poate
fi descris astfel. Pentru funcţiile f, g definite prin:

3 2

Fact=3*Fact(2)
Fact=2*Fact(1)

(o)
Fact=3*Fact(2)

(o)

1 0

Fact=1*Fact(0) Fact=1

2 1

Fact=2*Fact(1) Fact=1*Fact(0)

2
3

Fact=2*Fact(1)
Fact=3*Fact(2)

(o)
Fact=3*Fact(2)

(o)
Figura 3.1.a Evoluţia în stivă până la verificarea condiţiei terminale n=0
Subprograme recursive

1 2

Fact=1 Fact=2

2 3

Fact=2*Fact(1) Fact=3*Fact(2)

3
3

Fact=3*Fact(2)
Fact=6
(o)

(o)
Figura 3.1.b Eliberarea stivei după execuţia determinată de condiţia terminală

⎧2 x 3 + 1, x < 5
⎪ ⎧⎪5 x 2 − 3 x + 2 , x ≤ 1
f (x ) = ⎨ x 4 + 2 , 5 ≤ x < 8 , g (x ) = ⎨ 3
⎪3 , x > 8 ⎪⎩ x − x + 5 , x > 1

funcţiile C pentru calculul h=f◦g◦f pot fi descrise astfel,

float f(float x)
{ if (x<5) return 2*pow(x,3)+1;
if (x<8) return pow(x,4)+2;
return 3;
}

float g(float x)
{ if (x<=1) return 5*x*x-3*x+2;
return pow(x,3)-x+5;
}

float h(float x)
{ return f(g(f(x)));
}
Programarea calculatoarelor

3.2 Aplicaţii cu subprograme recursive


1. Să se scrie o funcţie C care citeşte o secvenţă oarecare de cuvinte a1, a2,
….., an terminată cu simbolul # şi afişează anan-1…a1. Pentru rezolvarea problemei
se utilizează funcţia recursivă Scrie.
void Scrie()
{ char cuvant[100];
scanf(“%s“,&cuvant);
if (strcmp(cuvant,“#“))
{ Scrie;
printf( “%s\n“,cuvant);
}
}

2. Calculul valorii funcţiei Ackermann.


Funcţia Ackermann este definită pentru argumentele m,n numere naturale
prin
⎧n + 1, m = 0

a (m , n ) = ⎨a (m − 1,1), n = 0
⎪a (m − 1, a (m , n − 1)), altfel

Funcţia C Ackermann calculează valoarea funcţiei a pentru m, n parametri
naturali daţi.

long Ackermann(unsigned m, unsigned n)


{ if (!m) return n+1;
if (!n) return Ackermann(m-1,1);
return Ackermann(m-1,Ackermann(m,n-1));
}

3. Problema calculului celui mai mare divizor comun dintre două numere
naturale a şi b poate fi rezolvată recursiv, conform definiţiei următoare,
⎧a , a = b
(a ,b ) = ⎪⎨( a − b ,b ), a > b
⎪( a ,b − a ), b > a

Funcţia C cmmdc(a,b) este,

long cmmdc(long a, long b)


{ if (a==b) return a;
if (a>b) return cmmdc(a-b,b);
return cmmdc(a,b-a);
}
Subprograme recursive

4. Problema turnurilor din Hanoi ilustrează foarte bine avantajele


recursivităţii. Problema poate fi enunţată astfel: se presupune că există trei tije a, b,
c, pe tija a fiind plasate n discuri de diametre diferite în ordinea descrescătoare a
acestora. Se cere ca cele n discuri de pe tija a să fie deplasate pe tija c astfel încât
să fie îndeplinite condiţiile:
• la fiecare mutare este deplasat unul dintre discurile aflate pe poziţia
superioară pe una din tije;
• oricare din discuri poate fi aşezat numai pe un disc de diametru mai
mare;
• tija b poate fi folosită pentru deplasări intermediare.

Notând cu P(n,a,c) problema transferului celor n discuri de pe tija a pe tija


c, pentru rezolvarea ei putem raţiona în modul următor. Dacă s-a rezolvat problema
P(n-1,a,b), atunci discul de diametru maxim care se află încă pe tija a este deplasat
pe tija c şi în continuare se rezolvă problema P(n-1,b,c). Soluţia recursivă este
prezentată în funcţia Hanoi.

Exemplu
Presupunând că discurile sunt numerotate în ordinea crescătoare a
diametrelor cu etichetele 1, 2, 3, o soluţie a problemei pentru n=3 poate fi descrisă
astfel.

Tija a Tija b Tija c Mutarea efectuată


1 a⇒c
2
3
2 1 a⇒b
3
3 2 1 c⇒b
3 1 a⇒c
2
1 3 b⇒a
2
1 2 3 b⇒c
1 2 a⇒c
3
1
2
3
#include <stdio.h>
#include <conio.h>

void Hanoi(unsigned n,unsigned a, unsigned b,unsigned c)


{
Programarea calculatoarelor

if(n>0)
{ Hanoi(n-1,a,c,b);
printf("Transfer disc de pe tija %u pe tija %u\n",a,b);
Hanoi(n-1,c,b,a); }
}
void main()
{ unsigned n,a,b,c;
clrscr();
printf("n=");scanf("%u",&n);
Hanoi(n,1,2,3);getch();
}

5. Căutarea în vectori sortaţi (căutarea binară)


Fie v este un vector de numere reale sortat crescător şi k este un număr real
dat. Problema este de a identifica (dacă există) o valoare poz, astfel încât v[poz]=k.
Rezolvarea ei porneşte cu luarea în considerare a întregului vector v şi
⎡n⎤
determinarea poziţiei mijlocului m = ⎢ ⎥ . Dacă v[m]=k, atunci poz:=m. Dacă
⎣2⎦
v[m]>k, atunci se procedează în acelaşi mod cu vectorul format din primele m
componente din v, altfel cu cel format din componentele v[m+1],…,v[n-1]. Se
generează astfel subvectori din ce în ce mai mici până când valoarea k este găsită
sau până când nu mai poate fi generat un nou subvector.

#include <stdio.h>
#include <conio.h>
int cauta_binar(float *,int,int,float);

void main()
{ clrscr();
printf("Dimensiunea vectorului:");
int n;
scanf("%i",&n);
printf("Elementele vectorului\n");
float v[100];
for(unsigned i=0;i<n;i++)
scanf("%f",&v[i]);
printf("Cheia de cautare:");
float k;
scanf("%f",&k);
int c=cauta_binar(v,0,n-1,k);
if(c==-1)
printf("Cheia nu a fost gasita");
else printf("Cheia pe pozitia:%i",c);
getch();
}

int cauta_binar(float *v,int li,int ls,float k)


{ if(li>ls)
return -1;
int mij=(li+ls)/2;
if(v[mij]==k)
Subprograme recursive

return mij;
if(v[mij]>k)
return cauta_binar(v,li,mij-1,k);
return cauta_binar(v,mij+1,ls,k);
}

6. Sortarea crescătoare prin inserare


Pentru sortarea crescătoare a unei secvenţe de numere reale se poate raţiona
astfel: dacă P(n) este problema sortării crescătoare a secvenţei a1, a2,…,an şi P(n-1)
este problema sortării primelor n-1 componente, atunci soluţia problemei P(n)
rezultă din soluţia problemei P(n-1) prin inserarea lui an în soluţia problemei
P(n-1). Fiecare problemă intermediară P(k), k = 2 ,..., n este rezolvată aplicând
aceeaşi metodă P(1) fiind o problemă „gata rezolvată” (condiţie terminală).
Funcţia insera realizează inserarea valorii x în vectorul v în poziţia
„corectă”. Funcţia recursivă inssort realizează sortarea vectorului cu n componente
prin inserţie.

void insera(float *v,int *n,float x)


{ for(int i=0;(i<*n)&&(x>v[i]);i++);
for(int j=*n;j>=i+1;j--)v[j]=v[j-1];
v[i]=x;(*n)++;
}

void inssort(float *v,int n)


{ if(n)
{ inssort(v,n-1);int m=n-1;
insera(v,&m,v[n-1]);
}
}

7. Pot fi realizate desene prin compunerea într-o manieră recursivă a unor


figuri geometrice primitive. Compunerea constă în repetarea primitivelor
considerate şi a rezultatelor obţinute prin rotirea lor într-un sens sau celălalt. Astfel,
dacă mulţimea de primitive H0 constă dintr-un punct şi pentru compunere este
considerat un segment de lungime h, atunci: H1 rezultă din patru exemple (cópii,
realizări, instanţe, clone) de primitive din H0 unite prin segmente de lungime h; H2
rezultă din 16 exemple din H0 unite prin 15 segmente de lungime h/2 ş.a.m.d. De
asemenea, H2 se poate obţine prin interconectarea a patru cópii ale lui H1 rotite cu
unghiuri drepte şi prin interconectarea punctelor izolate prin segmente de aceeaşi
lungime. Generalizând, o curbă Hn rezultă din patru cópii ale unei curbe Hn-1,
punctele izolate fiind unite prin segmente de lungime hn=h/2n. Curbele rezultate se
numesc curbele Hilbert Hi, i ≥ 0.
Programarea calculatoarelor

H1 H2 H3

Dacă cele patru părţi ale unei curbe Hilbert Hk sunt notate A, B, C, D şi se
reprezintă prin săgeţi rutinele care desenează segmentele care le interconectează,
atunci rezultă următoarele scheme recursive.

A: D ← A↓ A→ B

A: D ← A↓ A→ B
B: C↑B→B↓ A
C: B→C ↑C ←D
D: A↓ D← D↑C
Prin executarea următoarei surse C sunt obţinute curbele Hilbert H4.
#include <stdio.h>
#include <graphics.h>
#include <stdlib.h>
#include <conio.h>
const n=5;
const h0=480;

int i=0;
int h;
int x,y,x0,y0,gm;
int gd=DETECT;
void A(int);
void B(int);
void D(int);
void C(int);
void main()
{ clrscr();
initgraph(&gd,&gm,"D:\BC\BGI");
setbkcolor(0);
setcolor(4);
h=h0;y0=x0=h/2;
do{ i++;h/=2;
x0+=h/2;y0+=h/2;
x=x0;y=y0;moveto(x,y);
A(i); }
Subprograme recursive

while(i<n);
getch();
closegraph();
}
void A(int i)
{ if (i>0)
{ D(i-1);x-=h;lineto(x,y);
A(i-1);y-=h;lineto(x,y);
A(i-1);x+=h;lineto(x,y);
B(i-1);
}
}
void B(int i)
{ if (i>0)
{ C(i-1);y+=h;lineto(x,y);
B(i-1);x+=h;lineto(x,y);
B(i-1);y-=h;lineto(x,y);
A(i-1);
}
}
void C(int i)
{ if (i>0)
{ B(i-1);x+=h;lineto(x,y);
C(i-1);y+=h;lineto(x,y);
C(i-1);x-=h;lineto(x,y);
D(i-1);
}
}
void D(int i)
{ if (i>0)
{ A(i-1);y-=h;lineto(x,y);
D(i-1);x-=h;lineto(x,y);
D(i-1);y+=h;lineto(x,y);
C(i-1);
}
}
Programarea calculatoarelor

Curba Hilbert obţinută este

8. În cazul curbelor Hilbert, toate unghiurile determinate de segmentele


care unesc punctele sunt de măsură 900. Dacă se consideră ca valori pentru
măsurile unghiurilor determinate de aceste segmente 450, 900, 1350, rezultă curbele
Sierpinski Sn, n ≥ 1.
Curba Sierpinski S2 este,

Recursia pentru obţinerea curbelor Sierpinski poate fi descrisă astfel.


S: A B C D

A: A B⇒D A
Subprograme recursive

B: B C⇓ A B

C: C D⇐B C

D: D A⇑ C D

unde săgeţile duble indică segmente de lungime 2h.

Următorul program desenează curbele Sierpinski S4.

#include <stdio.h>
#include <graphics.h>
#include <stdlib.h>
#include <conio.h>

const n=4;
const h0=412;
int i=0;
int h;
int x,y,x0,y0,gm;
int gd=DETECT;
void A(int);
void B(int);
void D(int);
void C(int);
void main()
{ clrscr();
initgraph(&gd,&gm,"d:\bc\bgi");
setbkcolor(15);
setcolor(8);
h=h0/4;
x0=2*h;
y0=3*h;
do{ i++;
x0-=h;h/=2;y0+=h;
x=x0;y=y0; moveto(x,y);
A(i);x+=h;y-=h;lineto(x,y);
B(i);x-=h;y-=h;lineto(x,y);
C(i);x-=h;y+=h;lineto(x,y);
D(i);x+=h;y+=h;lineto(x,y);}
while(i!=n);
getch();
closegraph();
}

void A(int i)
{ if (i>0)
{ A(i-1);x+=h;y-=h;
lineto(x,y);
B(i-1);x+=2*h;
lineto(x,y);
Programarea calculatoarelor

D(i-1);x+=h;y+=h;
lineto(x,y);
A(i-1);
}
}

void B(int i)
{ if (i>0)
{ B(i-1);x-=h;y-=h;
lineto(x,y);C(i-1);
y-=2*h;
lineto(x,y);
A(i-1);x+=h;y-=h;
lineto(x,y);
B(i-1);
}
}

void C(int i)
{ if (i>0)
{ C(i-1);x-=h;y+=h;
lineto(x,y);
D(i-1);x-=2*h;
lineto(x,y);
B(i-1);x-=h;y-=h;
lineto(x,y);
C(i-1);
}
}
void D(int i)
{ if (i>0)
{ D(i-1);x+=h;y+=h;
lineto(x,y);
A(i-1);y+=2*h;
lineto(x,y);
C(i-1);x-=h;y+=h;
lineto(x,y);
D(i-1);
}
}

Rezultatul execuţiei programului este prezentat în următoarea figură.


Subprograme recursive

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