You are on page 1of 9

Seminarski rad iz osnova programiranja 1

Algoritmi za sortiranje
Ana Zrnić (56/20)
1. Januar, 2021. - 3. Januar, 2021.

1 Uvod i pregled osnovnih pojmova


Mnoge sfere naših života bile bi mnogo otežane da ne postoji neki koncept uredenosti, zbog toga ljudima
veoma prirodno dolazi sortiranje podataka i zaista, vidimo da je ovo nešto čime se susrećemo i na šta se
oslanjamo skoro svaki dan.
Formalno rečeno sortiranje je redanje podataka, u opadajućem ili rastućem poretku, po nekom kriterijumu
(leksikografski u slučaju stringova, po vrijednosti u slučaju brojeva, po boji, po nekoj funkciji, itd.)
Možemo da primijetimo moć ovog alata. Ono nam najprije olakšava i ubrzava snalaženje, npr. u nekom
nesortiranom nizu pretragu bismo vršili linearno, odnosno išli bismo element po element dok ne nademo ono
što smo tražili. Vidimo kako ovo nije efikasno jer u najgorem slučaju naš traženi element može da se nalazi
na samom kraju, što i nije loše za male količine podataka, ali predstavlja enorman problem za velike nizove
sa kojima se srećemo u praksi. Zbog toga su implementacije algoritama za sortiranje veoma dragocjene i na
programeru je da mudro procijeni koje algoritme treba da koristi, na osnovu sljedećih klasifikacija:
1. Vremenska složenost
Neki algoritmi će se pokazati kao brži od drugih, primijetićemo da što je implementacija teža, njeno
vrijeme izvršavanja će biti obrnuto kraće. Krajnji cilj je da težimo ka vremenskoj složenosti O(nlogn),
dok će se neki jednostavniji pokazati se sa vremenskom složenošću O(n2 ).
2. Prostorna složenost ili potrošnja memorije
Razlikovaćemo sortiranja u mjestu, koncept koji ne zahtijeva dodatnu memoriju (ili veoma malo memo-
rije) gdje je ideja da ulazni elementi mijenjaju mjesta preko pomoćne promjenljive, kao i ona sortiranja
koji koriste dodatnu memoriju, npr. u vidu nekog pomoćnog niza iste veličine kao i onaj koji sortiramo.
3. Stabilnost
Za neki niz kažemo da je stabilan“ kada elementi istih vrijednosti sadrže izvorni poredak čak i nakon

sortiranja.
4. Unutrašnje vs spoljašnje sortiranje
Unutrašnje sortiranje podrazumijeva korišćenje radne memorije ili RAM-a za izvršvanje (kod malih
nizova) dok će spoljašnje podrazumijevati smještanje dijelova podataka u radnu memoriju a za ostatak
se služi drugim uredajima kao npr. disk (zbog velike količine podataka)
Upoznaćemo se sa sljedećim algoritmima za sortiranje: selection, insertion, bubble, merge, quick i za kraj
ćemo pogledati u C-ov ugradeni qsort. Važno je dodati da načini implementacije za neke algoritme ne moraju
nužno izgledati onako kako su ovdje predstavljeni, ovo su samo neki od načina.

2 Implementacija algoritama
2.1 Generisanje nasumičnih brojeva i main funkcija
Prije nego što predemo na same algoritme moramo napraviti funkciju koja će generisati nasumične brojeve sa
kojima ćemo popuniti naš niz koji sortiramo. To ćemo uraditi koristeći rand funkciju koju ćemo prilagoditi
intervalu od 0 do 1000 na sljedeći način

1
Primjer 1 funkcija za generisanje nasumičnih brojeva
1 s t a t i c i n t r a n d b r ( i n t max)
2 {
3 i n t b r o j = rand ( )%max ; // o v a j n a c i n i m p l e m e n t a c i j e u v i j e k d a j e i n t e r v a l [ 0 , max)
4 return broj ;
5 }

Takode da izbjegnemo ponavljanje istih nasumičnih brojeva potrebno je na početku uključiti biblioteku
time.h, nakon toga main funkcija izgleda

Primjer 2 main funkcija


1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <time . h>
4 i n t main ( )
5 {
6 s r a n d ( time ( 0 ) ) ; // f u n k c i j a i z b i b l i o t e k e time . h
7 i n t n =1000; // v a r i j a b l a k o j a oznacava d i m e n z i j e n i z a
8 i n t max=1000; // maksimalna g r a n i c a za g e n e r i s a n j e n a s u m i c n i h b r o j e v a
9 i n t n i z [ n ] ; // n i z k o j i s o r t i r a m o
10 f o r ( i n t i =0; i <n ; i ++){
11 n i z [ i ]= r a n d b r (max) ; // p e t l j a kojom popunjavamo n i z p o z i v a j u c i f u n k c i j u r a n d b r
12 }
13 return 0;
14 }

