Sunteți pe pagina 1din 11

RECURSIVITATE

Recursivitatea reprezintă proprietatea unor noțiuni de a se defini prin


ele însele.
Un algoritm recursiv se caracterizează prin proprietatea că se autoapelează, adică din interiorul lui se
apelează pe el însuşi.
Într-un algoritm recursiv, pentru a realiza un anumit calcul sunt necesare două elemente: 1

1. o formulă de recurenţă; 2. o valoare iniţială cunoscută.


 recurenţă = repetiţie, revenire;
 formulă de recurenţă = formulă care exprimă orice termen dintr-un şir, în funcţie de termenii
precedenţi;

Un algoritm recursiv se implementează folosind o funcţie recursivă.


Se numeşte funcţie recursivă o funcţie care din corpul ei se apelează pe ea însăţi. Un subprogram se
numeşte recursiv dacă se autoapelează.
Orice funcţie recursivă trebuie să îndeplinească două condinţii:
1. să se poată executa cel puţin o dată fără să se autoapeleze;
2. toate apelurile să se producă astfel încât să se tindă spre îndeplinirea condiţiei de execuţie
fără apeluri.
Din afara funcţiei recursive, se face un prim apel la funcţia recursivă, după care funcţia se autoapeleză
de un anumit număr de ori.
 Pentru orice algoritm iterativ există un algoritm recursiv echivalent (rezolvă acceaşi problemă)
şi invers, pentru orice algoritm recursiv există unul iterativ echivalent.
 Autoapelul se face în conformitate cu antetul funcției recursive.
Astfel:
 dacă funcția recursivă este de tip non-void, autoapelul se va face într-o expresie;
 dacă funcția recursivă este de tip void, autoapelul se va face într-o instrucțiune de sine
stătătoare; dacă funcția întoarce valori, se vor folosi parametri de ieșire.

Alte formule recursive:


Tipuri de recursivitate

Aplicație

Ridicarea la putere în timp logarithmic sau exponențierea rapidă , complexitatea sa fiind O(log2n). Ea se
bazează pe următoarea formulă:

Implementare recursivă Implementare iterativă

int Putere(int A,int n) int Putere(int A,int n)


{ {
if(n == 0) int P = 1;
return 1; while(n)
if(n % 2 == 1) {
return A*Putere(A,n-1); if(n % 2 == 1)
int P = Putere(A,n/2); P = P * A;
return P*P; A = A * A;
} n /= 2;
}
return P;
}
Exemplu numeric:

Observații

 Ridicarea la putere rapidă poate fi folosită și pentru:


o operații modulo (determinarea restului împărțirii rezultatului la o valoare dată)
o numere mari
o ridicarea la putere a matricelor
o ridicarea la putere a polinoamelor

Probleme propuse

https://www.pbinfo.ro/probleme/2404/test (ușoară)
https://www.pbinfo.ro/probleme/2398/moka (dificilă)
Algoritmul Divide et Impera
 Împarte problema în subprobleme de acelaşi tip
 Rezolvă fiecare subproblemă independent
 Combină soluţiile subproblemelor pentru a obţine soluţia problemei iniţiale.
 Subproblemele în care se descompun problema dată trebuie să fie:
 de același tip cu problema dată; 5

 de dimensiuni mai mici (mai “ușoare”);


 independente (să nu se suprapună, prelucrează seturi de date distincte).
 DI admite implementare recursivă.

Divide et Impera admite de regulă o implementare recursivă – rezolvarea problemei constă în


rezolvarea unor subprobleme de același tip. Un algoritm pseudocod care descrie metoda este:
Algoritm DivImp(P)
Dacă P este problemă elementară
R <- RezolvăDirect(P)
Altfel
[P1,P2] <- Descompune(P)
R1 <- DivImp(P1)
R2 <- DivImp(P2)
R <- Combină(R1,R2)
SfârșitDacă
SfârșitAlgoritm

Suma elementelor dintr-un vector


Fie un vector V cu n elemente întregi, indexate de la 1 la n. Să se determine suma lor.
int Suma(int V[], int st, int dr)
{
if(st == dr)
return V[st]; // problemă elementară
else
{
int m = (st + dr) / 2; // împărțim problema în subprobleme
int s1 = Suma(V, st, m); // rezolvăm prima subproblemă
int s2 = Suma(V, m + 1, dr); // rezolvăm a doua subproblemă
return s1 + s2; // combinăm rezultatele
} 6

}
int main()
{
int V[101], n;
//citire n si V
cout << Suma(V,1,n);
return 0;
}

Cmmdc al elementelor dintr-un vector


