programarea calculatoarelor cursul 8: recursivitateusers.utcluj.ro/~robert/pc/curs/c08.pdf ·...
TRANSCRIPT
Programarea Calculatoarelor
Cursul 8: Recursivitate
Robert Varga
Universitatea Tehnică din Cluj-Napoca
Departamentul Calculatoare
Recursivitatea
• Tehnică de programare puternică și de uz general în care o funcție se auto-apelează
• Stă la baza inducției matematice• O tehnică puternică care demonstrează multe teoreme matematice
• Ajută la descompunerea unei probleme de dimensiuni mari în subprobleme – tehnica Divide et Impera
• Rezolvarea subproblemelor și combinarea rezultatelor duc la soluția problemei
• Ajută la determinarea tuturor soluțiilor posibile ale unei probleme – tehnica Backtracking
• Permite scrierea programelor într-o manieră mult mai compactă și mai clară
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 2
Recursivitatea
• O funcție recursivă se poate auto-apela• Direct – funcția conține un apel al ei însăși
• Indirect – funcția conține un apel al altei funcții, iar aceasta din urmă conține la rândul ei un apel către prima funcție
• La fiecare apel recursiv se stochează la locații separate pe stivă
• Valorile variabilelor locale automate pentru apelul respectiv
• Valorile parametrilor formali pentru apelul respectiv
• Adresa de întoarcere din apelul respectiv
• De-a lungul apelurilor recursive sunt stocate pe heap și își păstrează locația
• Variabilele statice
• Variabilele globale
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 3
Terminarea recursivității
• O funcție recursivă trebuie să aibă în construcția ei o condiție de terminare
• Altfel apelul recursiv poate să ducă la o buclă infinită (asemănătoare structurilor repetitive în care condiția de continuare este întotdeauna adevărată și care iterează la nesfârșit)!
• Adâncimea recursivității trebuie să nu fie una foarte mare
• La fiecare apel recursiv se memorează pe stivă• Parametrii funcției
• Variabilele locale automate
• Adresa de revenire din apelul funcției
• Aceasta variază în funcție de numărul de octeți conținuți în parametrii și variabilele locale automate
• În cazul depășirii dimensiunii maxime a stivei, programul se termină subit în urma unei erori – stack overflow
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 4
Recursivitate liniară
• Este cea mai simplă formă de recursivitate
• Apare atunci când o acțiune are o structură simplă repetitivă care constă într-un proces urmat de repetarea acțiunii respective
• Poate fi transformată în iterații nerecursive prin plasarea procesului într-o structură repetitivă (ex.: for, while)
• Condiția de terminare a recursivității este utilizată pentru a termina iterarea în cadrul structurii repetitive
• Exemple• Calculul sumei primelor n numere naturale
• Inversarea unui șir de valori
• Calculul minimului unui șir de valori
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 5
Exemplu
• Suma a primelor n numere naturale
𝑠𝑢𝑚 𝑛 = 0 dacă 𝑛 = 0
𝑛 + 𝑠𝑢𝑚(𝑛 − 1) dacă 𝑛 > 0
• Se identifică cazul special (n=0) în care funcția sum nu se apelează recursiv
• În cazul apelului recursiv se observă că parametrul nscade cu 1 la fiecare apel => se îndreaptă către cazul special (condiția de terminare) dacă n este un număr natural
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 6
Exemplu
#include <stdio.h>
int sum(int n){
printf("Apel recursiv cu n=%d\n", n);
int rez = 0;
if (n<=0)
rez = 0;
else
rez = n + sum(n-1); /* &2=adresa de intoarcere dupa apelul lui sum(n-1) */
printf("Iesirea din apelul recursiv cu n=%d\n", n);
return rez;
}
int main() {
int n;
printf("n=");
scanf("%d", &n);
int rez = sum(n);
for(int i=1; i<n; i++)
printf("%d + ", i);
printf("%d = %d", n, rez); /* &1=adresa de intoarcere dupa evaluarea lui sum(n) */
return 0;
}22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 7
Exemplu
Intrare și rezultate afișate de program:n=3
Apel recursiv cu n=3
Apel recursiv cu n=2
Apel recursiv cu n=1
Apel recursiv cu n=0
Iesirea din apelul recursiv cu n=0
Iesirea din apelul recursiv cu n=1
Iesirea din apelul recursiv cu n=2
Iesirea din apelul recursiv cu n=3
1 + 2 + 3 = 6
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 8
Exemplu
• Evoluția stivei de apeluri:a. Imediat după intrarea în
apelul lui sum(3)
b. Imediat după intrarea în apelul recursiv al lui sum(2)
c. Imediat după intrarea în apelul recursiv al lui sum(1)
d. Imediat după intrarea în apelul recursiv al lui sum(0)
e. Imediat după ieșirea din apelul recursiv (pentru n=0)
f. Imediat după ieșirea din apelul recursiv (pentru n=1)
g. Imediat după ieșirea din apelul recursiv (pentru n=2)
h. Imediat după ieșirea din apelul recursiv (pentru n=3)în funcția main()
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 9
&1n=3
&2n=2&1n=3
&2n=2&1n=3
&2n=1&2n=2&1n=3
&2n=0&2n=1&2n=2&1n=3
&2n=1&2n=2&1n=3
&2n=2&1n=3
&1n=3
a. b. c. d.
e. f. g. h.
Exemplu – însumarea elementelordintr-un tablou
int sum_last(int n, int* a){
if (n==1)
return a[n-1];
else
return a[n-1] + sum_last(n-1, a);
}
int sum_first(int n, int* a){
if (n==1)
return a[0];
else
return a[0] + sum_first(n-1, a+1);
}
int sum_mid(int from, int to, int* a){
if (from == to)
return a[from];
else{
int m = (from+to)/2;
return sum_mid(from, m, a) + sum_mid(m+1, to, a);
}
}
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 10
Exemplu – însumarea elementelordintr-un tablou
#include <stdio.h>
int sum_last(int n, int* a);int sum_first(int n, int* a);int sum_mid(int from, int to, int* a);
int main() {int a[] = {1, 2, 3, 4};int n = sizeof(a)/sizeof(a[0]);printf("last %d\n", sum_last(n, a));printf("first %d\n", sum_first(n, a));printf("mid %d\n", sum_mid(0, n-1, a));return 0;
}
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 11
Exemple de funcții recursive
• Șirul lui Fibonacci
𝐹 𝑛 = 0 𝑑𝑎𝑐ă 𝑛 = 01 𝑑𝑎𝑐ă 𝑛 = 1
𝐹 𝑛 − 1 + 𝐹(𝑛 − 2) 𝑑𝑎𝑐ă 𝑛 > 1
• Numărul de compoziții ale unui număr natural pozitiv• Numărul de moduri de a scrie n ca o sumă de numere naturale
pozitive
• Exemplu pentru numărul 4 sunt 8 compoziții:1+1+1+1= 1+1+2 = 1+2+1 = 2+1+1 = 1+3 = 2+2 = 3+1 = 4
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 12
Exemplu – Șirul lui Fibonacci
#include <stdio.h>
#include <stdlib.h>
int fib(int n)
{
if (n==0)
return 0;
if (n==1)
return 1;
return fib(n-1)+fib(n-2);
}
int main()
{
printf("%d\n",fib(20)); //6765
printf("%d\n",fib(42)); //Timp de executie ridicat!!! – recalculam multe valori
return 0;
}
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 13
Greșeli des întâlnite
• Nu există condiție de terminare:void afisare_cifre(int n) {
afisare_cifre(n / 10);
printf("%d\n", (n % 10));
}
• Există condiție de terminare, dar apelul recursiv nu modifică valoarea parametrului:
void afisare_cifre(int n) {
if (n < 10)
printf("%d\n", n);
else
{
afisare_cifre(n);
printf("%d\n", (n % 10));
}
}
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 14
Greșeli des întâlnite –recursivitate adâncă
#include <stdio.h>
int sum(int n){int rez = 0;if (n<=0)
rez = 0;else
rez = n + sum(n-1);return rez;
}
int main() {sum(1e7);return 0;
}
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 15
Recursivitatea – important !
• Pentru a evita ciclul infinit scrieți un caz special în care funcția să nu se apeleze recursiv
• Atenție la funcțiile care se pot apela prin recursivitate indirectă
• Recursivitatea trebuie gândită bine – altfel se pot scrie programe total ineficiente
• Recursivitatea este o alternativă pentru structurile repetitive for si while utilizate în calcule ce necesită iterații multiple
• Funcțiile recursive sunt foarte utile când structurile de date (ex. arbori binari de căutare) sau paradigma de programare (ex. divide et impera) sunt recursive prin definiție
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 16
Recursivitatea – important !
• O funcție recursivă ar trebui să aibă următoarele trei proprietăți
• Nu se auto-apelează la infinit• Se va identifica cazul special (sau condiția de terminare) în care funcția nu se
auto-apelează
• Se va asigura faptul că succesiunea de apeluri recursive va conduce către acest caz special
• Pot să existe mai multe astfel de cazuri speciale
• Fiecare caz special de oprire• Trebuie să returneze valoarea corectă pentru acel caz (în cazul funcțiilor care
returnează o valoare)
• Trebuie să execute acțiunile corecte pentru acel caz (în cazul funcțiilor care nu returnează nicio valoare)
• Pentru cazurile care necesită apeluri recursive, dacă apelul recursiv se execută corect (sau returnează o valoare corectă) atunci acțiunea finală este corectă (sau calculul final este corect)
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 17
Recursivitate – important !
• Nu vă gândiți în mod recursiv dacă• Folosiți variabile globale
• Folosiți variabile statice
• Transmiteți rezultate parțiale la autoapel• Este în regulă dacă vrem să construim răspunsul
• Numărul de parametrii la funcție nu este minimal
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 18
Etapele rezolvării unei probleme folosind recursivitate
• Rezolvarea pentru un caz simplu (de bază)
• Rezolvarea pentru cazuri mici – opțional, dar recomandat
• Stabilirea relației de recurență• Studiem cum putem să reducem problema la o subproblemă
• Observăm ce se întamplă: la primul pas, la ultimul pas sau la mijloc
• Cum putem să combinăm rezultatele de la subprobleme pentru a da răspunsul
• Implementare recursivă• Cu tehnica de memoizare (memorare)
• Implementare iterativă
• Formulă generală fără recursivitate
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 19
Triangularizări n-gon
• Calculați numărul de moduri de a triangula un poligon convex cu n vârfuri, 3<=n<35
• În câte moduri se poate împărți în triunghiuri
• Împărțirea se face dealungul diagonalelor
• Diagonalele nu se intersectează
• Exemple de triangulări:• n = 4 n = 6
• Fie T(n) numărul de moduri
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 20
Triangularizări n-gon
• Rezolvarea pentru un caz simplu • Pentru n=3
• Un triunghi se poate triangula într-un singur mod
T(3) = 1
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 21
Triangularizări n-gon
• Rezolvarea pentru cazuri mici• Pentru n=4, avem T(4) = 2
• Pentru n=5, avem T(5) = 5
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 22
Triangularizări n-gon
• Rezolvarea pentru cazuri mici• Pentru n=6, avem T(6) = 14
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 23
Triangularizări n-gon
• Stabilirea relației de recurență• Numerotăm vârfurile și studiem ce se întâmplă cu vârful 1
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 24
1 2
3n
n-1 ...
i
Triangularizări n-gon
• Stabilirea relației de recurență• Vârful 1 poate fi conectat la unul din vârfurile 3, 4, ..., n-1
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 25
1 2
3n
n-1 ...
i
Triangularizări n-gon
• Stabilirea relației de recurență• Dacă conectăm la vârful i
• Poligonul se împarte în două părți care se pot triangula independent
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 26
1 2
3n
n-1 ...
i
poligon cu i vârfuri
poligon cu n-i+2
vârfuri
Triangularizări n-gon
• Stabilirea relației de recurență• Vârful 1 nu este conectat• Trebuie să existe triunghiul (n,1,2) și rămâne un (n-1)-gon
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 27
1 2
3n
n-1 ...
i
Triangularizări n-gon
• Stabilirea relației de recurență• Dacă conectăm vârful 1 la vârful i avem T(i)T(n-i+2) modalități
• i poate fi 3, 4, ... n-1
• Dacă nu conectăm vârful 1 la nimic avem T(n-1) modalități
• Rezultă formula de recurență
𝑇 𝑛 =
𝑖=3
𝑛−1
𝑇 𝑖 𝑇 𝑛 − 𝑖 + 2 + 𝑇(𝑛 − 1)
• Dar unele cazuri se suprapun
• Conectăm vârful 1 cu i apoi cu j
• Conectăm vârful 1 cu j apoi cu i
• Recurență greșită!
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 28
Triangularizări n-gon
• Stabilirea relației de recurență• Numerotăm vârfurile și studiem ce se întâmplă cu muchia 1-2
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 29
1 2
3n
n-1 ...
i
Triangularizări n-gon
• Stabilirea relației de recurență• Muchia 1-2 poate forma un triunghi cu orice vârf 4, 5, ..., n-1
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 30
1 2
3n
n-1 ...
i
Triangularizări n-gon
• Stabilirea relației de recurență• Mai rămân două părți care se pot triangula independent
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 31
1 2
3n
n-1 ...
i
poligon cu i-1
vârfuri
poligon cu n-i+2
vârfuri
Triangularizări n-gon
• Stabilirea relației de recurență• Muchia 1-2 poate forma un triunghi cu vârful 3 sau cu n• Rămâne un (n-1)-gon de triangulat
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 32
1 2
3n
n-1 ...
i
Triangularizări n-gon
• Stabilirea relației de recurență• Dacă conectăm 1-2 la i avem T(i-1)T(n-i+2) modalități
• i poate fi 4, 5, ... n-1
• Dacă conectăm 1-2 la 3 sau cu n avem câte T(n-1) modalități• Rezultă formula de recurență
𝑇 𝑛 =
𝑖=4
𝑛−1
𝑇 𝑖 − 1 𝑇 𝑛 − 𝑖 + 2 + 2𝑇(𝑛 − 1)
• Dacă adoptăm T(2) = 1 atunci
𝑇 𝑛 =
𝑖=3
𝑛
𝑇 𝑖 − 1 𝑇 𝑛 − 𝑖 + 2
• Cazurile nu se suprapun• Recurență corectă!
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 33
Triangularizări n-gon
• Implementare recursivă
long long triangulari(int n){
if (n<=3)
return 1;
long long rez = 0;
for(int i=3; i<=n; i++)
rez += triangulari(i-1)*triangulari(n-i+2);
return rez;
}
• triangulari(20) = 477638700 sub o secundă
• triangulari(30) ?
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 34
Triangularizări n-gon
• Implementare recursivă – cu memoizare
long long T[50] = {[2] = 1, [3] = 1};
long long triangulari(int n){
if (T[n]>0)
return T[n];
long long rez = 0;
for(int i=3; i<=n; i++)
rez += triangulari(i-1)*triangulari(n-i+2);
T[n] = rez;
return rez;
}
• triangulari(20) = 477638700
• triangulari(30) = 263747951750360 câteva millisecunde
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 35
Triangularizări n-gon
• Implementare iterativă
long long triangulari_iter(int n){
long long T[50] = {[2] = 1, [3] = 1};
for(int k=4; k<=n; k++){
for(int i=3; i<=k; i++)
T[k] += T[i-1]*T[k-i+2];
}
return T[n];
}
• Formulă directă – numere Catalan• T(n) = C(n-2), unde
𝐶 𝑛 =1
𝑛 + 12𝑛𝑛
22 noiembrie 2019 Programarea Calculatoarelor - R. Varga 36