Sunteți pe pagina 1din 47

Analiza și Proiectarea Algoritmilor

FCIM, UTM

Buldumac Oleg, asistent universitar


Ce este un Algoritm?

Algoritmul reprezintă o serie de pași pe care trebuie să îi


urmăm pentru a rezolva o anumită problemă
Complexitatea algoritmilor

Complexitatea unui algoritm sereferă la cantitatea de resurse consumate la execuţie

- complexitate în timp

- complexitate în spațiu

Complexitatea timp sau timpul necesar execuţiei programului


depinde de numărul de operaţii elementare efectuate
Analiza asimptotică

Analiza asimptotică este o analiză a algoritmilor se concentrează pe:

- se analizează probleme cu dimensiuni mari de intrare

- se lasă numai termenul cel mai semnificativ al funcției

- se ignoră coeficientul termenului celui mai semnificativ


Limbaj pseudocod

Pseudocodul este o descriere la nivel înalt a unui algoritm

Mai puțin detaliat decât un program

Notare preferabilă pentru descrierea algoritmilor

Ascunde problemele legate de proiectarea a programului


O(1) – Timp constant

O(1) descrie algoritmi care necesită aceeași cantitate de timp


pentru execuție indiferent de dimensiunea intrării

Dacă o funcție necesită același timp pentru a procesa o problema din 10


elemente, precum și pentru 10000 de elemente, atunci spunem că are o rată de
creștere constantă sau O(1)

Exemple de algoritmi cu durata de execuție în timp constant O(1):

- printarea primului element din tablou


- verificarea unui element dintr-un tablou dacă este egal cu null
- verificarea dacă un număr este par sau impar
- salt la elementul Următor/Precedent într-o listă dublu înlănțuită
Nu contează dacă n este 10 sau 150000,
linia 2 se va executa doar o singură dată.

Operațiile primitive cum ar fi suma, înmulțirea, scăderea, divizarea,


modulo, operatorii pe biți au un timp de execuție constant.
O(n) – Timp liniar

Algoritmii de timp de rulare liniari sunt foarte des întîlniți.

Implică vizitarea fiecărui element din intrare în cel mai rău caz.

Complexitatea în timp liniar O(n) înseamnă că, pe măsură ce


intrarea crește, algoritmii se execută proporțional mai mult. O
funcție cu o complexitate în timp liniară are o rată de creștere n.

Exemple de algoritmi cu durata de execuție în timp liniar O(n):

- printarea tuturor valorilor dintr-un tablou


- găsirea unui element dat într-un tablou
- găsirea valorii maxime dintr-un tablou
- compararea a două șiruri de caractere (string)
Găsirea celei mai mari valori dintr-un tablou nesortat

Cîte operații va face funcția findMaxElement?

Deci, se verifică fiecare element din tabloul n.

Dacă elementul curent este mai mare decât max, se va efectua o atribuire.

Observați că am adăugat un counter, astfel încât să ne poată ajuta


să numărăm de câte ori este executat blocul interior.
T(n) = 10n - 4

Aplicând analiza asimptotică lăsăm cel mai semnificativ termen care este n.
Folosind notația Big O obținem: O(n).

Putem verifica numărul de iterații făcute folosind contorul nostru.

Dacă tabloul va conține 20 elemente atunci counter va fi egal la fel cu 20.


* O(n)
O(n²) - Timp pătratic

O funcție cu o complexitate de timp pătratică are o rată de creștere n².

Dacă intrarea are dimensiunea 5, va efectua 25 operații.

Dacă intrarea are dimensiunea 12, va dura 144 și așa mai departe.

Exemple de algoritmi cu durata de execuție în timp pătratic O(n²):

- sortarea elementelor dintr-un tablou folosind (sortarea prin


inserție, sortarea prin selecție sau sortarea prin interschimbare)

- verificarea dacă un tablou conține valori care se repetă

- traversarea unui tablou bidimensional


T(n) = 6n² - 6n - 1

Din nou, când avem o analiză asimptotică, abandonăm toate constantele și


lăsăm termenul cel mai semnificativ n². Deci, în notația O mare va fi O(n²).
Putem verifica numărul de iterații făcute folosind contorul nostru.

Dacă tabloul va conține 20 elemente atunci counter va fi egal 400 în cel mai rău caz.

Cel mai rău caz → tabloul nu conține elemente care se repetă


(defavorabil)
Bubble Sort (Sortarea prin interschimbare)

Cazul cel mai favorabil: Ω(n) → tabloul este deja sortat

Cazul mediu: Θ(n2) → elementele sunt aranjate aleator

Cazul cel mai rău(defavorabil): O(n2) → tabloul este invers sortat


Se poate de observat că pentru o lungime foarte mare a tabloului,
timpul necesar pentru rezolvarea problemei crește foarte mult.

Cînd o funcție are o singură buclă de obicei complexitatea de execuție


în timpe este O(n).

Cînd într-o funcție sunt 2 bucle imbricate timpul de execuție va fi pătratic: O(n²),
dar nu tot timpul este așa.
Buclele imbricate(nested loops) sunt concepte programatice constituite
din două sau mai multe bucle plasate una în interiorul celeilalte.