Ovo je šablon koji ćemo koristiti za svaki algoritam, uz manuelne promjene varijable n od strane programera.

2.2 Selection sort


1. Vremenska složenost: O(n2 )
2. Potrošnja memorije: u mjestu
3. Stabilnost: nije stabilan
4. Mjesto sortiranja: unutrašnje

Ovo je jedan od jednostavnijih algoritama za implementiranje, što se i odražava u njegovoj vremenskoj


složenosti. Selection sort, kao što mu samo ime kaže, vrši neki izbor. Na koji način? Naime, zaviseći od
poretka, monotono rastućeg ili opadajućeg, ono traži najmanji ili najveći element, tako što će u prvom koraku
proći kroz čitav niz i postaviti traženu vrijednost na početak niza. U ostalim prolascima kroz niz, sortiranje
će početi od narednog prvog nesortiranog elementa i isti postupak se ponavlja dok ne dode do kraja.

Primjer 3 selection sort funkcija


1 s t a t i c void s e l e c t i o n s o r t ( i n t niz [ ] , i n t n) {
2 i n t temp ;
3 f o r ( i n t i =0; i <n−1; i ++) // p e t l j a k o j a p r o l a z i od prvog i n d e k s a
4 f o r ( i n t j=i +1; j <n ; j ++) // druga p e t l j a k o j a p r o l a z i od s u s j e d n o g i n e d k s a cime s e
omogucava p o r e d j e n j e
5 i f ( n i z [ i ]< n i z [ j ] ) { //u ovom u s l o v u s e v r s i zamjena uz pomoc p r i v r e m e n e v a r i j a b l e
6 temp=n i z [ i ] ;
7 n i z [ i ]= n i z [ j ] ;
8 n i z [ j ]=temp ;
9 }
10 }

2
Primjer 4 način pozivanja funkcije
1 i n t main ( )
2 {
3 .
4 .
5 s e l e c t i o n s o r t ( n i z , n ) ; // p r o s l j e d j u j e m o n e s o r t i r a n n i z i n j e g o v e d i m e n z i j e
6 .
7 .
8 return 0;
9 }

2.3 Insertion sort


1. Vremenska složenost: O(n2 )
2. Potrošnja memorije: u mjestu
3. Stabilnost: jeste stabilan
4. Mjesto sortiranja: unutrašnje

Takode jedan od jednostavnijih algoritama za sortiranje sa kvadratnom vremenskom složenošću. Zamisao


ove metode jeste da elemente iz nesortiranog dijela ”umećemo” u sortirani dio prolaskom i poredenjem kroz
isti. Iz ovakve postavke možda čak i zvuči da koristimo neki pomoćni niz ali kao što vidimo način potrošnje
memorije je u mjestu. Zašto je to tako? U prvom koraku ćemo pretpostaviti da prvi element pripada
sortiranom dijelu niza, što ostavlja ostatak niza (dimenzije i) sa i-1 nesortiranih vrijednosti. Uzimamo prvi,
poredimo ga sa pretpostavljenim prvim sortiranim i postavljamo ga na njegovo odgovarajuće mjesto. Sada
sortirani dio ima 2 elementa, a nesortirani i-2. Kao što vidimo ovaj postupak će se ponavljati tako što će se
sortirani odsječak povećavati uvijek za jedan, a nesortirani smanjivati za isti. Sve ovo se dešava u jednom
nizu korištenjem jedne pomoćne promjenljive za zamjene.

Primjer 5 insertion sort funkcija


1 s t a t i c void i n s e r t s o r t ( i n t niz [ ] , i n t n) {
2 i n t temp , k ;
3 f o r ( i n t i =1; i <n ; i ++){ // k r e c e od prvog zbog p r e t p o s t a v k e da j e v e c s o r t i r a n
4 k=i ; // j o s j e d n a pomocna v a r i j a b l a k o j a o l a k s a v a p r o v j e r u p o r e d j e n j a
5 w h i l e ( k !=0 && n i z [ k−1]< n i z [ k ] ) {
6 temp=n i z [ k ] ;
7 n i z [ k]= n i z [ k − 1 ] ;
8 n i z [ k−1]=temp ;
9 k−−; // samo p r e k o k zbog u s l o v a mozemo i z a c i i z p e t l j e
10 }
11 }
12 }

