Sunteți pe pagina 1din 8

Curs 9.

Sortări

Nota teoretica:

A. Sortarea presupune existenta unei ordini totală - relaţie binară:


• reflexivă
• antisimetrică
• tranzitivă
• orice două elemente sunt comparabile.
B. Dacă există valori identice, le putem identifica poziţiile iniţiale/finale?
Dacă da, atunci ele îşi păstrează ordinea relativă?
(Stabilitate v. fig.1, b)

a) instabila 4734334
3334447
b) stabila 4734334
3334447
Fig1. Stabilitate – fiecare element isi mentine pozitia (culoarea) respectiv inregistrarile cu
chei egale isi pastreaza pozitiile relative in urma sortarii

Testarea sortarii

bool este_sortat (float v[], int n)


{
// se initializeaza variabila sortat cu adevarat, considerandu-se apriorii vectorul sortat
bool sortat = true;
// se compara doua cate doua elementele vecine pentru orice i din intervalul [0, n-1]
for (i=0 ; i<n-1 ; i++)
{// daca cel putin doua elemente vecine nu sunt in ordinea ceruta variabila fanion
sortat primeste valoarea fals
if (v[i]>v[i+1])
sortat = false ;
}
return sortat ; }
C. Clasificare sortari:
după necesităţile de memorie:
i). Pe loc (memorie suplimentară constantă)
ii). Cu consum major de memorie suplimentară (depinde de n).

După metoda utilizată:


i). Prin comparaţie
ii). Alte metode.

Dupa cuantumul de memorare a inregistrarilor


i)Sortare interna este aceea sortare care pastreaza, pe tot parcursul executarii sale toate
inregistrarile in memoria interna a calculatorului.
ii)Sortare externa este aceea sortare care, din cauza numarului mare de inregistrari, nu
pastreaza, pe tot parcursul executarii sale toate inregistrarile in memoria interna a
calculatorului si foloseste pentru memorare memoriile externe.

Cateva extreme:
1) Bogosort (goro, bozo, etc)
2) Quick Sort

Este sortare prin comparaţie, varianta de bază este stabilă şi necesită consum suplimentar
de memorie.

procedure quick_sort(array)
list left, right
if (length(array)<2) return array
pivot = last(array)
remove(last(array))
foreach x in array
if (x<=pivot) append(left, x)
else append(right, x)
return concatenate(quick_sort(left), pivot, quick_sort(right))

Procedură recursivă!

Sortarea Rapida, detaliere


Sortarea rapida foloseste algoritmul divide et impera si mai este cunoscuta sub numele de
sortarea prin interschimbare cu partitii. Acest algoritm presupune parcurgerea a trei etape:

1. Etapa Divide: vectorul V, format clin elementele cu indecşii între s şi d, notat în


continuare prin V[s..d], este partiţionat în doi subvectori V[s..m] şi V[m+1..d], fiecare
parte având cel puţin un element, astfel încât fiecare element din prima parte este mai mic
sau egal decât oricare element din cea de-a doua parte.

2. Impera: cele două părţi sunt sortate apelând recursiv până când se ajunge la porţiuni
formate dintr-un singur element.
3. Combină: având în vedere că fiecare subvector este sortat şi cel mai mare element din
subvectorul din stânga este mai mic sau egal decât cel mai mic element din subvectorul
din dreapta atunci întregul vector este de fapt sortat.

Implementarea algoritmului:

void sortareRapida(int v[], int st, int dr)


{
int m = partitioneaza(v, st, dr);
if (st<m)
{
sortareRapida(v, st, m);
}
if ((m+ l)<dr)
{
sortareRapida(v, m + 1, dr);
}
}

Procedura de partiţionare. Există mai multe moduri în care putem împărţi vectorul, dar
următorul algoritm, inventat, de R. Sedgevvick, pare a fi cel mai bun: se porneşte de la
ambele capete, folosind doi indicatori, i pentru partea stângă, j pentru partea din dreapta,
căutându-se elementele care ar trebui să fie în cealaltă parte; în momentul în care se
găsesc elementele se verifică dacă i < j; dacă da se interschimbă cele două elemente şi se
continuă algoritmul, altfel algoritmul întoarce valoarea lui j; element de referinţă se
consideră ca fiind primul element din vector.

Descrierea algoritmului:

Următorul algoritm partiţionează vectorul v[st..dr] în doi subvectori v[st..m] şi v[m+l..dr]


astfel încât toate elementele din primul subvector să fie mai mici sau egale decât oricare
element din cel de-al doilea subvector, la final întorcând valoarea lui m.

P1. Se iniţializează elementul de referinţă x cu v[st], i cu st şi j cu dr.


