- 1 2
- 1 5
- 1 3
- 2 5
- 2 3
- 2 3
Quicksort
-Tutorial-
Acest tutorial este destinat tuturor celor ce vor sa aprofundeze cunostintele legate de algoritmii de sortare prin imbogatirea acestora cu un algoritm pe cat de simplu, pe atat de eficient (quicksort).
Cunoscuta si sub numele de sortarea prin partitionare, quicksort este o metoda de sortare rapida, de unde deriva si numele.
Cu o complexitate medie de O(n log n), quicksort este alegerea optima de fiecare data cand se intalnesc probleme de sortare.
Algoritmul metodei imbina caracterul simplist cu performantele, ceea ce denota atat o usoara intelegere a algoritmului cat si performante destul de mari.
Pentru inceput, vom analiza aproximativ linie cu linie urmatorul cod in limbajul de programare C++:
- void quicksort(int v[100], int stanga, int dreapta)
- {
- int i, j, mijloc, aux; // variabilele
- i=stanga; // initializarea
- j=dreapta; // indicilor
- mijloc=v[(stanga+dreapta)/2]; // initializarea variabilei - pivot
- while(i<=j)
- {
- while(v[i]<mijloc) // apropierea i-ului de mijloc
- i++;
- while(v[j]>mijloc) // apropierea j-ului de mijloc
- j--;
- if(i<=j) //conditia efectuarii operatiei de interschimbare
- {
- aux=v[i];
- v[i]=v[j]; // operatia de interschimbare
- v[j]=aux;
- i++;
- j--;
- }
- }
- if(stanga<j) //recursivitatea
- quicksort(v, stanga, j); // in partea stanga
- if(i<dreapta)
- quicksort(v, i, dreapta); // in partea dreapta
- }
Aceasta functie este implementarea in C++ a algoritmului metodei de sortare quicksort.
- void quicksort(int v[100], int stanga, int dreapta)
Aceasta linie reprezinta antetul functiei. Dupa cum se observa, functia este declarata void pentru ca
nu trebuie sa intoarca nici un rezultat, ci doar sa modifice pozitia elementelor din vector.
Functia primeste 3 parametri: primul este vectorul ce trebuie sortat, al doilea este un numar ce reprezinta indexul primului element din vector (cel din stanga) iar al treilea este, de asemenea, un numar ce reprezinta indexul ultimului element din vector (cel din dreapta).
- int i, j, mijloc, aux;
Pentru a parcurge algoritmul, sunt necesare 4 variabile: o variabila ( i ) cu rolul de a parcurge vectorul (vorbind la modul general) spre dreapta prin incrementare ( i + + ); o variabila ( j ) cu rolul de a parcurge vectorul spre stanga prin decrementare ( j - - ); o variabila (mijloc) cu rolul de pivot, mai exact cu rolul de a partitiona, de a imparti vectorul in parti mai mici ce urmeaza a fi sortate ulterior si o variabila auxiliara ( aux ) cu rolul de a pastra temporar valorile elementelor ce vor interveni in operatia de interschimbare.
- i=stanga;
- j=dreapta;
In aceste doua operatii, variabilele i si j sunt initializate cu valorile parametrilor primiti de functie "stanga" respectiv "dreapta".
Motivul pentru care in algoritm nu sunt utilizati parametrii in mod direct (in loc de variabilele i si j) este reprezentat de faza recursiva a algoritmului unde valorile initiale ale parametrilor amintiti mai sus sunt indispensabile.
- mijloc=v[(left+right)/2];
Aceasta linie reprezinta initializarea variabilei - pivot cu o valoare apropiata de mijlocul vectorului (nu trebuie neaparat sa fie exact mijlocul). Aceasta valoare va deveni o valoare - reper in operatia de parcurgere a vectorului.
- while(i<=j)
Algoritmul este structurat in 3 instructiuni repetitive cu numar necunoscut de pasi si cu test initial (while). Prima structura este una de ansamblu in care se afla celelalte doua plus operatia de interschimbare. Aceasta structura de ansamblu are rolul de a "monitoriza" pozitia indicilor (i si j) si de a participa la partitionarea vectorului.
- while(v[i]<mijloc)
- i++;
- while(v[j]>mijloc)
- j--;
Urmatoarele doua instructiuni repetitive au rolul de a apropia indicii de variabila - pivot aflata aproape de mijlocul vectorului.
Prima instructiune apropie i-ul de mijloc iar a doua apropie j-ul. Aceste doua instructiuni participa constructiv la eficientizarea algoritmului din punct de vedere al timpului de executie.
- if(i<=j)
- {
- aux=v[i];
- v[i]=v[j];
- v[j]=aux;
- i++;
- j--;
- }
Aceasta bucata de cod reprezinta operatia de interschimbare. Este mai degraba o operatie mai complexa dat fiind faptul ca este compusa din mai multe operatii de atribuire. Rolul acesteia este acela de interschimba valorile elementelor vectorului atunci cand cele doua instructiuni repetitive pozitionate inaintea acestei operatii permit acest lucru.
- if(stanga<j)
- quicksort(v, stanga, j);
- if(i<dreapta)
- quicksort(v, i, dreapta);
Algoritmul se incheie, dupa cum se observa cu doua instructiuni conditionale ce verifica daca valorile indicilor rezultate dupa operatiile din cadrul algoritmului se incadreaza in partile corespunzatoare lor din vector (i-ul va trece in dreapta prin incrementare, deci trebuie sa fie mai mic decat parametrul initial "dreapta" primit de functie, iar j-ul trece in stanga prin decrementare, deci trebuie sa fie mai mare decat parametrul "stanga" primit initial de functie ).
Pentru a va ajuta sa intelegeti mai bine algoritmul, am creat urmatoarele diagrame sugestive ce reprezinta pozitia si valoarea variabilelor implicate direct in algoritm la fiecare parcurgere:

