Sunteți pe pagina 1din 4

Eficienta algoritmilor

Un algoritm este o metodă generală de rezolvare a unui anumit tip de problemă. Pentru o
problemă pot fi găsiţi mai mulţi algoritmi, dintre aceştia alegându-se cel mai bun.
Criteriul de comparaţie între algoritmi este eficienţa lor. Eficienţa unui algoritm depinde atât
de metodele de programare cât şi de tipurile de date utilizate. Eficienţa se exprimă în funcţie de
resursele de calcul necesare aplicării algoritmului. Resursele de calcul sunt spaţiul de memorie
ocupat pentru stocarea datelor folosite de algoritm şi timpul de execuţie al algoritmului.

I. Spaţiul necesar stocării datelor se exprimă prin cantitatea de memorie ocupată de toate
variabilele declarate în programul care implementează algoritmul. De exemplu, dacă un program
conţine declaraţiile
int v[100], n, i;
float x[100];
cantitatea de memorie necesară păstrării acestor variabile va fi egală cu:
4*100+4+4 octeţi pentru datele de tip int
+
4*100 octeţi pentru datele de tip float

Calculele de mai sus s-au făcut în funcţie de spaţiul rezervat la declarare pentru
reprezentarea tipurilor elementare de date existente în C (corespunzător CodeBlocks).
Tip Dimensiune (în octeţi)
char 1
int 4
long 4
long long 8
float 4
double 8
long double 12

II. Pentru estimarea timpului de execuţie se calculează numărul de operaţii elementare


executate de algoritm (operaţie elementară  atribuire, operaţie aritmetică, comparaţie, operaţie
logică).
Analiza timpului de execuţie se face pe cazul cel mai favorabil, cel mai defavorabil sau pe
cazul mediu (al unor date de test arbitrare). Analiza pe cazul cel mai favorabil furnizează limita
inferioară a timpului de execuţie. Analiza pe cazul cel mai defavorabil furnizează limita superioară a
timpului de execuţie. Analiza pe cazul mediu este media timpilor de execuţie corespunzători unor
date de intrare pentru care se cunoaşte probabilitatea de apariţie.

Exemplu: căutarea secvenţială a unei valori într-un vector. Operaţia dominantă este cea de
comparare a valorii x cu câte un element din vector.
V=(1, 8, 2, 4, 7, 6) Număr de comparaţii efectuate
Cazul cel mai favorabil apare pentru x=1 1
Cazul cel mai defavorabil apare pentru x=6 6
Cazul mediu presupune că valoarea căutată x s-ar putea afla cu aceeaşi probabilitate în orice
poziţie din tablou.
Notând cu p - probabilitatea ca x să fie în V într-o poziţie i oarecare
T - timpul mediu de execuţie al algoritmului
Ti - timpul necesar pentru a compara valoarea x cu toate elementele din
poziţia 1 până în poziţia i (Ti este egal cu i unităţi de timp, deoarece sunt necesare i
operaţii de comparare între x şi valorile V1, V2, ..., Vi ), atunci
T=(p*T1+p*T2+...+p*Tn)/n=p*(1+2+…+n)/n=p*(n+1)/2
Concluzia este că timpul mediu de căutare secvenţială a unei valori într-un vector
depinde liniar de n, dimensiunea vectorului în care se caută.

Dacă un algoritm necesită timp de execuţie de ordin n, se spune că se execută în timp liniar,
iar algoritmul respectiv se numeşte algoritm liniar. Similar, un algoritm este numit pătratic, cubic,
logaritmic, polinomial sau exponenţial, dacă necesită timp de execuţie de ordin n2, n3, log n, nk,
respectiv cn, unde k şi c sunt constante.

Algoritmii de însumare a elementelor unui vector sau de determinare a minimului dintr-


un vector sunt liniari, fiind necesară o singură parcurgere a vectorului pentru obţinerea rezultatului.
Algoritmul de căutare binară a unei valori într-un şir de n numere are complexitatea log2n.
Operaţia de căutare se va relua de fiecare dată într-un interval cu lungime înjumătăţită faţă de
precedentul, în fiecare interval executându-se o comparaţie.
Dacă se efectuează căutarea secvenţială a unei valori într-un vector de 1000 de elemente
ordonate, în cazul cel mai defavorabil ar fi necesare 1000 de comparaţii. Dacă se efectuează căutarea
binar, vor fi necesare maxim 10 comparaţii (210=1024).
Metoda bulelor de ordonare crescătoare a unui vector parcurge în mod repetat vectorul şi
compară perechi de elemente consecutive. Dacă vectorul este ordonat crescător de la început (cazul
cel mai favorabil), este necesară o singură parcurgere a lui. În cazul în care vectorul este ordonat
descrescător (cazul cel mai defavorabil) sunt necesare n-1 parcurgeri. După fiecare parcurgere un
element ajunge la locul lui şi nu va mai fi folosit în comparaţii la etapele următoare. Pentru cea de-a
i-a parcurgere se vor efectua n-i comparaţii. În total se execută (n-1)+(n-2) +…+1=n*(n-1)/2
comparaţii. Algoritmului este deci pătratic. Algoritmii de sortare prin selecţie, inserţie,
numărare necesită tot un timp pătratic de execuţie. Ei sunt acceptabili pentru n mic. Pentru cazul
în care n este mare, algoritmii mergesort şi quicksort au un timp mediu mai bun, de ordin n* log2n,
primul necesitând însă spaţiu de memorie suplimentar.
Optimizarea unui algoritm înseamnă modificarea lui pentru a-l face mai eficient.