P2. Dacă v[i] >= x se trece la pasul P5. Altfel se continuă cu pasul următor.
P3. Se incrementează i.
P4. Se continuă cu pasul P2.
P5. Dacă v[j] <= x se trece la pasul P8. Altfel se continuă cu pasul următor.
P6. Se decrementează valoarea lui j.
P7. Se continuă cu pasul P5.
P8. Dacă i >= j algoritmul se încheie întorcând valoarea lui j. Altfel se continuă cu pasul
P9.
P9. Se interschimbă v[i] cu v[j].
P10. Se incrementează i.
P11. Se decrementează j.
PI2. Se continuă cu pasul P2,

Descrierea algoritmului in C:

int partitioneaza(int v[], int st, int dr)


{
int i = st; int j = dr; int x = v[st];
do
{
while (x > v[i])
{
i++;
}
while (x < v[j])
{
j--;
}
if(i<j)
{
interschimba(v[i], v[j]);
i++;
j--;
}
else
{
return j;
}
} while (true);
}

Dacă v[i] este mai mic decât elementul de referinţă atunci cu certitudine el face parte din
primul subvector, altfel, este un element care poate fi plasat în cel de-al doilea subvector.
Primul ciclu caută elemente mai mari sau egale cu elementul de referinţă.

Dacă v[j] este mai mare decât elementul de referinţă atunci cu certitudine el face parte
din cel de-al doilea subvector, altfel, este un element care poate fi plasat în primul
subvector. Cel de-al doilea ciclu caută elemente mai mici sau egale cu elementul de
referinţă.

Primul lucru pe care trebuie să-1 demonstrăm este că cele două cicluri se termină. Prima
valoare a lui i pentru care se termină primul ciclu este chiar st deoarece acesta este
elementul de referinţă. Avem mai multe cazuri:

Cazul 1: x este cel mai mic element din vector, ne mai existând nici un element egal cu
el.
In cazul în care x este cel mai mic element din vector, neexistând nici un element egal
cu.el, cel de-al doilea ciclu se termină tot cu j egal cu st. In acest caz partiţionarea se
încheie, cel mai din dreapta element al subvectorului stâng fiind cel de pe poziţia j.

Cazul II: Există cel puţin un element mai mic sau egal cu x.

In acest caz, cel de-al doilea ciclu se termină pe primul element mai mic sau egal decât x.
Deoarece acest element se află în dreapta primului element are loc interschimbarea celor
două elemente. Să presupunem acum că ultima interschimbare pe care o efectuează
algoritmul este cea dintre elementele v[p] şi v[q], cu p <q. După interschimbare avem
v[p] <= x şi x <= v[q]. Acum avem două subcazuri:

a) q - p = 1. In acest caz cele două elemente au fost şi au rămas vecine. Cele două cicluri
se încheie cu i = q şi j = p. Este evident că algoritmul se încheie întorcând indexul celui
mai din dreapta element ai subvectorului stâng, adică valoarea lui j.

b) q - p > 1. Este evident că cele două cicluri se încheie, în cel mai rău caz când primul
ciclu îl va găsi pe v[q], iar cel de-al doilea ciclu îl va găsi pe v[p]. Deoarece nu se mai
efectuează nici o interschimbare, cele două cicluri se încheie cu i >= j. Avem v[k] < x,
pentru oricare k din intervalul [p + 1, i - 1] şi x < v[m] pentru oricare m din intervalul
[j+1, q-1]. Este evident că cele două intervale, [p + 1, i - 1] şi [j+1, q-1], sunt disjuncte.
Avem deci i - 1 < j + 1, de unde rezultă că i - j < 2.

O să ilustrăm funcţionarea algoritmului cu următorul exemplu:

Partiţionarea 1: Prin partiţionarea şirului: 7 13 100 1 54 87 5 13 44 99 89 2 9 6 35 88 se


