Documente Academic
Documente Profesional
Documente Cultură
La modul general, analiza complexității unui algoritm are ca scop estimarea volumului
resurselor de calcul necesare pentru execuția algoritmului:
Dacă în anii 1990 spațiul de memorie utilizat de un program era o resursă critică datorită
capacității reduse a memoriilor calculatoarelor din acea vreme, astăzi aceast factor este mai
puțin important. Calculatoarele actuale au suficientă memorie pentru procesările obișnuite.
Kevin Kelly in The New York Times, “the whole written literature of the humanity, from the
appearence of writting and in all languages, does not go over 50 petabytes” (pentabyte =
1015bytes).
Bineînțeles ca volumul resurselor necesare depinde de volumul datelor de intrare. Mai precis,
dimensiunea datelor de intrare este calculată după numărul de biți necesari pentru stocarea
datelor. Mărimea unei instanțe, notată cu |x| este dată de numărul de biți necesari pentru
stocarea lui x. Astfel, când vorbim de stocare, |x| este numărul de elemente care se sortează. La
algoritmii numerici, |x| este valoarea numerică a instanței x.
Dintre cele două resurse de calcul, cea critică este timpul de execuție. Acesta depinde de
numărul de operații care trebuie efectuate și de dimensiunea datelor de intrare. Deci, timpul de
execuție diferă de la rulare la alta.
Având in vedere că pentru seturi de date de intrare diferite, un același algoritm operează în
timpi de execuție diferiți, analiza complexității algoritmilor tratează cu precădere două cazuri:
Un algoritm este considerat eficient dacă necesită un volum rezonabil de resurse de calcul.
Unitatea de măsură pe baza căreia vom calcula eficiența teoretică a unui algoritm derivă din
principiul invariației potrivit căruia două implementări diferite ale aceluiași algoritm nu diferă
în eficiență cu mai mult decât o constantă multiplicativă. Adică, presupunând ca avem două
implementări de T1(|x|) respectiv T2(|x|) secunde pentru un caz de mărime |x|, există o constantă
pozitivă c astfel încât T1(|x|) < cT2(|x|), pentru orice |x| suficient de mare.
Dacă un algoritm necesită un timp de ordin |x| vom spune că necesită un timp liniar sau că este
un algoritm liniar. Similar, un algoritm este pătratic, cubic, polinomial sau exponențial dacă
necesită timp de ordinul |x|2, |x|3, |x|k respectiv k|x|, unde k este o constantă.
Exemplu. Să se analizeze algoritmul care determină factorialul unui număr n, adică calculează
n! = n(n-1)(n-2) ... 2⋅1
4
Procesoarele actuale pot executa miliarde de operații pe secundă. Astfel, un număr constant de unități de
timp în plus sau în minus nu impactează ordinul de creștere al timpului de execuție.
48
Analiza complexităţii algoritmilor nerecursivi
Algoritm Cost
function factorial(n)
fact ← 1 1
i←2 1
while (i <= n) do n
fact ← fact × i 2(n-1)
i ← i +1 2(n-1)
endwhile
return fact 1
endfunction
Cazul cel mai favorabil (CCMF) furnizează o margine inferioară a timpului de execuție. Acest caz e mai
puțin util în analiza eficienței algoritmilor însă permite identificarea algoritmilor ineficienți – acei
algoritmi care au un timp de execuție mare și în cel mai favorabil caz.
Cazul cel mai defavorabil (CCMD) reprezintă cel mai mare timp de execuție furnizînd limita superioară
pentru acesta. În analiza algoritmilor, marginea superioară este mai importantă decât marginea
inferioară.
În practică aceste cazuri extreme (CCMF și CCMD) se întălnesc rar, astfel că analiza acestora
nu furnizează suficientă informație despre comportamentul algoritmului studiat. Însă pot
delimita zona în care poate varia timpul de execuție al algoritmului.
49
Analiza complexităţii algoritmilor nerecursivi
Cazul mediu (CM) se bazează pe cunoașterea distribuției de probabilitate a datelor de intrare, adică
pe estimarea probabilității de apariție a fiecăreia din instanțele posibile. Timpul mediu de execuție
este valoarea medie (în sens statistic) a timpilor de execuție corespunzători diferitelor instanțe.
Timpul mediu de execuție reprezintă o valoare medie a timpilor de execuție calculată în raport cu
distribuția de probabilitate a datelor de intrare.
Dacă probabilitatea de apariție este aceeași pentru toate clasele (cazuri echiprobabile) atunci:
Tmediu(|x|) = ∑ (| |)
Observație. Datorită dificultăților ce pot interveni în stabilirea timpului mediu și mai ales,
datorită faptului că, în cele mai multe cazuri, acesta diferă de timpul în cazul cel mai defavorabil
doar prin valori ale constantelor implicate, analiza algoritmilor se rezumă la estimarea timpului
de execuție pentru cazul cel mai defavorabil.
50
Analiza complexităţii algoritmilor nerecursivi
datelor de intrare se face analiza pe cele 3 cazuri: CCMF, CCMD și CM. Dacă TCCMF(|x|) ≠ TCCMD(|x|) se
determină și TCM(|x|).
4. Expresia matematică a lui T(|x|) sau, pentru algoritmi care depind de proprietățile datelor de intrare,
a lui TCM(|x|) (după caz se poate calcula direct TCCMD(|x|)) dă ordinul de complexitate al algoritmului.
Ieșire: o permutare v[σ(1)], ..., v[σ(n)] astfel încât: v[σ(1)] < ... < v[σ(n)]
...
CCMF: tabloul este sortat ⇒ δ(i)=0, i=2, ⇒ TCCMF(n) = c1 + c2n + c3(n-1) + c4(n-1) + c5(n-
1) + c6∑ 1 + c9(n-1) = k1 + k2n.
()= 1 ă 2
i=2, ; i=3, ( ) = ; ...
0 0
deci obținem δ(i)=i-1, i=2, ⇒ TCCMD(n) = c1 + c2n + c3(n-1) + c4(n-1) + c5(n-1) + c6∑ +
c7 ∑ ( − 1) + c8 ∑ ( − 1) + c9(n-1) = k1 + k2n + k3n2
51
Analiza complexităţii algoritmilor nerecursivi
CM: toate permutările au aceeași probabilitate de apariție. Timpul de execuție în acest caz are
aceeași expresie ca în cazul CCMD, deoarece în medie δ(i)=i/2 (în general, pentru un element
oarecare v[i] avem în medie jumătate de elemente din vector mai mari decât el și jumatate mai
mici, deci în medie ar trebui să parcurgem jumatate de vectorul v[1... i-1] pentru a-l insera în
poziția corectă.
Și în acest caz timpul de execuție depinde de valorile din tabloul de intrare. Vom face o analiză
pe toate cele trei cazuri:
Cazul cel mai favorabil CCMF când elementul x aparține vectorului și v[1] = x
CCMF CM CCMD
δ1(n) 1 k n
δ2(n) 1 1 0
Marginile timpului de execuție sunt date de cazul cel mai favorabil (limita inferioară) și cazul
cel mai defavorabil (limita superioară).
unde |x| notează dimensiunea datelor de intrare, care în acest caz este n.
Deci timpul mediu de execuție pentru algoritmul de căutare secvențială depinde liniar de
dimensiunea datelor de intrare.
Din formula timpului mediu putem să determinăm formulele pentru timpul de execuție în cazul
în care elementul se află în tablou: p=1:
+1
/∈1& … ' ( )=
2
ceea ce se poate interpreta astfel: în cazul aparteneței elementului la vector, algoritmul parcurge
în medie jumătate din vector.
În cazul non-aparteneței elementului la vector (cazul cel mai defavorabil) avem p=0, ceea ce
implică:
Definiție. Numim termen dominant, termenul din expresia timpului de execuție care devine
semnificativ mai mare când dimensiunea problemei crește. Astfel, el dictează comportamentul
algoritmului, pe date de intrare de dimensiune mare.
Exemple de expresii pentru timp de execuție (în bold este scris termenul dominant)
53
Analiza complexităţii algoritmilor nerecursivi
- T1(n) = an+b
- T2(n) = alogn+b
- T3(n) = an2 + bn + c
- T4(n) = an + bn + c
Definiție. Ordinul de creștere caracterizează creșterea termenului dominant din expresia timpului de
execuție în raport cu dimensiunea problemei. Mai precis, ordinul de creștere cuantifică creșterea
termenului dominant când dimensiunea problemei crește de k ori.
Exemple:
TD2(n) = alogn ⇒ TD2 (kn) = alog(kn) = a(logk + logn) = alogk + TD2(n) – creștere logaritmică
TD3(n) = an2 ⇒ TD3 (kn) = a(kn)2 = k2⋅ an2 = k2TD3(n) – creștere pătratică
( > )
∑ = - sumă liniară, complexitate pătratică
( > )( > )
12 + 22 + … + n2 = ?
= @n3 + n2 + ?n – complexitate cubică
BC<
∑ A
= - complexitate polinomială
A>
A DC< E
∑ ! = - complexitate exponențială (nu contează baza)
AE
54
Analiza complexităţii algoritmilor nerecursivi
∑ ( ±G )=∑ ±∑ G
∑ H = H∑
∑ A1 = −!+1
ln(∏A A) = ∑A ln( A)
55
Analiza complexităţii algoritmilor nerecursivi
Exerciţii
Exercițiu 1. Considerăm problema calculării sumei primelor n numere naturale ∑ .
Dimensiunea acestei probleme poate fi considerată n.
Obținem că T(n) = c1 + c2 + (n-1)c3 + nc4 + nc5 = nk1 + k2 deci timpul de execuție depinde
liniar de dimensiunea datelor de intrare, adică de n.
Exercițiu 2. Considerăm problema înmulțirii a două matrici A(m,n) și B(n, p). În acest caz
dimensiunea datelor de intrare este data de tuplul (m, n, p).
Spre deosebire de cazurile anterioare, timpul de execuție al acestui algoritm nu poate fi stabilit
cu certitudine, el depinzând de valorile tabloului de intrare. Cazul cel mai favorabil (CCMF)
este atunci când minumul se află pe prima poziție in vector. În acest caz intrucțiunea 4. nu se
va mai executa și avem δ(n) = 0.
Cazul cel mai defavorabil (CCMD) este când vectorul e sortat descrescător deci minimul se
află pe ultima poziție în tablou și instrucțiunea 4. se execută de un număr maxim de ori. În acest
caz δ(n) = n-1. În ambele cazuri, atât limita inferioară cît și limita superioară depind liniar de
dimensiunea problemei, deci T(n) = k1n + k2.
Exercițiu 4. Să se definească un algoritm care stabileste dacă un vector are numai elemente
distincte.
57
Analiza complexităţii algoritmilor nerecursivi
Operația dominantă a algoritmului este testarea egalității de pe rândul 7. deoarece este cea mai
imbricată operație a algoritmului (cea mai repetitivă).
CCMF (depinde de valorile din vectorul v): primele două elemente sunt egale
CCMF CCMD CM
δ1(n) 1 n-1 (n-1)/2
E E
( − )
δ2(n) 1 K( − ) K
2
δ3(n) 1 1 sau 0 1
5
Repetările în cazul instrucţiunilor FOR contorizează numărul de executări pentru incrementările variabilelor
contor.
58
Analiza complexităţii algoritmilor nerecursivi
2. for j ← i+1, n do k2 ∑ E
( − )
3. if (v[i]>v[j]) then c3 ∑ E
( − )
4. v[i] ↔ v[j] k4 δ(n)
5. endif
6. endfor
7. endfor
T(n) = k1(n-1) + k2 ∑ E
( − ) + c3∑ E
( − ) + k4 δ(n).
CCMF CCMD CM
(vectorul e sortat crescător) (vectorul e sortat descrescător)
E E
( − )
δ(n) 0 K( − ) K
2
Cazul cel mai defavorabil pentru funcţia δ(n) corespunde numărului de executări ale
instrucţiunii IF care este operaţia dominantă a algoritmului, deci acest algoritm are o
complexitate pătratică.
Obţinem că în cazul cel mai defavorabil (CCMD) ca și în cazul mediu (CM) δ(n) = n(n-1)/2,
deci ordin pătratic de creştere pentru termenul dominant.
Discuţie. Complexitatea algoritmului QuickSort în cazul cel mai defavorabil este O(n2) iar în
cazul mediu este O(n⋅logn).
Algoritmul QuickSort încearcă partiţionarea vectorului în doi vectori mai mici, până când
întregul vector este sortat (algoritmul QuickSort este de tipul Divide et Impera).
La fiecare apel al funcţiei se încearcă poziţionarea elementelor mai mici în partea stângă şi a
elementelor mai mari în partea dreapta (dacă se implementează sortarea crescătoare).
Cazul cel mai defavorabil, ca la toţi algoritmii de sortare corespunde situaţiei în care vectorul
este sortat descrescător. Deci în acest caz toate elementele din partea dreaptă trebuie aduse în
partea stângă ⇒ n/2 pași, în total n⋅ n/2 pași ⇒ complexitate pătratică.
Dacă elemente au o probabilitate medie de a nu se afla în ordinea dorită atunci vom efectua
logn mutări dintr-o parte în alta, deci complexitatea algoritmului este O(n⋅ log n).
59