Sunteți pe pagina 1din 46

C2 : Complexitatea algoritmilor - notiuni generale.

Algoritmi de cautare si sortare


Notiuni introductive
Structura de date = un mod de a organiza si stoca o colectie de datele pentru a facilita
manipularea lor (eg. accesul/modificarea, adaugarea, stergerea, gasirea unui element,
sortarea colectiei).

Ce tipuri de structuri de date cunoasteti?

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

Exemplu:

Pb. 1 Se da un sir de numere ce contine, in ordine, mediile studentilor dintr-o serie (n


studenti). Care e a 3-a medie din serie, dar a i-a? Astfel de interogari sunt prioritare,
colectia de date nu se modifica.

Pb. 2 Intr-o aplicatie se doreste stocarea temperaturilor medii zilnice. Valoarea


temperaturii din ziua curenta, dupa ce e adaugata, este actualizata/afisata/accesata des.
Rar e de interes temperatura dintr-o anumita zi din trecut.

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 intr-un mod repetabil si cu

resurse (timp si memorie) finite; 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 eficient probleme (intr-un timp

scurt si cu resurse putine) si structuri de date care manipuleaza optim datele.


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, tehnologiilor 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,
mobile), multe altele se bazeaza pe ei.

Exemple:

1. Considerati o aplicatie de tip serviciu Web care calculeaza rute intre 2 locatii (Google maps).
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 foloseste algoritmi de optimizare pentru cautari

4. Clasificarea imaginilor, Sisteme de recomandare (Netflix) – Algoritmi de tip deep/machine


learning(invatare profunda/ automata)
Complexitatea algoritmilor
Obiectivul unui algoritm:
- rezolva o clasa (cat mai larga) de probleme cu cat mai putine resurse de timp si spatiu - >
complexitate temporala si spatiala

<=> timp/memorie de/pentru executie

Algoritm pentru realizarea cumparaturilor eficient temporal


- se foloseste o masina mare pentru a merge la magazin
- se cumpara toate produsele dorite si sunt duse la destinatie
Algoritmul salveaza timp, dar necesita o masina mare(complexitate temporala scazuta, dar
complexitate spatiala mare).

Algoritm pentru realizarea cumparaturilor eficient spatial


- se merge la magazin cu un rucsac
- se cumpara un produs si se duce la destinatie, unde rucsacul e golit
- se reia procesul pana se cumpara toate produsele dorite
Algoritmul salveaza spatiu (nu e nevoie de masina), dar dureaza mult (complexitate
temporala mare, dar complexitate spatiala redusa).

In functie de resursele avute la dispozitie si alte restrictii trebuie ales algoritmul potrivit.
Complexitatea algoritmilor

Analiza algoritmilor:
- o problema poate sa aiba mai multe solutii (eg. sortarea elementelor 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 – numarul de instructiuni depinde de limbaj /stilul
programatorului (nu e o masura buna)

Solutie: folosim ca masura o functie care depinde de dimensiunea n a 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 unui vector, gradul unui polinom, dimensiunea unei matrice, numarul
de biti al unei reprezentari binare, numarul 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, cazul cel mai nefavorabil, cazul mediu

*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 complexitatii 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 (A) 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 e un multiplu de n – iar complexitatea
se refera la durata de executie.
Complexitatea algoritmilor

 Cazul cel mai nefavorabil - complexitatea pentru cel mai neprietenos input = max ( f(n) )
 Cazul cel mai favorabil - complexitatea 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

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

limita inferioara <= durata medie <= limita superioara


Complexitatea algoritmilor
Analiza asimptotica a complexitatii
Notatii asimptotice - avand expresiile – f(n)- pentru cazul cel mai favorabil/ mediu/ nefavorabil –
trebuiesc depistate limitele inferioare/superioare pentru f(n)

1. Notatia O - “Big O”
• Descrie comportamentul unui algoritm in cazul cel mai nefavorabil fara a se face
referire la celelalte situatii.
• Intrucat, in general, e de interes comportamentul 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.

*O – ordin de complexitate
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 – O - pentru:


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

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

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

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 limitele superioare si inferioare 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>=5/3
f(n)=Θ(n^2), c1=1/5, c2=1, n0=2
Analiza asimptotica a algoritmilor- recomandari

1. Bucle – durata de executie a unui structuri repetitive este maxim egala cu durata
instructiunilor executate (incluzand testele) inmultita 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 x durata instructiunilor executate

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 insumarea 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 nefavorabil 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 de clasa 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: 1, 2, 4, 8..2^k.
La pasul k, i este : 2^k>=n => executia buclei se termina.
log(2^k)=log(n)
k*log(2)=log(n)
k= log (n), unde logaritmul e in baza 2.
Ordin de complexitate: O(logn)

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


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

Rate de crestere
o n! – rata factoriala - permutari
o 2^n – rata exponentiala – turnurile din Hanoi
o n^3 – rata cubica –inmultirea de matrice
o n^2 – rata patratica – calea cea mai scurta intre
2 noduri ale unui graf; adunare matrice
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- aflarea valorii unui element
i dintr-un vector; 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 f( input x de dimensiune n )


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

Timpul total pentru acest algoritm e dat de relatia: 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 parte de jos a 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 cu elemente de acel tip.
- 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)
 Tabele de dispersie - C7
 Cautare specializata pentru siruri de caractere (tries, arbori de sufixe, etc) – C7