obţin următoarele partiţii: 6 2 5 1 şi : 54 87 100 13 44 99 89 13 9 7 35 88
Partiţionarea 2. Prin partiţionarea şirului: 6 2 5 1 se obţin partiţiile: 1 2 5 si 6
Partiţionarea 3: Prin partiţionarea şirului: 1 2 5 se obţin partiţiile: 1 şi 2 5
Partiţionarea 4. Prin partiţionarea şirului 2 5 se obţin partiţiile: 2 şi 5
Partiţionarea 5. Prin partiţionarea şirului: 54 87 100 13 44 99 89 13 9 7 35 88 se obţin
partiţiile:
35 7 9 13 44 13 şi 89 99 100 87 54 88
Partiţionarea 6. Prin partiţionarea şirului: 35 7 9 13 44 13 se obţin partiţiile:13 7 9 13 şi
44 35
Partitionarea 7. Prin partitionarea şirului: 13 7 9 13 se obţin partiţiile: 13 7 9 şi 13
Partitionarea 8. Prin partitionarea şirului: 13 7 9 se obţin partiţiile: 9 7 şi 13
Partitionarea 9. Prin partitionarea şirului: 9 7 se obţin partiţiile: 7 şi 9
Partitionarea 10. Prin partitionarea şirului: 44 35 se obţin partiţiile: 35 şi 44
Partitionarea 11. Prin partitionarea şirului: 89 99 100 87 54 88 se obţin partiţiile: 88 54 87
şi 100 99 89
Partiţionarea 12. Prin partiţionarea şirului: 88 54 87 se obţin partiţiile: 87 54 şi 88
Partiţionarea 13. Prin partiţionarea şirului: 87 54 se obţin partiţiile: 54 şi 87
Partiţionarea 14. Prin partiţionarea şirului: 100 99 89 se obţin partiţiile: 89 99 şi 100
Partiţionarea 15. Prin partiţionarea şirului: 89 99 se obţin partiţiile:89 şi 99
în final se obţine şirul ordonat crescator: 1 2 5 6 7 9 13 13 35 44 54 87 88 89 99 100

In cel mai rău caz, partiţionarea produce o regiune cu un singur element. Cel mai bun caz
se obţine atunci când vectorul este împărţit în două părţi egale. Timpul mediu este mai
apropiat de cel mai bun caz, adâncimea arborelui de partiţionare fiind aproximativ egală
cu log2n, iar pentru fiecare nivel sunt comparate toate elementele arborelui cu elementele
de referinţă, ceea ce înseamnă că avem o complexitate de ordinul n. Deci complexitatea
algoritmului este de ordinul n*log2n, notată cu

O(n*log2n). în cel mai rău caz, care se obţine de exemplu atunci când şirul este deja
sortat, adâncimea arborelui este n, ceea ce ne duce la complexitatea 0(n2). Pentru a evita
obţinerea celui mai rău caz se poate modifica procedura de partiţionare astfel încât
elementul de referinţă să fie ales aleator.

Analiza complexităţii (cazul anterior )


1. Cazul cel mai nefavorabil: de fiecare dată pivotul este elementul marginal, deci una
dintre liste este de lungime 0 şi lungimea celeilalte scade cu 1.
O(n2)

T(0) = T(1) = 0
T(n + 1) = T(n) + T(0) + n = T(n) + n, n>0

T(n + 1) = (T(n - 1) + (n – 1)) + n = … = T(1) + 1 + 2 + … + n = O(n^2)


2. Cazul mediu:
Timpul mediu pentru toate permutările posibile ale unei secvenţe de intrare: O(n log(n))

5) sortarea prin numărare a unui vector A


for i=1 to n do
C[i]=B[i]=0;

for i=0 to n-2 // fixez cate un pivot si numar toate elem a caror val e
mai mare, adaugand cate un 1 dupa caz
     for j=i+1 to n-1 do
if a[i]<a[j] then
B[j]++
else
B[i]++

for i=0 to n-1 do


C[B[i]]=A[i]
exemplu A=(7, 2, 3, 4)

6) sortarea radix
Radix sort
Imagine din:

algoritm? (home study)


7) Sortarea prin inserţie

Este sortare prin comparaţie, stabilă, pe loc.

procedure insertion_sort(array)
for i = 2 to n // 1 to n-1
key = array[i]
j=i-1
while (j ≥ 1 and array[j] > key)
do array[j + 1] = array[j]
j=j−1
array[j + 1] = key
exemplu:
A= (2, 5, 1, 7, 2)
*****
i=2;
key=5;
j=1;
A[2]=5;// (2/>5)
---
i=3;
key=1;
j=2; //(5>2);
A[3]=5; //A=(2, 5, 5, 7, 2);
j=1;
A[2]=1; // A= (2, 1, 5, 7, 2); j>=1;A[1]=2>1
A[2]=A[1]; // A= (2, 2, 5, 7, 2); j=0
A[1]=1; // A = (1, 2, 5, 7, 2)
--------
i=4
key=7 ....
-----
i=5
key=2
j=4... A=(1, 2, 5, 7, 7), A= (1, 2, 5, 2, 7), A= (1, 2, 5, 5, 7), A= (1, 2, 2, 5, 7)

Analiza complexităţii
1. Cazul cel mai nefavorabil: lista este sortată descrescător.
Se compară fiecare element cu toate cele care îl preced, inclusiv:
n 1
n(n  1)
T (n)  1  2  ...  n  1   i   O(n 2 )
i 1 2

7 6 5 4 3 2 1
7 6 5 4 3 2 1
2. Cazul mediu:
O(n^2)