Primjer 6 način pozivanja funkcije


1 i n t main ( )
2 {
3 .
4 .
5 i n s e r t s o r t ( n i z , n ) ; // p r o s l j e d j u j e m o n e s o r t i r a n n i z i n j e g o v e d i m e n z i j e
6 .
7 .
8 return 0;
9 }

3
2.4 Bubble sort
1. Vremenska složenost: O(n2 )
2. Potrošnja memorije: u mjestu

3. Stabilnost: jeste stabilan


4. Mjesto sortiranja: unutrašnje

Bubble sort je prvi od predstavljenih gdje iz imena ne možemo naslutiti na koji način funkcioniše. Ideja je
da naši traženi elementi spremni za sortiranje ”isplivaju” na površinu tako što će porediti parove elemenata i
zamijeniti im mjesto ukoliko je to potrebno i svakim prolaskom počinje od nultog indeksa i ponavlja poredenja
dok više ne ispunjava uslov za poredenje. Smatran kao jedan od najsporijih algoritama za sortiranje, jer ima
mnogo prolazaka kroz kompletan niz.

Primjer 7 bubble sort funkcija


1 s t a t i c void bubble sort ( i n t niz [ ] , i n t n) {
2 i n t b r z a m j e n a =0 , temp ; // br zamjena : k o n t r o l n a v a r i j a b l a k o j a c e r e c i kad j e n i z
sortiran
3 do {
4 b r z a m j e n a =0; // zbog r e s e t o v a n j a j e p o s t a v l j e n a i u nut ar w h i l e f u n k c i j e
5 f o r ( i n t i =0; i <n−1; i ++)
6 i f ( n i z [ i ]< n i z [ i +1]) {
7 temp=n i z [ i ] ;
8 n i z [ i ]= n i z [ i + 1 ] ;
9 n i z [ i +1]=temp ;
10 b r z a m j e n a++;
11 }
12 } w h i l e ( b r z a m j e n a !=0) ; //u jednom t r e n u t k u n e c e u o p s t e u c i u i f i tada zbog r e s e t o v a n o g
b r z a m j e n a p e t l j a c e p r e s t a t i s a radom
13 }

Primjer 8 način pozivanja funkcije


1 i n t main ( )
2 {
3 .
4 .
5 b u b b l e s o r t ( n i z , n ) ; // p r o s l j e d j u j e m o n e s o r t i r a n n i z i n j e g o v e d i m e n z i j e
6 .
7 .
8 return 0;
9 }

2.5 Merge sort


1. Vremenska složenost: O(nlogn)
2. Potrošnja memorije: koristi dodatnu memoriju za pomoćni niz iste dužine kao i onaj koji sortiramo
3. Stabilnost: jeste stabilan

4. Mjesto sortiranja: spoljašnje sortiranje

Došli smo do prvog algoritma logaritamske vremenske složenosti, zaključak je da je ovo jedan od bržih
algoritama, kao i komplikovanije implementacije. Takode prvi put nailazimo na sortiranje koje se ne dešava
u mjestu već se koristi dodatna memorija. Ovo se dešava jer nam je za ovu metodu potreban pomoćni niz koji

4
je iste dužine kao i onaj koji upravo želimo sortirati, veoma očigledno iz ove postavke vidimo zašto je mjesto
sortiranja spoljašnje, za potrebe velikih dimenzija biće potrebno duplo prostora u memoriji da se sačuvaju
svi podaci. Algoritam funkcioniše rekurzivno poloveći niz u dva podniza dok ne dodemo do najprostijeg
slučaja kad imamo samo jedan element u podnizu. Tada se vraćamo unazad spajajući manje podnizove u
veće, prilikom čega ih sortiramo istovremeno. Potrebne su nam dvije funkcije, jedna koja polovi sam niz i
vrši rekurziju i druga koja vrši sortiranje i spajanje elemenata.

Primjer 9 merge sort funkcije


