Sunteți pe pagina 1din 42

C2 : Notiuni despre complexitatea

algoritmilor. Algoritmi de cautare si sortare


Notiuni introductive
Structura de date = un mod de a organiza si stoca datele pentru a facilita manipularea
(eg. accesul, modificarea, stergerea) lor.

Ce tipuri de structuri de date cunoasteti?

OBS: Modul in care datele sunt organizate poate sa rezolve o buna parte din problema.

Exemplu:

Pb. 1 Se da un sir de numere intregi ce contine, in ordine, mediile studentilor dintr-o serie.
Care e a 3-a medie din serie, dar a n-a?

Pb. 2 Intr-o aplicatie se doreste stocarea temperaturilor medii zilnice. Valoarea celei mai
recente masuratori se stocheaza la inceputul structurii. Din cand in cand se afiseaza
temperatura medie din ultimele n zile.

Exista vreo structura de date pe care ati prefera-o pentru rezolvarea Pb 1 si, respectiv, Pb 2?
Daca da - care si de ce?
Notiuni introductive
=> Nu exista stuctura de date care sa fie buna pentru orice scop/ in orice situatie.

“There is no ultimate data structure...” – alegerea unei structuri se face in functie de cerinta

problemei.

Algoritm

= o procedura / o secventa de pasi care rezolva o problema data in timp finit; primeste un

set de date de intrare si returneaza o solutie – set de date de iesire

Structura de date + Algoritm -> Program

Ne intereseaza sa dezvoltam/folosim algoritmi care rezolva probleme eficient (durata scurta

si resurse putine) si structuri de date care manipuleaza date eficient.


Notiuni introductive
Mai sunt algoritmii de interes/importanti?

… in contextul computerelor actuale: arhitecturilor si tehnologiilor de fabricatie


avansate, usor de folosit, intuitive, cu interfete grafice utilizator / sistemelor orientate
obiect/ tehnologii Web integrate/ accesului rapid in retele – wireless sau cablu?

Da.

Chiar daca o serie de aplicatii nu necesita explicit algoritmi (ex: aplicatii simple Web
based), multe altele se bazeaza pe ei.

Exemple:

1. Considerari o aplicatie de tip serviciu Web-based care calculeaza rute intre 2 locatii.
Implementarea se bazeaza pe cel mai rapid hardware, o interfata grafica, retea si
probabil integreaza concepte POO. Cu toate astea, va avea nevoie de algoritmi
pentru anumite operatii: gasirea rutelor optime(shortest-path algorithm), redarea
hartilor, interpolarea adreselor.

2. Aplicatiile mobile de timp real folosesc algoritmi de optimizare, etc (Waze)

3. Google search – algoritmi de optimizare


Complexitatea algoritmilor
Obiectivul unui algoritm:
- rezolva o clasa cat mai larga de probleme cu cat mai putine resurse de timp si spatiu de
memorie - > complexitate temporala si spatiala

<=> timp/memorie finit(a) de/pentru executie

Analiza algoritmilor:
- o problema poate sa aiba mai multe solutii (sortarea elemetelor unui vector poate sa fie
facuta in multe feluri). Cum putem sa aflam care e cel mai eficient algoritm din punct de
vedere al duratei si memoriei consumate?

Cum comparam algoritmii?


- durata de executie – depinde de masina pe care se ruleaza (nu e o masura buna)
- nr. de instructiuni executate – nu e o masura buna pentru ca nr. de instructiuni depinde de
limbaj /stilul programatorului

Solutie: folosim ca masura o functie care depinde de dimensiunea datelor de intrare - f(n)

Analiza la executie (running time analysis) - ne spune cum creste durata cu cresterea
dimensiunii problemei (a datelor de intrare)
Complexitatea algoritmilor

Dimensiunea datelor de intrare(input)


- dimensiunea vectorului, gradul polinomului, nr. de elemente dintr-o matrice, nr. de
biti al unei reprezentari binare, nr. de noduri sau muchii ale unui graf.

Alti factori care afecteaza cantitatea de resurse folosita