Fie un vector V cu n elemente naturale nenule, indexate de la 1 la n. Să se determine cel mai mare
divizor comun al lor.
La fel în cazul problemei precedente, o transformă într-una cu secvențe. Vom determina cel mai mare
divizor comun al elementelor dintr-o secvență delimitată de indicii st și dr.
CMMDC(V,st,dr)
 dacă secvența este formată dintr-un singur element (st == dr), atunci rezultatul este chiar V[st];
 altfel:
 determinăm indicele de la mijloc, m = (st + dr) / 2;
 determinăm recursiv a = CMMDC(V, st, m);
 determinăm recursiv b = CMMDC(V, m + 1, dr);
 rezultatul este Cmmdc2(a,b), unde Cmmdc2(x,y) este cel mai mare divizor comun a lui x
și y, și poate fi determinat cu algoritmul lui Euclid.

MergeSort (1945, von Neumann)


Sortarea prin interclasare, sau Mergesort este o metodă eficientă de sortare a elementelor unui
tablou, bazată pe următoarea idee: dacă prima jumătate a tabloului are elementele sortate și a doua
jumătate are de asemenea elementele sortate, prin interclasare se va obține tabloul sortat.
Preambul (interclasarea vectorilor ordonați):
Sortarea prin interclasare este un exemplu tipic de algoritm divide et impera.
Rezolvare:
 Se divide vectorul de dimensiune n în doi vectori de dimensiune n/2
 Procesul de divizare(împărţire) a subvectorilor continuă până când se ajunge la vectori de
dimensiune 1
 Se interclasează elementele a doi subvectori aparţinând aceleiaşi divizări şi se obţine un vector
sortat
7
 Procesul de interclasare continuă (bottom-up) până este sortat vectorul iniţial.

void DI(int st, int dr) void Merge(int st, int mid, int dr)
{ // interclasam secventa [st,mid] cu
if(st < dr) secventa [mid+1,dr] in vectorul b
{ {
int mid =(st+dr)/2; int b[100001],i,j,k;
DI(st,mid); i=st; j=mid+1;k=1;
DI(mid+1,dr); while(i<=mid && j<=dr)
Merge(st,mid,dr); if(a[i]<a[j]) b[k++]=a[i++];
} else b[k++]=a[j++];
} while(i<=mid)
b[k++]=a[i++];
while(j<=dr)
b[k++]=a[j++];
k=1;
for(i=st;i<=dr;i++)
a[i]=b[k++];
}

Aplicație

https://www.pbinfo.ro/probleme/1025/mergesort
QuickSort
QuickSort sau Sortarea rapidă este este unul dintre cei mai rapizi şi mai utilizaţi algoritmi de sortare
până în acest moment, bazându-se pe tehnica „Divide et impera“, algoritm descoperit în 1960 de
programatorul britanic C.A.R. Hoare.
Pentru un set de n valori oarecare algoritmul efectuează O(nlogn) comparații, dar în cazul cel mai
nefavorabil se efectuează O(n2) comparații. 8

Algoritmul este de tip divide et impera; el sortează o secvență a tabloului (inițial întreg tabloul), astfel:
 se alege un element special al listei, numit pivot;
 se ordonează elementele listei, astfel încât toate elementele din stânga pivotului să fie mai mici
sau egale cu acesta, și toate elementele din dreapta pivotului să fie mai mari sau egale cu
acesta;
 se continuă recursiv cu secvența din stânga pivotului și cu cea din dreapta lui.
void qsort(int st, int dr)
{
int i, j, m = (st+dr) / 2;
int piv = x[m]; // determinăm pivotul
i=st; j=dr;
while (i <= j){
while(x[i] < piv) i++;
while(piv < x[j]) j--;
if (i <= j){
swap(x[i], x[j]);
i++; j--;
}
}
if (i < dr) qsort(i, dr);
if (st < j) qsort(st, j);
}

*** Funcţia qsort este inclusă în stdlib.h şi se apelează astfel:


qsort(numeVector,nrElemente,sizeof(element),functieComparare);
Aplcație

https://www.pbinfo.ro/probleme/1024/quicksort (medie)

Observații:
 în timpul pivotării:
o la fiecare iterație, doar una dintre variabilele i și j se modifică: sau crește i, sau scade j
9
o pivotul este elementul cu indicele care nu se modifică
 algoritmul descris mai sus realizează ordonarea crescătoare a tabloului; pentru ordonarea
descrescătoarea algoritmul este asemănător: prin pivotare, elementele din stânga pivotului
devin mai mari decât acesta, cele din dreapta devin mai mici;
 algoritmul este cu atât mai rapid cu cât la fiecare etapă cele două secvențe delimitate de pivot
au lungimi cât mai apropiate (ideal egale);
 dacă tabloul este de a început ordonat crescător sau descrescător, complexitatea algoritmului
devine pătratică – O(n2);

Turnurile din Hanoi