Cautare liniara/secventiala intr-o multime neordonata

Se presupune dat un vector neordonat => 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 cautare (fara succes)
- cazul mediu - sunt evaluate aproximativ n/2 numere pentru cautarea cu succes

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


Complexitatea din punct de vedere al 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 ordonat => nu trebuie parcurs tot vectorul ca sa vedem daca
elementul se gaseste acolo sau nu, ne oprim daca am gasit un element mai mare sau egal
cu 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;
Pot mereu sa tin datele in
return -1;
forma ordonata?
}

- 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 domeniul care poate 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; Cum ati implementa
} aceasta functie recursiv?
return -1;
}
Cel mai nefavorabil caz - nu se examineaza mai mult de logn+1 numere => O(logN)
Complexitatea spatiala este O(1)

Ex. v: 21 25 31 46 59 100 m = (0+5)/2=2; 25<v[2](=31); r=2-1; l ramane 0


i: 0 1 2 3 4 5 m = (0+1)/2=0; 25>v[0](=21); l=0+1; r ramane 1
//caut elementul 25, care sunt pasii? m = (1+1)/2=1; 25=v[1] (=25)=>gasit
Cautare binara - forma recursiva

Se considera vectorul sortat de dimensiune n.

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+1, r , data);

return -1;
}

Recurenta pentru cautarea binara e:


T(n) = aT(n/b) +f(n) = 1*T(n/2) +Θ(1) pentru ca mereu consideram doar jumatate din input
=> Θ(n^logb(a) * log(n))=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) O(1) /nefavorabil 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 -> ~ O(nlogn)

- alte metode – care se bazeaza pe o reprezentare bine aleasa a datelor - > < O(nlogn)

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 e importanta se poate creea 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 parcurgere - 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 se afla pe poz. 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 cartilor 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, aux;
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
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){


int min_idx, i, j;
for (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)
min_idx = i;
for (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 vrem sa mutam elementele vectorului

original mai mult decat e necesar (eventual vrem sa facem pozitionarea cand stim

locatia finala a elementului in vectorul sortat)

 Folosim un vector de indici/pozitii p: p[0],…p[n-1] ce contine pozitia fiecarui element in

vectorul original.

 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 (exista imbunatatiri si pentru acesta situatie => nlogn).
 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 2
Pozitia finala a pivotului 96 e 7

Pozitia finala a pivotului 17 e 0


Pozitia finala a pivotului 45 e 3
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
• Sortati vectorul: 80 21 59 45 15 30 90 50

1. Se alege 50 pivot.
Pozitia finala a pivotului 50 e 4
(*v începe de la 0, si sunt 4 elemente in
multimea L).
2. Se alege 30 pivot.
Pozitia finala a pivotului 30 e 2.
3. Se alege 90 pivot.
Pozitia finala a pivotului 90 e 7.
4. Se alege 15 pivot.
Pozitia finala a pivotului 15 e 0.
5. Pozitia finala a lui 45 e 3.
5. Se alege 59 pivot.
Pozitia finala a pivotului 59 e 5.
6. Pozitia finala pentru 21 este 1.
7. Pozitia finala pentru 80 este 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(array, low, high); // impart in L, G, E


//aleg pivotul si il plasez pe pozitia sa finala - q
//in dreapta are numai elemente mai mari
// in stanga numai elemente mai mici
quickSort(array, low, q-1); //sortez tot ce e la stanga lui
quickSort(array, 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 //in i retin indicele elementelor din L

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


daca arr[j]<=pivot //verific daca sunt mai mici decat 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], pivot); //pun pivotul pe pozitia sa finala in vector


return (i+1) //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 (e de fapt o lista)
Metode avansate de sortare
Observatii:

• se poate alege orice element din subvector ca pivot

• o varianta care ajuta, e sa consideram elementele de la capetele subvectorului si

elementul din mijloc, sa le sortam, si sa alegem valoarea mediana ca pivot

4 1 10 7 100 11 3 2

4 1 10 7 100 11 3 2

2 1 10 4 100 11 3 7

• Alegem valoarea mediana ca pivot.

• Aceasta este o estimare buna pentru valoarea elementului din mijloc si conduce la

o complexitate apropiata de O(nlogn) indiferent de starea initiala a vectorului


Metode avansate de sortare

2. Mergesort
 Foloseste paradigma divide et impera
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 mergesort pentru algoritmul aplicat pe o secventa cu 8 elemente:
Metode avansate de sortare
• Sortati vectorul 80 21 59 45 30 90 50
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. Care dintre metodele de sortare precedente sunt stabile? Dar in-place?

III. Implementati recursiv bubble sort, insertion sort, selection sort. Care e
complexitatea spatiala/temporala pentru aceste implementari?

IV. Implementati 1-6 sub forma de functii (cerinte partial implementate in Tema
recapitulativa) si calculati complexitatea lor:
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 (cu scaderea capacitatii


vectorului)

5. Parcurgeti vectorul si afisati elementele.

6. Eliberati spatiului ocupat de v.

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