- starea datelor de intrare (ex: in general, se consuma mai putine resurse ca sa sortezi
un vector gata/aproape sortat decat unul “bine amestecat” = > cazul cel mai favorabil,
nefavorabil; cazul mediu)

*Aceasta analiza (la executie) presupune o estimare matematica a numarului de pasi


parcursi de algoritm.

DAR: Numarul exact de pasi este dificil de calculat => se impun o serie de simplificari
=> in calculul de complexitate tinem cont numai de operatiile critice din algoritm
(acele operatii care, prin natura lor, consuma multe resurse sau sunt efectuate de un
numar semnificativ de ori)

Ex: Intr-un algoritm de sortare o operatie critica este compararea elementelor, intrucat
se produce de foarte multe ori.
Complexitatea algoritmilor

Definitii

 Complexitata unui algoritm poate fi masurata cu o functie f(n) care da durata de


executie sau/si memoria necesara pentru A in termeni de dimensiune - “n” a datelor
de intrare si forma lor particulara.

 Rata de crestere(rate of growth) = rata cu care creste durata de rulare/memoria


folosita in functie de dimensiunea datelor de intrare.

OBS: In cele mai multe cazuri spatiul de stocare este un multiplu de n – iar complexitatea
se refera la durata de executie.
Complexitatea algoritmilor

 Cazul cel mai nefavorabil - defineste inputul care conduce la cel mai lung timp de
rulare/ ce complexitate rezulta pentru cel mai neprietenos input = max ( f(n) )
 Cazul cel mai favorabil - defineste inputul care conduce la cel mai scurt timp de
rulare/ ce complexitate rezulta pentru cel mai prietenos input = min ( f(n) )
 Cazul mediu - e o predictie a duratei medii = la ce complexitate ne putem astepta in
cazul inputurilor aleatoare (dificil de calculat, ar trebui calculata media ponderata a
complexitatilor in functie de distributia statistica a inputurilor si probablilitatea lor de
de aparitie) – valoarea asteptata

limita inferioara <= durata medie <= limita superioara

Pentru un algoritm putem sa reprezentam cazul cel mai (ne)favorabil, mediu ca functii:
- f(n)= n^2+500, cel mai nefavorabil caz
- f(n) = 100n+500, cel mai favorabil caz
***Notatie: n^m = n la puterea m
Complexitatea algoritmilor
Analiza asimptotica a complexitatii
Notatii asimptotice - avand expresiile pentru cazul cel mai favorabil/ mediu/ nefavorabil – trebuiesc
depistate limitele inferioare/superioare pentru f(n)

1. Notatia O - “Big O”
• descrierea comportamentul unui algoritm in cazul cel mai nefavorabil fara a se face
referire la celelalte situatii. (Intrucat, in general, e de interes comportarea algoritmului
pentru date arbitrare de intrare este suficient sa specificam o margine superioara pentru
timpul de executie.)
• f(n) ∈ O(g(n)) implica faptul ca f(n) creste asimptotic cel mult la fel de repede ca g(n)
• O(g(n))={f(n)|exista c si no>0 astfel incat 0<=f(n)<=cg(n), orice n>=no}.
• in general, nu ne intereseaza valorile mici
ale lui n; rata de crestere pentru n<no poate
fi diferita.
Complexitatea algoritmilor
O(g(n))={f(n)|exista c si no >0 astfel incat 0<=f(n)<=cg(n), orice n>=no}

Exemple - Gasiti limita superioara pentru:


1. f(n)=3n+8
3n+8<=4n, n>=1 => f(n)=O(n), c=4, no=8

2. f(n) = n^2+1
n^2+1<=2n^2, n>=1 = > f(n)=O(n^2), c=2, no=1

3. f(n)= 410
410<=410*1, n>=1 = > f(n) = O(1), c=1, no=1?

Observatii:
1. O(n^2) include O(1), O(n), O(nlogn)
2. Valorile pentru c si no nu sunt unice!
Complexitatea algoritmilor