Problemă: Se dau trei tije, a, b şi c. Pe tija a se află n discuri de dimensiuni diferite, ordonate în ordinea
diametrelor (discul cel mai mare la bază). Se doreşte mutarea tuturor discurilor de pe tija a pe tija b,
utilizând tija intermediară c, cu condiţia ca un disc c, cu condiţia ca un disc cu diametrul mai mare să nu
fie pus pe vreo tijă peste un disc cu dimensiune mai mică.

Rezolvare:

 Se caută o modalitate de divizare a problemei inițiale în subprobleme identice.

Exemplu numeric:

Astfel, pentru n=2 avem:

 H(2,a,b,c) = H(1,a,c,b),ab,H(1,c,b,a) = ac,ab,cb

iar pentru n=3 rezultă:

 H(3,a,b,c) = H(2,a,c,b),ab,H(2,c,b,a) =
H(1,a,b,c),ac,H(1,b,c,a),ab,H(1,c,a,b),cb,H(1,a,b,c)
= ab,ac,bc,ab,ca,cb,ab.
void Hanoi (int n, char a, char b, char c)
{

if (n == 1) g << a << "->" << c << "\n";


else {
Hanoi(n-1, a, c, b);
g << a << "->" << c << "\n";
Hanoi(n-1, b, a, c);
} 10

}
https://www.openbookproject.net/py4fun/hanoi/hanoi3.html

*Isoric:
François Édouard Anatole Lucas (cunoscut ca Édouard Lucas), inventatorul jocului, a fost un
matematician francez care a trăit între anii 1842-1891. În studiul său, "Récréations Mathématiques, Vol.
3, pp. 55–59, Gauthier-Villars, Paris, 1893", a notat faptul că există în templul Kashi Vishwanath,
Varanasi, India, un set de 64 de discuri de aur (foarte fragile) pe 3 tije diamantate numite "Turnurile din
Brahma".
Călugării încearcă să rezolve problema matematică "de la începuturile timpului". Deși aparent o poveste
imaginară, o legendă, problema necesită 1.84 × 1019 mutări pentru a fi rezolvată. La o mutare pe
secundă sunt necesare 5.85 x 1011 ani. Legenda spune că atunci când turnul discurilor de aur va fi
complet transferat pe o altă tijă, templul se va prăbuşi iar lumea va lua sfârşit.

Aplicație

https://www.pbinfo.ro/probleme/2527/hanoi (ușoară)

Probleme propuse

https://www.pbinfo.ro/probleme/1149/existaprimedivimp (ușoară)
https://www.pbinfo.ro/probleme/1157/ksort2 (ușoară)
https://www.pbinfo.ro/probleme/1023/cmmdc3 (ușoară)
https://www.pbinfo.ro/probleme/3165/matrdivimp1 (ușoară)
Problema Matrice_Div_Et_Imp | www.pbinfo.ro (medie)
https://www.pbinfo.ro/probleme/3206/nrinversiuni (medie)
Observație

O analiză interesantă a complexității algoritmilor de sortare găsiți aici, platformă - laborator a Universității
Politehnice din București:
http://elf.cs.pub.ro/sda-ab/wiki/laboratoare/laborator-02 (Bubble sort - interschimbare; Selection sort - selecţie
Insertion sort – inserare; Merge sort – interclasare; Quick sort - partiţionare).
Se descrie un algoritm de sortare prin:
 timp mediu - timpul de execuţie la care ne aşteptăm, în medie, pentru sortare 11
 timp la limită- timpul de execuţie pentru cel mai rău caz posibil
 memorie - memoria maximă de care are nevoie algoritmul pentru sortare(excludem memoria deja
alocată înainte de algoritm → vectorul efectiv ce va fi sortat)
 stabilitate - un algoritm stabil păstrează ordinea în care apar două elemente cu aceeaşi cheie(atributul
după care sortăm)
Bubble sort Selection sort Insertion sort
 timp mediu: O(N^2)  timp mediu: O(N^2)  timp mediu: O(N^2)
 timp la limită: O(N^2)  timp la limită: O(N^2)  timp la limită: O(N^2)
 memorie: O(1)  memorie: O(1)  memorie: O(1)
 Stabil: DA  Stabil: DA  Stabil: DA
Merge sort Quick sort Intro sort (STL)
 timp mediu: O(N log N)  timp mediu: O(N log N)  timp mediu : O(N log N)
 timp la limită: O(N log N)  timp la limită: O(N^2)  timp la limită : O(N log N)
 memorie: O(N)  memorie: O(log N)  Memorie folosita : O(log N)
 Stabil: DA  Stabil: NU  Stabil : DA
sort-ul din STL este o combinați
între QuickSort randomizat și
HeapSort, numită IntroSort.

Alți algoritmi de sortare care necesită structure de date mai complexe:


http://elf.cs.pub.ro/sda-ab/wiki/laboratoare/laborator-09 (Shell Sort; Heap Sort; Radix Sort; qsort; sort)

Prof. Gabriela Nodea


Colegiul Național ”Tudor Vladimirescu”

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