Exemple: 1. Nr prim
prim=1;
if(n%2==0 && n>2) prim=0;
else
{
int k=sqrt(n);
for(int d=3; d<=k && prim; d=d+2)
if(n%d==0) prim=0;
}

2. Calculul cmmdc a două numere prin scăderi succesive poate fi îmbunătăţit dacă se
folosesc împărţiri succesive, cunoscut fiind faptul că o împărţire este o scădere repetată.
while(a!=b) a=30 b=7
if(a>b) a=a-b; a=23 b=7
else b=b-a; a=16 b=7
a=9 b=7
a=2 b=7  după 5 paşi
a=2 b=5
a=2 b=3
a=2 b=1  după 8 paşi
a=1 b=1
r=a%b; a=30 b=7 r=2
while(r!=0) a=7 b=2 r=1  la al doilea pas
{ a=2 b=1 r=0  după 3 paşi
a=b;
b=r;
r=a%b;
}

3. Calculul numărului de cifre existente într-un număr natural n.


int nrCifre=0; int nrCifre;
while(n) nrCifre=(int)log10(n)+1;
{ cout<<nrCifre;
nrCifre++;
n/=10;
}
cout<<nrCifre;
În prima variantă, algoritmul face atâtea împărţiri la 10 câte cifre are numărul. Complexitatea lui este
logaritmică, ordinul de mărime fiind log10n.
În varianta a doua algoritmul are complexitate 1, timpul de execuţie nedepinzând de n.
Presupunând că n are nrCifre, înseamnă că este adevărată relaţia 10nrCifre-1≤n<10nrCifre. Logaritmând,
rezultă nrCifre-1≤ log10n<nrCifre. Din care se obţine nrCifre=[log10n]+1.

4. Calculul numărului de divizori ai unui număr natural n.


Varianta 1. Se parcurg toate numerele între 2 şi n/2 şi se testează dacă sunt divizori proprii pentru n.
if(n==1) nrDiv=1;
else nrDiv=2; //se iniţializează cu numărul divizorilor improprii
for(d=2; d<=n/2; d++)
if(n%d==0) nrDiv++;

Varianta 2. Se descompune n în factori primi. n=f1e1*f2e2*…*fkek. Numărul de divizori este


(e1+1)*(e2+1)*…*(ek+1).
De exemplu, 100 are descompunerea 22*52. El are 3*3=9 divizori, obţinuţi din produsul cartezian al
mulţimilor {1, 2, 4} şi {1, 5, 25}, adică 1, 5, 25, 2, 10, 50, 4, 20, 100.
nrDiv=1;
d=2;
while(n!=1)
{
e=0;
while(n%d==0)
{
e++;
n/=d;
}
nrDiv=nrDiv*(e+1);
d++;
}

Considerând că dominantă este operaţia de împărţire cu rest, pentru n=10000, prima variantă de calcul va
efectua 4999 de împărţiri (pentru valorile d=2, …, 5000), a doua 10 (de 4 ori la d=2, o dată la d=3, o dată la
d=4, de 4 ori la d=5)
5. Nr cuburi perfecte <=n=radical de ordin 3 din n
6.suma cuburi perfecte <=n= n2*(n+1)2/4
7.Numerele care nu sunt patrate perfecte au nr par de divizori
8.Singurele nr cu nr impar de divizori sunt patratele perfecte
9.Ciurul lui Erastotene
Pentru i0,n executa v[i]0
v[0]=v[1]=1;
pentru i2,sqrt(n) executa
daca v[i]=0 atunci
pentru j2,[n/i] executa
v[i*j]1
10. 1<<n (2n )
11. n>>k (n/2k)
12. n&1 =0 daca n este par, =1 daca n este impar
13 .n&(n-1) = 0 daca n este putere a lui 2

Comparativ, pentru diferite valori ale lui n şi complexităţi ale algoritmilor, durata lor de execuţie
este:

Tema:
#18, #570, #388

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