Notatia Ω – “Big Omega”


• se foloseste pentru a exprima eficienta algoritmului pornind de la timpul de executie
corespunzator celui mai favorabil caz
• faptul ca f(n) ∈ Ω(g(n)) inseamna ca f(n) creste asimptotic cel putin la fel de repede
ca g(n)
• Ω(g(n)) = {f(n)| exista c si no >0 astfel incat 0<=cg(n)<=f(n), orice n>=no}

Exemplu -Gasiti limita inferioara pentru :


f(n)=5n^2
0<=cn<=5n^2 cu c=1 si no=1
Complexitatea algoritmilor

Notatia Θ – “Big Theta”


• Aceasta notatie decide daca limita superioara si inferioara a unei functii sunt identice sau nu.
• Daca notatile O si Ω dau acelasi rezultat atunci notatia Θ are aceasi rata de crestere ca ele.
• faptul ca f(n) ∈ Θ(g(n)) inseamna ca f(n) si g(n) sunt asimptotic echivalente, adica au acelasi
ordin/aceasi rata de crestere.
• Θ(g(n)) = { f(n)|exista c1, c2 si no pozitive astfel incat 0<=c1g(n)<=f(n)<=c2g(n), orice n>no}

Exemplu - Gasiti Θ pentru:


f(n)=n^2/2-n/2
n^2/5 <= n^2/2-n/2 <= n^2, n>=1
f(n)=Θ(n^2), c1=1/5, c2=1, n0=1
Analiza asimptotica a algoritmilor- recomandari

1. Bucle – durata de executie a unui structuri repetitive este maxim egala cu durata
instructiunilor executate (incluzand testele) inmultit cu numarul de iteratii

for(i=0; i<n; i++) // se executa de n ori

m=m+2; //timp constant – c

Timpul total = cxn = O(n)

2. Bucle imbricate (nested loops) - se analizeaza din exterior spre interior; timpul total e
produsul dimensiunilor buclelor

for( i=0; i<n; i++) // se executa de n ori

for( j=0; j<n; j++) // se executa de n ori

k=k+1; //timp constant – c

Timpul total = nx(nxc) = O(n^2)


Analiza asimptotica a algoritmilor- recomandari

3. Instructiuni consecutive - timpul total se obtine prin insumare complexitatilor


x=x+1; //timp constant c0

for( i=0; i<n; i++) // se executa de n ori


m=m+2; //timp constant – c1

for( i=0; i<n; i++) // se executa de n ori


for( j=0; j<n; j++) // se executa de n ori
k=k+1; //timp constant – c2

Timpul total = c0+c1xn+c2xnxn = O(n^2)


Analiza asimptotica a algoritmilor- recomandari

4. Instructiuni if-then-else - testul + timpul cel mai defavorabil de pe ramura then sau else