1 s t a t i c void merge sort ( i n t niz [ ] , i n t help [ ] , i n t n i z i , i n t v i s i ) {
2 i f ( n i z i >= v i s i ) r e t u r n ; // b a z n i s l u c a j
3 i n t s r e d n j i = n i z i + ( ( v i s i −n i z i ) /2 ) ; // i z b j e g a v a n j e o v e r f l o w a
4 m e r g e s o r t ( n i z , h e l p , n i z i , s r e d n j i ) ; // l i j e v a p o l o v i n a
5 m e r g e s o r t ( n i z , h e l p , s r e d n j i +1 , v i s i ) ; // desna p o l o v i n a
6 merge ( n i z , h e l p , n i z i , s r e d n j i , v i s i ) ;
7 }
8 s t a t i c v o i d merge ( i n t n i z [ ] , i n t h e l p [ ] , i n t n i z i , i n t s r e d n j i , i n t v i s i ) {
9 f o r ( i n t k = n i z i ; k <= v i s i ; k++) {
10 h e l p [ k ] = n i z [ k ] ; // pravimo k o p i j u pomocnu
11 }
12 int i = nizi ;
13 i n t j = s r e d n j i +1;
14 f o r ( i n t k = n i z i ; k<=v i s i ; k++){
15 i f ( i > s r e d n j i ) n i z [ k ] = h e l p [ j ++]; // ako d e s n i p o d n i z s a d r z i v i s e e l e m e n a t a
16 e l s e i f ( j > v i s i ) n i z [ k ] = h e l p [ i ++]; // ako l i j e v i p o d n i z s a d r z i v i s e e l e m e n a t a
17 e l s e i f ( h e l p [ i ] >= h e l p [ j ] ) n i z [ k ] = h e l p [ i ++]; // p o r e d j e n j e dva t r e n u t n a e l e m e n t a
i z l i j e v o g i desnog podniza , manji s e cuva u s o r t i r a n i n i z
18 e l s e n i z [ k ] = h e l p [ j ++];
19 }
20 }

Primjer 10 način pozivanja funkcije


1 i n t main ( )
2 {
3 .
4 i n t h e l p [ n ] ; // uvodimo pomocni n i z
5 m e r g e s o r t ( n i z , h e l p , 0 , n−1) ; // p r o s l j e d j u j e m o n e s o r t i r a n n i z , pomocni , n j e g o v n a j m a n j i
i n d e k s , kao i n a j v e c i
6 .
7 .
8 return 0;
9 }

2.6 Quick sort


1. Vremenska složenost: O(nlogn)
2. Potrošnja memorije: u mjestu
3. Stabilnost: nije stabilan
4. Mjesto sortiranja: unutrašnje sortiranje

Ako već nije očigledno iz imena, vremenska složenost nam ukazuje brzinu ovog algoritma. Takode izbjega-
vanjem korištenja dodatne memorije vidimo u kojem slučaju je pogodniji u odnosu na merge sort. Jedan od
načina da se implementira ovaj algoritam je sljedeći: potrebno je proglasiti jedan nasumični element niza
pivotom (to je najčešće prvi element niza) na osnovu kojeg polovimo niz tako što ćemo lijevi dio niza (ili
desni) namijeniti za one elemente koji su manji od pivota i obrnuto za one koji su veći. Nakon toga se

5
elementi manji od pivota pomijeraju lijevo od pivota, a svi elementi veći od njega se postavljaju sa njegove
desne strane. Postupak se rekurzivno ponavlja za svaki od dva podniza (bez pivot elementa) dok ne dobijemo
sortiran niz.

Primjer 11 quick sort funkcije


1 s t a t i c void q u i c k s o r t ( i n t niz [ ] , i n t n i z i , i n t v i s i ) {
2 i f ( v i s i <=n i z i ) r e t u r n ; // b a z n i s l u c a j
3 i n t p i v o t=p o d j e l a ( n i z , n i z i , v i s i ) ;
4 q u i c k s o r t ( n i z , n i z i , p i v o t −1) ; // p o z i v na l i j e v i p o d n i z
5 q u i c k s o r t ( n i z , p i v o t +1 , v i s i ) ; // p o z i v na d e s n i p o d n i z
6 }
7 s t a t i c int podjela ( int niz [ ] , int nizi , int v i s i ){
8 i n t p i v o t=n i z [ v i s i ] ; // p i v o t smo s t a v i l i na v r i j e d n o s t p o s l j e d n j e g i n d e k s a
9 i n t k=n i z i ; // pomocna v a r i j a b l a k o j a pamti n a j n i z i i n d e k s
10 i n t temp ;
11 f o r ( i n t i=k ; i < v i s i ; i ++)
12 i f ( n i z [ i ]> p i v o t )
13 {
14 temp=n i z [ i ] ;
15 n i z [ i ]= n i z [ k ] ;
16 n i z [ k]=temp ;
17 k++;
18 }
19 temp=n i z [ v i s i ] ;
20 n i z [ v i s i ]= n i z [ k ] ;
21 n i z [ k]=temp ;
22 r e t u r n k ; // c i l j f u n k c i j e j e da v r a c a i n d e k s p i v o t a
23 }

