Brzo sortiranje bira jedan element – pivot – i preraspoređuje niz tako da levo od pivota budu manji, a desno veći elementi. Zatim isto radi, rekurzivno, za levi i desni deo. U proseku radi O(n log n) i u praksi je jedan od najbržih algoritama sortiranja.
Pre nego što počneš: potrebno predznanje – sortiranje objedinjavanjem, rekurzija. Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.
Razdvajanje oko pivota
Za pivot uzimamo poslednji element dela niza a[l..d]. Promenljiva i pokazuje kraj dela sa elementima manjim od pivota. Prolazimo sa j od l do d − 1: kada je a[j] < pivot, proširujemo „mali“ deo (i++) i menjamo a[i] i a[j]. Na kraju pivot stavljamo odmah iza malog dela – na poziciju i + 1, koja je njegovo konačno mesto.
Razdvajanje niza 7 2 9 4 3 8 5 (podvučen je deo manji od pivota)
j
a[j]
Akcija
Niz posle koraka (pivot = 5)
početak, i = −1
7 2 9 4 3 8 5
0
7
7 > 5, ništa
7 2 9 4 3 8 5
1
2
i = 0, zamena a[0] i a[1]
2 7 9 4 3 8 5
2
9
9 > 5, ništa
2 7 9 4 3 8 5
3
4
i = 1, zamena a[1] i a[3]
2 4 9 7 3 8 5
4
3
i = 2, zamena a[2] i a[4]
2 4 3 7 9 8 5
5
8
8 > 5, ništa
2 4 3 7 9 8 5
kraj
pivot na mesto i + 1 = 3
2 4 35 9 8 7
Posle razdvajanja broj 5 je na mestu na kome će biti i u sortiranom nizu. Ostaje da se sortiraju delovi 2 4 3 i 9 8 7 – istim postupkom.
Kompletan program
#include <iostream>
#include <vector>
using namespace std;
vector<int> a;
// razdvaja a[l..d] oko pivota a[d]; vraća konačnu poziciju pivota
int razdvoji(int l, int d) {
int pivot = a[d];
int i = l - 1; // a[l..i] su elementi manji od pivota
for (int j = l; j < d; j++) {
if (a[j] < pivot) {
i++;
swap(a[i], a[j]);
}
}
swap(a[i + 1], a[d]); // pivot ide iza manjih elemenata
return i + 1;
}
void quickSort(int l, int d) {
if (l >= d) return; // 0 ili 1 element: već sortirano
int p = razdvoji(l, d);
quickSort(l, p - 1); // levo od pivota
quickSort(p + 1, d); // desno od pivota
}
int main() {
int n;
cin >> n;
a.resize(n);
for (int &x : a) cin >> x;
quickSort(0, n - 1);
for (int x : a) cout << x << " ";
cout << endl;
return 0;
}
Za opadajući poredak u funkciji razdvoji uslov se menja u a[j] > pivot.
Video: brzo sortiranje
Postupak razdvajanja i rekurzivnih poziva prikazan je u videu.
Složenost i izbor pivota
Ako pivot svaki put podeli niz na dva približno jednaka dela, nivoa rekurzije ima oko log₂ n, a na svakom nivou se obradi n elemenata – ukupno O(n log n).
Najgori slučaj nastaje kada je pivot uvek najmanji ili najveći element – na primer kada je niz već sortiran, a za pivot uzimamo poslednji element. Tada se od n elemenata odvaja samo pivot, pa je složenost O(n²), a dubina rekurzije n.
Rešenje: pivot biramo slučajno. Pre razdvajanja zamenimo slučajno izabrani element sa poslednjim:
#include <cstdlib>
// na početku funkcije razdvoji(l, d):
int r = l + rand() % (d - l + 1); // slučajna pozicija iz [l, d]
swap(a[r], a[d]); // ona postaje pivot
Izbor / umetanje
Merge sort
Quick sort
std::sort
Prosečno
O(n²)
O(n log n)
O(n log n)
O(n log n)
Najgori slučaj
O(n²)
O(n log n)
O(n²)
O(n log n)
Dodatna memorija
ne
da, n
ne (samo rekurzija)
ne
Stabilan
umetanje da
da
ne
ne (postoji stable_sort)
Funkcija sort iz C++ biblioteke koristi poboljšano brzo sortiranje koje se, ako rekurzija postane preduboka, prebacuje na drugi algoritam – zato nikada ne radi O(n²).
Primena: k-ti najmanji element
Posle razdvajanja pivot je na svojoj konačnoj poziciji p. Ako tražimo element koji bi u sortiranom nizu bio na poziciji k, dovoljno je nastaviti samo u jednom delu: levo ako je k < p, desno ako je k > p. Ovaj algoritam (quickselect) u proseku radi O(n). U C++ postoji gotova funkcija nth_element.
Zadaci za vežbu
Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.
ZADATAK 01Težina: Srednje
K-ti najmanji element
Dato je n različitih celih brojeva i broj k (1 ≤ k ≤ n ≤ 105). Ispiši k-ti najmanji broj (za k = 1 to je najmanji).
Ulaz: u prvom redu n i k, u drugom n brojeva.
Primer 1
Ulaz
7 3
7 2 9 4 3 8 5
Izlaz
4
Primer 2
Ulaz
5 5
10 -3 8 1 6
Izlaz
10
Uputstvo
Koristi funkciju razdvoji iz lekcije. Traženi element je na poziciji k − 1 sortiranog niza. Posle razdvajanja: ako je p == k − 1, našli smo ga; ako je k − 1 < p, nastavi u levom delu, inače u desnom. Petlja umesto rekurzije je dovoljna jer se nastavlja samo u jednom delu.
Rešenje (C++)
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
vector<int> a;
int razdvoji(int l, int d) {
int r = l + rand() % (d - l + 1); // slučajan pivot
swap(a[r], a[d]);
int pivot = a[d], i = l - 1;
for (int j = l; j < d; j++)
if (a[j] < pivot) swap(a[++i], a[j]);
swap(a[i + 1], a[d]);
return i + 1;
}
int main() {
int n, k;
cin >> n >> k;
a.resize(n);
for (int &x : a) cin >> x;
int l = 0, d = n - 1, cilj = k - 1;
while (true) {
int p = razdvoji(l, d);
if (p == cilj) break;
if (cilj < p) d = p - 1; // traženi je levo od pivota
else l = p + 1; // traženi je desno od pivota
}
cout << a[cilj] << endl;
return 0;
}
ZADATAK 02Težina: Srednje
Otporno brzo sortiranje
Dato je n brojeva (1 ≤ n ≤ 105) koji su već sortirani opadajuće. Sortiraj ih rastuće brzim sortiranjem tako da program radi brzo i za najveće n.
Primer
Ulaz
5
9 7 5 3 1
Izlaz
1 3 5 7 9
Uputstvo
Sa pivotom na poslednjem mestu ovaj ulaz je najgori slučaj: O(n²) i dubina rekurzije 105. Uvedi slučajan izbor pivota iz odeljka o složenosti.
Rešenje (C++)
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
vector<int> a;
int razdvoji(int l, int d) {
int r = l + rand() % (d - l + 1); // slučajan pivot sprečava najgori slučaj
swap(a[r], a[d]);
int pivot = a[d], i = l - 1;
for (int j = l; j < d; j++)
if (a[j] < pivot) swap(a[++i], a[j]);
swap(a[i + 1], a[d]);
return i + 1;
}
void quickSort(int l, int d) {
if (l >= d) return;
int p = razdvoji(l, d);
quickSort(l, p - 1);
quickSort(p + 1, d);
}
int main() {
int n;
cin >> n;
a.resize(n);
for (int &x : a) cin >> x;
quickSort(0, n - 1);
for (int x : a) cout << x << " ";
cout << endl;
return 0;
}
Česte greške i saveti
Pivot se na kraju razdvajanja stavlja na poziciju i + 1, a rekurzija se poziva za [l, p − 1] i [p + 1, d] – pivot se više ne dira.
Bez slučajnog pivota sortiran ili obrnuto sortiran ulaz pravi O(n²) i može da prekorači stek.
Ako u nizu ima mnogo jednakih elemenata, i ova verzija je spora (svi jednaki odlaze na istu stranu). Na takmičenju zato koristi sort.
Brzo sortiranje nije stabilno.
Proveri znanje
Otvori kviz
Brzo sortiranje
1. Gde se nalazi pivot posle razdvajanja?
2. Kada brzo sortiranje sa pivotom na poslednjem mestu radi O(n²)?
3. Kako se izbegava najgori slučaj?
4. Koja funkcija iz C++ biblioteke nalazi k-ti element bez potpunog sortiranja?
BRZO SORTIRANJE(QUICK - SORT ENGL.)
Algoritam sortiranja koji se pokazao najbržim. Sastoji se u rekurzivnom ponavljanju razdvajanja elemenata niza oko neke vrednosti koja je usvojena za "srednju". Ovaj element se naziva PIVOT i postoje različit način da se odabere. U ovo tekstu za PIVOT je odabran krajnji element podniza u tekućoj iteraciji. Ako se npr. vrši sortiranje niza u rastućem redosledu, onda se prolaskom kroz niz, s leva na desno vrši razdvajanje tako da posle završetka iteracije na levoj strani od PIVOT-a budu vrednosti koje su manje ili jednake od PIVOT-a, a na desnoj veće. Proces se ponavlja sve dok se ne ispitaju svi elementi do PIVOT-a. Na kraju se PIVOT stavlja na pravo mesto, tj. na mesto posle levo raspoređenih elemenata, onih čije su vrednosti bile manje od PIVOT-a. Posmatrajmo npr. sledeći nesortiran niz:
VIDEO: Brzo sortiranje postupak
Slika 1: Brzo sortiranje. Početni niz
Za PIVOT-a je usvojen element na kraju niza, tj. na poziciji 9, čija je vrednost 14. Narandžastim okvirom je obeležen tekući element niza čija se vrednost poredi sa PIVOT-om. Na slici iznad se vidi stanje u drugom prolazu. Ta vrednost se poredi sa pivotom i ako je manja, vrši se zamena sa elementom uokvirenim isprekidanom crvenom linijom. Crveni okvir pokazuje u stvari prvu sledeću poziciu desno od poslednjeg zamenjenog elementa. Sa leve strane od te pozicije svi elementi moraju biti manji od PIVOTA, u ovom primeru manji od 14. Dakle, ukoliko je došlo do zamene tekućeg elementa(narandžasto) i elementa(crveno), pozicija za sledeću eventualnu zamenu se pomera za jedno mesto u desno. Ukoliko je tekući element niza veći od pivota nema zamene već se prelazi na ispitivanje sledećeg elementa na desno, tj narandžasti okvir se premešta. U prvom prolazu se poredio broj 25 sa 14, i s obzirom da je veči, nije bilo zamene. U drugom prolazu, koji je prikazan na prethodnoj slici, poredi se broj 13 sa PIVOT-om i s obzirom da je manji izvršiće se zamena broja 13 i 25, tj. brojeva na poziciji 0 i 1(vidi sledeću sliku)
Slika 2: Brzo sortiranje(Quick-Sort). Zamena elemenata
Element 13 se posle toga nalazi na početku niza a sledeće pozicija za eventualnu zamenu je desno na poziciji br. 1. U sledećem prolazu poredi se broj 10 sa 14(PIVOT-om), a zatim i -2. Obe ove vrednosti su manje od 14 pa će se i one zameniti i naći sa leve strane od sledeće pozicije za zamenu koja se sada premešta na poziciju 3(Crveni pravougaonik na slici 3). Sledeći član niza, 16 je veći od 14 pa se samo prelazi na sledeći element na poziciji 5, vidi sliku 3:
Slika 3: Brzo sortiranje(Quick-Sort). Premeštanje pozicije
Na kraju posmatrane iteracije, kada se uporede svi elementi niza levo od PIVOT-a, PIVOT se stavlja na odgovarajuće mesto desno od svih premeštenih članova na levoj strani. Ta pozicija je ona obeležena crvenim pravougaonikom, vidi sliku 4:
Slika 4: Brzo sortiranje(Quick-Sort). Premeštanje PIVOT-a
Kod koji vrši gore prikazana razdvajanja je prikazan pomoću funkcije prikazane ispod:
/*Metoda koja vrši razdvajanje elemenata niza. Niz je metodi prosleđen kao parametar a[ ]. Takođe se prosleđuju krajnja leva pozicija posmatranog niza(podniza) "l" i krajnja desna pozicija "d"*/ void deljenjeNiza(int a[ ], int l,int d)
{
int p = a[ d ];// Pivot je element na krajnjoj poziciji int i, j;
i=l-1; for(j=l; j<=d-1; j++)
{
if(a[j] < p)
{
if(i != j)
{
zamena(&a[i+1],&a[j]);
i++;
}
}
}
}
Podešavanja kolačića
Koristimo kolačiće da bismo vam pružili najbolje moguće iskustvo. Takođe nam omogućavaju da analiziramo ponašanje korisnika kako bismo stalno poboljšavali veb lokaciju za vas.