if (n==0){ // timp constant c0

return false; // timp constant – c3

}else{

for( i=0; i<n; i++) {// se executa de n ori

m=m+2; //timp constant – c1

x--; //timp constant –c2

Timpul total = c0+ (c1+c2)xn = O(n)


Analiza asimptotica a algoritmilor - recomandari

5. Complexitate logaritmica: un algoritm e O(logn) daca dureaza un timp constant ca


sa micsoreze problema cu o anumita rata (de exemplu sa o injumatateasca):
for (i=1; i<=n; )
i=i*2;
Valoarea lui i se dubleaza la fiecare pas: 2, 4, 8..2^k. La pasul k : 2^k>=n si executia
buclei se termina.
log(2^k)=log(n)
k*log(2)=log(n)
k= log (n), unde logaritmul e in baza 2.
Timpul total = O(logn)

6. Daca o problema se poate sparge in n module de complexitate log k vom obtine o


𝑛
complexitate O(nlogn): 𝑘=1 log 𝑘 ≤ nlogn
Analiza asimptotica a algoritmilor - Concluzii

Rate de crestere
o 2^n – rata exponentiala – turnurile din Hanoi
o n! – rata factoriala - permutari
o n^3 – rata cubica –inmultirea de matrice
o n^2 – rata patratica – calea cea mai scurta intre
2 noduri ale unui graf
o nlogn – rata liniar logaritmica – sortare cu
metoda merge sort
o n – rata liniara – gasirea unui element intr-un
vector nesortat
o log n – rata logaritmica - gasirea unui element
intr-un vector sortat
o 1 – rata constanta- adaugarea unui element la
inceputul unei liste
Complexitatea algoritmilor

Teorema master pentru algoritmi recursivi de tip divide et impera


Considerati ca solutia unei probleme e data de algoritmul:

functie p( input x de dimensiune n )


daca n < k (o constanta)
rezolva x fara recursivitate
altfel
Creeaza a subprobleme din x, fiecare de dimensiune n/b
Apeleaza p recursiv pe fiecare subproblema
Combina rezultatele subproblemelor

Ce problema s-ar putea rezolva asa?

Timpul total pentru acest algoritm este: T(n)=aT(n/b)+f(n)


- f(n)- timpul pentru crearea subproblemelor si combinarea rezultatelor.
- a – factor de diviziune -“branching factor” ; b-factor reducere dim. problema

Fie E exponentul critic: E = logb(a)


Complexitatea algoritmilor
Pentru recurenta precedenta - arborele de recurenta va contine:
- pe al doilea nivel a noduri - fiecare nod reprezentand o problema de dimensiune n/b
- pe urmatorul nivel vor fi a^2 noduri, si tot asa….
- pe ultimul nivel pe care se vor gasi a^inaltimea_arborelui noduri (frunze).
Nr_frunze = a^logb(n) =n^logb(a) =n^E

Considerand doar varful si baza arborelui de recurenta:


- la varf, costul semnificativ este costul nerecursiv f(n)
- la baza, fiecare frunza rezolva o problema elementara => costul ultimului nivel din
arbore e influentat nu atat de ce face fiecare frunza, cat de numarul de frunze, n^E, care
determina de cate ori se face acel efort “elementar”
* intrebarea este: care dintre costul de la varf si cel de la baza este dominant; daca este
unul dominant, atunci el va dicta complexitatea algoritmului
Cele 3 cazuri din teorema master se bazeaza pe relatia dintre aceste 2 costuri, in felul
urmator:
Complexitatea algoritmilor
Cazul 1 (n^E dominant)
- valabil daca exista ε>0 a.i. f(n) apartine O(n^(E-ε))
- T(n)= Θ(n^E)

Cazul 2 (costuri similare pentru n^E si f(n))


- valabil daca f(n) apartine Θ(n^E)
- in acest caz, efortul e distribuit uniform intre niveluri din arbore si va fi de ordinul
f(n)*numarul_de_niveluri
- T(n)= Θ(f(n) * log(n)) = Θ(n^E * log(n))

Cazul 3 (f(n) dominant)


- valabil daca exista ε>0 a.i. f(n) apartine Ω(n^(E+ε)) si, in plus, exista constantele
0<c<1, n0, a.i. pt orice n≥n0, a f(n/b) ≤ c f(n)
- T(n)= Θ(f(n))
- daca nu s-ar indeplini conditia suplimentara a f(n/b) ≤c f(n), costul nerecursiv ar
creste foarte mult in josul arborelui, iar f(n) nu ar mai fi dominant
Cautari si sortari

Algoritmi de cautare (searching algorithms)


- implica gasirea unui element cu o anumita proprietate intr-o colectie de elemente.
- elementele pot sa fie inregistrari intr-o baza de date, elemente intr-un vector, text in fisiere,
noduri intr-un arbore, muchii sau noduri intr-un graf sau elemente in alte spatii de cautare.
- sunt algoritmi de baza -> foarte utilizati => trebuie sa fie eficienti.

Obs: Exista modalitati de organizare a datelor care imbunatatesc procesul de cautare => daca tinem
datele intr-o anumita ordine, gasirea elementului cautat se face usor. Sortarea colectiei in care se
face cautarea e o astfel de tehnica.

Tipuri de cautari
 Cautare liniara intr-o multime neordonata ( Unordered Linear Search)
 Cautare liniara intr-o multime ordonata ( Sorted/Ordered Linear Search)
 Cautare binara ( Binary search)
 Symbol Tables si Hashing - daca e timp*
 Cautare de siruri de caractere (Tries, Ternary Search si Suffix Trees) – daca e timp*
Cautare liniara/secventiala intr-o multime neordonata

Se presupune dat un vector cu elemente intr-o ordine necunoscuta (vectorul nu e ordonat) =>
trebuie parcurs tot vectorul ca sa vedem daca elementul se gaseste acolo sau nu.

int search(int v[], int n, int data)


{ int i;
for (i=0; i<n; i++)
if (v[i] == data) return i;
return -1;
}

- cazul cel mai nefavorabil - algoritmul examineaza n numere pentru cautarea fara succes
- cazul mediu - sunt evaluate aproximativ n/2 numere pentru cautarea cu succes

Complexitatea din punct de vedere a duratei este O(n)


Complexitatea din punct de vedere a memoriei este O(1) – nu mai trebuie alte resurse fata de
datele initiale
Cautare liniara/secventiala intr-o multime ordonata

Se presupune dat un vector cu elemente intr-o ordine cunocuta => nu trebuie parcurs tot
vectorul ca sa vedem daca elementul se gaseste acolo sau nu, ne oprim daca am gasit un
element >= fata de cel cautat

int search_ord(int v[], int n, int data)


{ int i;
for (i=0; i<n; i++)
if (v[i] == data) return i;
else if (v[i]>data) return -1;
return -1;
}

- cazul cel mai nefavorabil - algoritmul examineaza n numere pentru cautarea fara succes
- cazul mediu - se fac mai putine evaluari comparativ cu cazul precedent , dar

Complexitatea din punct de vedere al duratei este tot O(n)


Complexitatea din punct de vedere al memoriei este O(1) – nu mai trebuie alte resurse fata
de datele initiale
Cautare binara
Se considera vectorul sortat. Cautarea se face impartind la fiecare pas domeniul de cautare in
doua – si se selecteaza cel care contine elementul de interes

int search_bin(int v[], int n, int data)


{int r=n-1, l=0;
while (r >= l) {
int m = (l+r)/2;
if (v[m]==data) return m;
if (v[m]>data) r=m-1;
else l=m+1;
}
return -1; Cum ati implementa
} aceasta functie recursiv?

Cel mai nefavorabil caz - nu se examineaza mai mult de logn+1 numere => O(logN)
Complexitatea spatiala este O(1)
Tema: Considerati vectorul: 1 3 7 9 10 – se cauta 1- care sunt pasii facuti?
Cautare binara - forma recursiva

Se considera vectorul sortat.

int search_bin_rec(int v[], int l, int r, int data)


{
int m = (l+r)/2;

if (v[m]==data) return m;
else if (v[m]>data) return search_bin_rec(v, l, m-1, data);
else return search_bin_rec(v, m, r , data);

return -1;
}

Recurenta pentru cautarea binara e: T(n) = T(n/2) +Θ(1) pentru ca mereu consideram doar
jumatate din input => O(logN) *caz 2 Teorema master
Complexitatea spatiala – la fiecare apel recursiv o sa se puna un stack frame pe stiva =>
cazul cel mai favorabil (elementul cautat e gasit la primul apel)/nefavorabil O(1)/ O(logN)
Algoritmi de sortare

- metode elementare – potrivite unui numar mic de elemente (<1000): bubble, insertion,
selection sort - > O(n^2)
- metode avansate – potrivite pentru cazul cu multe inregistrari: quick sort, merge sort,
heap sort
Criterii de interes: timp de rulare si cantitatea de memorie suplimentara.

Sortare stabila vs instabila


Sortarea stabila - mentine ordinea relativa a inregistrarilor cu chei egale
*daca stabilitatea este importanta se poate crea o– cheie extinsa

Sortare indirecta – daca elementele de sortat sunt mari - e bine sa nu le


schimbam pozitia => folosim un vector de indici

Sortare in-place / out- of-place – spatiul suplimentar (pe langa cel al containerului ) e
mic/mare
Metode elementare de sortare

1. Metoda bulelor (bubble sort / sortare prin compararea vecinilor)


Ideea: Se parcurge vectorul de sortat si se compara valorile pentru fiecare 2 elemente
vecine.
Daca nu se gasesc in ordinea dorita se face interschimbare intre cele 2 valori.
Se reia procesul pana ce vectorul e ordonat.

Fie vectorul cu elementele: 3 2 1.

Prima trecere prin vector: 3 2 1 // efect 2 3 1


2 3 1 // efect 2 1 3
A doua trecere prin vector: 2 1 3 //efect 1 2 3
1 2 3 //efect 1 2 3
A treia trecere prin vector: 1 2 3 //efect 1 2 3
1 2 3 //efect 1 2 3
Nu am nicio modificare de facut=>e sortat.
*La a treia trecere vectorul era deja sortat (ar fi ideal sa nu fac mai mult de o astfel de
parcurgere inutila).
**Nu este niciodata suficienta o singura parcurgere a vectorului (daca dimensiunea>2)
pentru a-l sorta
Metode elementare de sortare
void bubble_sort (int v[], int n){
int i, sortat = 1; //consider vectorul nesortat
while (sortat==1){ // repet cat timp vectorul nu e sortat
sortat = 0; //presupun ca poate a fost sortat la pasul anterior
//verific daca mai gasesc elemente neordonate
for (i = 0; i < n-1; i++) //am grija sa nu accesez elemente care nu sunt in v
if (v[i] > v[i+1]){ //accesez elementul i+1 care are valoare max: n-2+1
int temp =v[i]; //daca elementele sunt neordonate
v[i] =v[i+1]; //le interschimb valorile
v[i+1]=temp;
sortat=1; //marchez vectorul ca neordonat
}
}
}
Cazul cel mai favorabil(vectorul e sortat)/mediu/nefavorabil: n / n^2 / n^2 = > O(n^2)
Complexitate spatiala (nu folosesc alte resurse in plus): O(1).
Metode elementare de sortare
2. Sortarea prin insertie
Ideea: Functioneaza similar cu aranjarea cartile de joc in mana: de
fiecare data cand trag o carte o plasez pe pozitia corecta

• Incep cu al doilea element din vector si parcurg toate elementele


• la pasul i – caut pozitia corecta a elementului v[i] astfel incat secventa v*0+,v*1+…v*i+
sa fie ordonata:
- stochez valoarea lui v[i] intr-un aux
- mult la dreapta toate elementele v[j] > aux ; j<i
- il inserez pe aux pe pozitia libera

aux=v[6]
Metode elementare de sortare

void insertion_sort(int v[], int n){


int i, poz;
for (i=1; i<n; i++) // pornesc de la al doilea element din vector si presupun ca
{ //nu se gaseste pe pozitia corecta; repet si pentru celelalte elemente
int aux=v[i];
//caut pozitia corecta a elementului cu valoarea aux
for (poz=i; poz!=0 && v[poz-1]>aux; poz--) //daca am elementele >aux
v[poz]=v[poz-1]; //le mut la dreapta cu o pozitie
v[poz]=aux; // plasez aux pe pozitia libera
}

Cazul cel mai favorabil/mediu/nefavorabil: n/n^2/n^2


=> O(n^2)
Complexitate spatiala : O(1).
Metode elementare de sortare

3. Sortarea prin selectie


Ideea: La fiecare pas i - se ia elementul de valoare minima din sub- vectorul (i, n)
si se pune pe pozitia i

Pas 0: 9 7 6 15 16 5 10 11 -> 5 7 6 15 16 9 10 11
Pas 1: 5 | 7 6 15 16 9 10 11 -> 5 6 7 15 16 9 10 11
Pas 2: 5 6 | 7 15 16 9 10 11 -> 5 6 7 15 16 9 10 11
Pas 3: 5 6 7 | 15 16 9 10 11 -> 5 6 7 9 16 15 10 11
Pas 4: 5 6 7 9 | 16 15 10 11 -> 5 6 7 9 10 15 16 11

Cati pasi mai trebuiesc executati? 2


Tema: Realizati-i!
Metode elementare de sortare

void selection_sort(int v[], int n){


for (int i = 0; i < n-1; i++)
{// la fiecare pas consider ca vectorul e nesortat; caut minimul e intre indicii i+1 si n
// gasesc minimul in sub-vectorul nesortat (mai exact, pozitia minimului)
int min_idx = i;
for (int j = i+1; j < n; j++)
if (v[j] < v[min_idx])
min_idx = j;
// interschimb minimul gasit in subsirul neordonat cu primul element al subsirului
int temp =v[min_idx];
v[min_idx] =v[i];
v[i]=temp;
}
}

Cazul cel mai favorabil/mediu/nefavorabil: n^2/n^2/n^2 = >O(n^2)


Complexitate spatiala : O(1).
Sortarea indirecta

 In cazul inregistrarilor de dimensiune mare, nu vream sa mutam elementele vectorului

original

 Folosim un vector de indici/pozitii p: p*0+,…p*n-1]

 Algoritmii precedenti trebuie modificati astfel:

- se vor referi la v[p[k]] in loc de v[k] in cadrul comparatiilor

- si la p[k] in loc de v[k] pentru interschimbare.

 Dupa ce s-a realizat sortarea indirecta – daca se doreste- se pot rearanja inregistrarile.
Metode avansate de sortare
1. Quicksort
 cel mai utilizat algoritm de sortare
 1960- C.A.R. Hoare
 consuma mai putine resurse decat alte metode
 in medie foloseste nlogn operatii pentru a sorta n elemente, dar n^2 pentru
cazul cel mai nefavorabil.
 se foloseste de paradigma divide et impera (problema se imparte in
subprobleme care sunt apoi rezolvate – iar solutiile partiale sunt recombinate
intr-o solutie finala)

Divide: daca v (vectorul de sortat) are cel putin 2 elemente, selecteaza un element
x din v – pivot (de exemplu ultimul element) si imparte elementele din v in 3 parti:
elemente < x (L) elemente = x (E) elemente > x (G)

Impera: sorteaza L si G <=> aplica divide pe L si G

Combine: reface v punand in ordine L, E si G


Metode avansate de sortare
• Arborele quicksort pentru algoritmul aplicat pe o secventa cu 8 elemente:

Pozitia finala a pivotului 50 e 4


(*v incepe de la 0)
Pozitia finala a pivotului 31 e 3
Pozitia finala a pivotului 96 e 7

Pozitia finala a pivotului 17 e 0


Pozitia finala a pivotului 45 e 2
Pozitia finala a pivotului 63 e 5

Pozitia finala a pivotului 24 e 1


Pozitia finala a pivotului 85 e 6
Metode avansate de sortare

quickSort(array, low, high)

daca low <high // pana nu se mai poate imparti (am cel putin 2 elemente)

q <- partition(arr, low, high); // impart in L, G, E


//q este pivotul si a ajuns pe pozitia sa finala
//in dreapta are numai elemente mai mari
// in stanga numai elemente mai mici
quickSort(arr, low, q-1); //sortez tot ce e la stanga lui
quickSort(arr, q+1,high); //sortez tot ce e la dreapta lui

quickSort(a, 0, N-1) sorteaza toata secventa


Metode avansate de sortare
partition(arr, low, high)
pivot <- arr[high] // trebuie sa pozitionez acest element (cel de pe ultima pozitie) pe
//pozitia sa finala in vectorul sortat; il stochez in pivot
//ca sa ajunga pe pozitia dorita – trebuie mutate toate elementele
//mai mari ca el in partea de final a vectorului
i<- low-1

pentru j=low, high-1 //pentru toate elementele subvectorului


daca arr[j]<=pivot //verific daca sunt mai mici sau mai mari ca pivotul
i++; // primele i elemente (fata de low sunt < pivotul)
interschimba (arr[i],arr[j]) // daca mai mici – le mut in prima parte a vectorului,
//inainte de pivot

intershimba (arr[i+1], arr[high]); //pun pivotul pe pozitia sa finala in vector


return (i+1) //ii returnez pozitia pe care a ajuns pivotul

Complexitatea temporala – cel mai nefavorabil/mediu/favorabil caz: O(n^2)/O(nlog n)/O(nlog n)


* cel mai nefavorabil caz - arborele obtinut e neechilibrat(vectorul e deja sortat)
Complexitatea spatiala- in cel mai bun caz O(logn) – cel mai rau caz O(n) – daca arborele de
recursivitatea e neechilibrat

*Se poate alege orice element din subvector ca pivot


Metode avansate de sortare

2. Mergesort
 Foloseste paradigma divide et impera
 Poate fi implementat astfel incat sa acceseze elementele intr-o maniera secventiala
Avantaje: e potrivit secventelor reprezentate prin liste inlantuite

Divide: daca v are 0 sau 1 element, intoarce solutia.


altfel (v are cel putin 2 elemente) imparte v in 2 parti disjuncte egale
- v1 – cu primele n/2 elemente
- v2 – cu ultimele n/2 elemente
Conquer : sorteaza v1 si v2
Combine: reface v prin imbinarea v1 si v2 intr-o secventa sortata.
Metode avansate de sortare
• Arborele megesort pentru algoritmul aplicat pe o secventa cu 8 elemente:
Metode avansate de sortare

mergesort(arr, left, right)


daca (left<right) // daca mai pot sa impart in subprobleme -> tot impart
middle <- (left+right)/2
mergesort(arr, left, middle);
mergesort(arr, middle+1,right);
merge (arr, left, middle, right); // recompun vectorul sortat
Metode avansate de sortare
merge ( arr , left, middle, high) //…
n1 <- middle – left+1 // lungimea subvectorului stang // copiez si restul elementelor
n2 <- right- middle // lungimea subvectorului drept
left_arr[n1] , right_arr[n2] // subvectorii stang si drept cat timp (i<n1)
pentru i=0,n1 left_arr[i] <- arr[l+i] // copiez in ei informatiile arr[k] = left_arr[i];
i<-i+1
pentru i=0,n2 right_arr[i] <- arr[m+1+i] // necesare k<-k+1

//unesc subvectorii temporari in arr*left…right+ cat timp (j<n2)


// – facand ordonarea elementelor arr[k] = right_arr[j];
i<-0 // index pentru left_arr j<-j+1
k<-k+1
j<-0 // index pentru right_arr
k<-left // index pentru arr *left…right+
cat timp (i<n1 && j<n2) // interclasez subvectorii
daca (left_arr[i]<right_arr[j]
arr[k]=left_arr[i];
i<-i+1 Complexitatea temporala – cel mai
altfel nefavorabil/mediu/favorabil caz:
arr[k]=right_arr[j]; O(nlogn) / O(nlog n)/ O(nlog n)
Dar:
j<-j+1
Complexitatea spatiala: O(n)=
k<-k+1
= O(log n)(pt stiva)+O(n)(copia vect)
//…
Teme:

I. Implementati mergesort si quicksort

II. Implementati 1-6 sub forma de functii si calculati complexitatea for:

1. Creati unui vector v - alocat dinamic, de dimensiune n. Vectorul contine


elemente de tip Date. Date e un tip de date definit de voi.

2. Adaugati un element pe pozitia x intr-un vector, cu cresterea capacitatii


vectorului daca e nevoie. Noul element contine informatia val.

3. Modificati valoarea elementului de pe pozitia x in val.

4. Stergeti elementul din vector cu continutul val.

5. Parcurgeti vectorul.

6. Eliberati spatiului ocupat de v.

III. Care dintre metodele de sortare precedente sunt stabile? Dar in-place?

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