Primjer 12 način pozivanja funkcije


1 i n t main ( )
2 {
3 .
4 .
5 q u i c k s o r t ( n i z , 0 , n−1) ; // p r o s l j e d j u j e m o n i z , n a j m a n j i i n d e k s i n a j v e c i i n d e k s
6 .
7 .
8 return 0;
9 }

2.7 Ugradeni qsort


1. Vremenska složenost: O(nlogn)
2. Potrošnja memorije: u mjestu
3. Stabilnost: nije stabilan
4. Mjesto sortiranja: unutrašnje sortiranje
Kao posljednji algoritam koristićemo C-ovu ugradenu funkciju za sortiranje koja radi na logici quick sort-a.

Primjer 13 deklaracija funkcije


1 v o i d q s o r t ( v o i d ∗ base , s i z e t nitems , s i z e t s i z e , i n t ( ∗ compar ) ( c o n s t v o i d ∗ , c o n s t v o i d ∗ ) )

Koje sve elemente moramo proslijediti funkciji? Kao što vidimo po prvom elementu koji je zapravo poka-
zivač, prosljedujemo željeni niz za sortiranje, zatim njegove dimenzije, i veličinu bajtova svakog elementa u

6
nizu, odnosno veličinu tipa podatka kojom smo deklarisali niz. Na samom kraju vidimo da je proslijedena
funkcija koja zahtijeva prenošenje dva elementa po njihovim adresama. Ova posljednja funkcija je ostavljena
programeru da je napiše, jer je ona ta koja odreduje zapravo sam kriterijum poredenja elemenata.

Primjer 14 pomoćna funkcija koja korisniku daje slobodu izbora kriterijuma poredenja
1 s t a t i c i n t cmpfunc ( i n t ∗ a , i n t ∗ b ) {
2 r e t u r n ∗b − ∗ a ; //u nasem s l u c a j u t o j e o p a d a j u c i poredak
3 }

Primjer 15 način pozivanja funkcije


1 i n t main ( )
2 {
3 .
4 .
5 q s o r t ( n i z , n , s i z e o f ( i n t ) , cmpfunc ) ; // p r o s l j e d j u j e m o n i z , n j e g o v u d i m e n z i j u , v e l i c i n u t i p a
podatka k o j i k o r i s t i m o za n i z i na k r a j u f u n k c i j u k o j a o d r e d j u j e k r i t e r i j u m p o r e d j e n j a
6 .
7 .
8 return 0;
9 }

3 Rezultati
Nakon što smo uveli svaki algoritam, preostalo je samo da uporedimo rezultate istih. Mjerenja su izvršena
koristeći dimenzije od 1000, 10000, 50000, 100000 i 150000 elemenata. Vrijeme izvršavanja je izraženo u
milisekundama.

3.1 Tabelarni prikaz rezultata


Kao najbrži algoritam se pokazao ugradeni qsort.

3.2 Grafički prikaz rezultata

7
3.3 Zaključak
Kako znati kada koristiti koji algoritam na osnovu svih prethodnih informacija?

Selection sort:

• kod malih nizova


• kada imamo ograničenu memoriju
Insertion sort:
• kod malih nizova koji su parcijalno sortirani, jer se kod potpuno nesortiranih nizova pokazuje kao
najbrži pred kraj posla
• kada imamo ograničenu memoriju
• kada nam je bitna stabilnost
Bubble sort:

• takode bolji kod malih nizova koji su parcijalno sortirani


• kod velikih dimenzija koji je parcijalno sortiran jer je potreban samo jedan prolazak da algiritam
provjeri da li je niz sortiran ili ne
• kada nam je bitna stabilnost

Merge sort:
• kada imamo dimenzije u blizini 2n (neki stepen broja 2)
• kod velikih nizova gdje nam je bitna brzina

• kada nam je bitna stabilnost


Quick sort:
• kod velikih nizova gdje nam je bitna brzina, ali uzeti u obzir da u najgorem slučaju ima kvadratnu
vremensku složenost

• kada imamo dovoljno mjesta u radnoj memoriji da podržimo velike nizove, u suprotnom slučaju koristiti
merge

8
4 Izvori
D. Matić, Uvod u programiranje kroz programski jezik C, Prirodno – matematički fakultet u Banjoj Luci,
2018.
https://en.wikipedia.org/wiki/Sorting algorithmStability
https://en.wikipedia.org/wiki/Internal sort
https://www.tutorialspoint.com/c standard library/c function qsort.htm
https://www.geeksforgeeks.org/when-to-use-each-sorting-algorithms/

You might also like