In acest pas ne aflam in momentul in care vectorul este nesortat inainte de inceperea primei parcurgeri.

Pasul 2 reprezentat in imaginea de mai sus descrie apropierea de mijloc a celor doi indici i si j asta insemnand mai exact parcurgerea celor doua while-uri pozitionate inaintea operatiei de interschimbare.
Explicatie:
Dupa cum am vazut in primul pas, mijlocul ia valoarea v[(stanga + dreapta)/2] ceea ce inseamna v[ (0 + 6)/2] adica v[3] care este egal cu 3.
La intrarea in primul while despre care am vorbit mai sus (cel care incrementeaza i - ul), valoarea lui i este 0 (zero) iar conditia instructiunii repetitive ( v[0]<mijloc ) nu este indeplinita deoarece v[0]=4 care este mai mare decat 3 (mijlocul) prin urmare, primul while nu este parcurs. De aceea in pasul 2 ( pasul de fata ) i - ul ramane 0 (zero) pentru ca nu se incrementeaza.
In mod analog, la intrarea in al doilea while se observa ca v[ j ] > mijloc ceea ce permite parcurgerea instructiunii ( adica decrementarea j -ului) insa parcurgerea are loc doar o singura data pentru ca dupa decrementare, j-ul devine 5 iar v[5] = 2 care este mai mic decat 3 deci conditia nu mai este indeplinita.

Imaginea de mai sus este destul de sugestiva. Dupa o scurta analiza a pasului 2 de mai sus, se observa ca i-ul este 0 (zero) iar j-ul 5. Acest lucru satisface conditia necesara efectuarii operatiei de interschimbare. Prin urmare, are loc interschimbarea intre v[ 0] si v[ 5 ].

In iteratia 1, primul pas este reprezentat de cele doua while-uri (exact ca in pasul 1 al parcurgerii initiale). Valoarea lui i este 1 pentru ca s-a incrementat in operatia de interschimbare din parcurgerea precedenta iar v[1] nu este mai mic decat variabila mijloc ( 6 > 3 ) deci nu se incrementeaza. Pe de alta parte, j-ul fiind decrementat din operatia de interschimbare se decrementeaza din nou, v[j] fiind mai mare decat variabila mijloc ( 5 > 3 ).

Operatia de interschimbare din iteratia 1.

Primul pas din iteratia a doua impune nerespectarea ( i > j ) a doua conditii identice: cea a instructiunii repetitive de ansamblu si cea a operatiei de interschimbare.

Dupa cum clar se observa, vectorul s-a impartit in doua, lucru impus de recursivitate. Imediat ce i-ul depaseste valoare j-ului, in vector se individualizeaza doua parti: prima este marginita de parametrul initial primit de functie (stanga) si de j (care s-a decrementat ) iar a doua parte este marginita de i (care s-a incrementat) si de celalalt parametru primit initial de functie (dreapta). In final, apelarea functiei prin parametrii ce marginesc cele doua parti, fac posibila sortarea integrala a vectorului de unde si numele de sortare prin partitionare . Urmarorii pasi sunt exact cei descrisi mai sus, aplicatii separat pe cele doua partitii formate.
In final, vectorul sortat va avea forma:

Pentru a va ajuta sa intelegeti mai bine algoritmul quicksort, va recomand urmatorul program:
http://www.cs.oswego.edu/~mohammad/classes/csc241/samples/sort/Sort2-E.html
Se numeste "Sort Animation" si este un program structurat destul de inteligent: In partea stanga este algoritmul (functia in C++) unde fiecare linie este colorata in momentul cand programul o parcurge iar in partea dreapta se afla vectorul cu pozitia fiecarui element si valoarea tuturor variabilelor din program. Exista optiuni de setare a vitezei de parcurgere a algoritmului cat si de schimbare a acestuia cu alte 3 metode de sortare cunoscute
Sper ca m-am facut inteles. Spor la invatat !
Cosmin_NTG
Welcome to BitCell. Click here to register !
) .
Dar totusi ma intereseaza cum as putea sa fac sa le ordonez intr-un singur QS...