Bucla cea mai din interior este executată de cele mai multe ori, în timp
ce bucla cea mai exterioară, de cele mai puține ori.

Acest lucru se datorează faptului că buclele interioare trebuie să-și


efectueze toate ciclurile pentru fiecare ciclu al buclei imediat exterioare.
O(nc) - Timp polinomial

Rularea polinomială este reprezentată ca O(nc) când c> 1.

În exemplele anterioare două bucle imbricate se execută în


O(n²), deoarece în majoritatea cazurilor trebuie să treacă prin
elementele tabloului de două ori.

Se execută trei bucle imbricate într-o complexitate de timp


cubică ? În majoritatea cazurilor, da!
De obicei dorim să ne ținem mai departe de algoritmii ce se
execută într-o complexitate de timp polinomială(O(n2), O(n3),
O(n4), etc.) deoarece odată ce mărimea problemei crește rapid
la fel de rapid crește și timpul de execuție(numărul de operații
efectuate).

De menționat că algoritmii ce se execută în complexitate de


timp polinomială nu sunt cei mai lenți, există complexități ce
cresc și mai rapid.
O(log n) - Timp logaritmic

Complexitatea în timp logaritmică se aplică de obicei algoritmilor


care împart problemele în două subprobleme de fiecare dată.

Divide et impera(dezbină și stăpînește)

Exemple de algoritmi cu durata de execuție în timp logaritmic O(log n):

- găsirea unei persoane într-o carte telefonică

- găsirea unui cuvânt într-un dicționar fizic

- căutarea binară

- găsirea numărului cel mai mare / cel mai mic dintr-un arbore
binar de căutare
n log2n
0 Nu există
1 0
32 5
128 7
512 9
1024 10
16384 14
65536 16
262144 18
1048576 20
4294967296 32
O funcție logaritmică este opusul unei funcții exponențiale.
Când spui că ceva crește exponențial, se înmulțește.
Când ceva crește logaritmic, este împărțit.
Căutare binară (Binary search)

Cazul cel mai favorabil: Ω(1) → elementul este în mijloc

Cazul mediu: Θ(log n) → elementul nu se află în mijlocul tabloului

Cazul cel mai rău(defavorabil): O(log n) → elementul este pe prima sau ultima poziție
Acet algoritm împarte intrarea în jumătate de fiecare dată și
se dovedește că log(n) este funcția care se comportă astfel.
Calculul complexității în timp al algoritmului binarySearch nu este la fel de
simplu ca exemplele anterioare. Acest algoritm utilizează recursivitatea.

Există mai multe metode de a analiza algoritmii recursivi.

- metoda Master

- metoda substituției

- metoda arborelui

- metoda Akra–Bazzi
O(n log n) - Timp liniar-logaritmic

O funcție cu o complexitate în timp liniar logaritmică este puțin mai


lentă decât una liniară, dar este mult mai bună decât una pătratică.

Exemple de algoritmi cu durata de execuție în timp O(n log n):

- algoritmii de sortare eficienți, cum ar fi MergeSort, HeapSort și QuickSort

- anumiți algoritmi divide et empere bazate pe optimizarea algoritmilor O(n2)


Mergesort

Care este cel mai bun mod de a sorta un tablou? Anterior, s-a
propus o soluție folosind sortarea prin interschimbare care are o
complexitate în timp de O(n²)

Putem face mai bine?

Putem folosi un algoritm numit Mergesort pentru a-l îmbunătăți

Cazul cel mai favorabil: O(n log n)

Cazul mediu: O(n log n)

Cazul cel mai rău(defavorabil): O(n log n)


Conceptul divide et impera

1. Împărțiți problema în mai multe probleme mici.

2. Cuceriți subproblemele rezolvându-le. Ideea este de a


descompune problema în subprobleme atomice, unde
acestea sunt apoi rezolvate.

3. Combinați soluțiile subproblemelor pentru a găsi


soluția problemei inițiale
O(2n) - Timp exponențial

Complexitatea în timp exponențială (baza 2) înseamnă că


operațiile efectuate de un algoritm se dublează de fiecare dată
ce intrarea crește.

Exemple de algoritmi cu durata de execuție în exponențial O(2n):

- toate submulțimile unei mulțimi (mulțimea putere)

- problema comis-voiajorului utilizînd programarea dinamică


Ar trebui de evitat funcțiile cu timpi de funcționare exponențiali (dacă este
posibil).

Timpul necesar procesării ieșirii se dublează cu fiecare dimensiune de intrare


suplimentară.

Timpul de rulare exponențial nu este cel mai rău, sunt algoritmi care se execută
și mai încet.
O(n!) - Timp factorial
Factorialul unui număr întreg pozitiv n este notat cu n! și este egal cu produsul
numerelor naturale mai mici sau egale cu n.

După cum se vede, este de dorit să se evite cît este de posibil algoritmii care au
acest timp de execuție deoarece numărul de operații crește extrem de rapid.
10!

10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 = 3 628 800

Exemple de algoritmi cu durata de execuție în factorial O(n!):

- permutări ale unui șir de caractere

- problema comis-voiajorului utilizînd căutarea cu forță brută (căutarea


exhaustivă)
Pentru un șir de caractere cu lungimea de 11 execuția a
durat aproximativ 76 de secunde

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