Mali, manji, najmanji...




Maja Vilus

Koliko puta ste slagali karte ze remi, bridž ili neku drugu igru? Verovatno niste ni znali da ste, fino se zabavljajući, koristili jedan od elementarnih algoritama sortiranja, koji se koristi i pri rešavanju računarskih problema.

 Postoji mnogo načina da se niz podataka uredi, od najelementarnijih do veoma kompleksnih metoda. Izbor metode, odnosno algoritma, zavisi od procene "početnih uslova": ako se sortira kratak niz podataka, lakše je izabrati elementarni metod koji ćete lako implementirati. Međutim, ako se sortiranje podavlja veliki broj puta ili se radi o ogromnim nizovima, brzina postaje jako bitan, čak odlučujući faktor.
 Efikasnost nekih algoritama zavisi od početne "sortiranosti" elemenata: algoritam koji je najbolji kada su brojevi nasumično raspoređeni može da se pokaže veoma sporim ako su brojevi već gotovo sortirani. Zbog toga ćemo pri predstavljanju metoda sortiranja njihovu efikasnost pratiti sa tri aspekta: nad slučajno generisanim nizom elemenata, nad potpuno sortiranim i nad "inverzno" sortiranim nizom elemenata, pri čemu je ovaj poslednji redosled "noćna mora" mnogih algoritama.
 Takođe, značajan podatak je i količina dodatne memorije, neophodna za pojedine metode sortiranja. Ovde je moguće izdvojiti tri slučaja. U prvom, sortiranje se odvija na "licu mesta", odnosno dodatna memorija je neophodna za neki vrlo mali stek ili tabelu. Drugi slučaj je karakterističan za algoritme koji koriste povezanu listu, pa im je, shodno tome, potrebna memorija za još N (broj elemenata koji se sortiraju) pointera, a treći, memorijski najzahtevniji, za algoritme kojima je neophodna kompletna kopija niza elemenata koji se sortiraju.
 Poslednja, ali nikako najmanje značajna karakteristika je stabilnost algoritma, tj. mogućnost očuvanja relativnog poretka elemenata iste vrednosti pri sortiranju. Recimo da treba prikazati spisak finalistkinja izbora za Mis sveta, pri čemu je početni redosled bio formiran po abecednom redu. Ako postoji više podjednako lepih devojaka (čitati: sa istim brojem poena), stabilno sortiranje će, kao prvu, na spisak postaviti lepoticu sa imenom koje počinje slovom A.
 Često se javlja i potreba za sortiranjem slogova u okviru velikih datoteka. Slogovi, pored ostalih podataka, imaju i tzv. ključeve - podatke na osnovu kojih se vrši pristup, pa i sortiranje. Naprimer, u bazi podataka o studentima, ključevi za sortiranje mogu biti redni brojevi indeksa. Pošto se i sortiranje zapisa svodi na sortiranje po ključevima koji su, najčešće, alfanumerički podaci, algoritmi će biti predstavljeni generalizovano, na primeru nizova celih brojeva.


Metoda selekcije

 SelectionSort
SelectionSort (int n) {
    int i, j, Min;
    for (i = 0; i << n; i++) {
       Min = i;
       for (j = i + 1; j << n; j++) 
          if (a[Min] >> a[j]) 
             Min = j;
          j = a[Min];
          a[Min] = a[i];
          a[i] = j; }
 }
 Jedan od najjednostavnijih algoritama je sortiranje metodom selekcije ( selection sort ). Treba pronaći najmanji element niza i zameniti ga sa elementom na prvoj poziciji, zatim pronaći sledeći najmanji element i zameniti ga sa elementom na drugoj poziciji itd. Program koji ovako sortira dat je na slici 1: a[i] je niz celih brojeva, a promenljiva n predstavlja broj elemenata niza. Ovaj metod je pogodan za datoteke sa manjim brojem jako velikih zapisa, pošto se svaki slog premešta najviše jednom.
 Ne upuštajući se u matematičko dokazivanje postavljene teoreme, navedimo da se metodom selekcije, u prosečnom slučaju, izvrši oko n 2 /2 poređenja i n zamena. Na vreme izvršavanja utiče samo broj promene vrednosti indeksa min - u većini slučajeva reč je o O(n*log(n)) zavisnosti, pa se može smatrati da je metod spor, ali uglavnom "neosetljiv" na redosled ulaznih podataka.


Metoda umetanja

 InsertionSort
 InsertionSort (int n) {
    int i, j, b;
    for (i = 0; i << n; i++) {
        b = a[i];
        for (j = i; j  >> 0; j--)
            if (a[j-1] >> b)
               a[j] = a[j-1];
            else
               break;
        a[j] = b; }
 }
Za nijansu komplikovanija, metoda umetanja ( insertion sort ) je već pomenuta kao efikasno oružje strastvenih kartaroša. Svaka "karta" se posebno posmatra i umeće na odgovarajuće mesto među već sortirane (npr. žandar između desetke i dame, kralj posle dame itd), sve dok se sve karte ne srede. Element se umeće na taj način što se svi veći pomeraju za jednu poziciju udesno. Postupak je prikazan na slici 2, a odgovarajući kod na slici 3.
 Pri sortiranju elemenata ovom metodom izvrši se prosečno n 2 /4 poređenja i n 2 /8 zamena, a u najgorem slučaju duplo više. Metod umetanja je efikasan ako se sortira već uglavnom sortiran niz (tada teži linearnom), pošto broj poređenja i zamena značajno zavisi od strukture ulaznih podataka.


Metoda mehurića

 BubbleSort
 BubbleSort (int n) {
    int i, j, b;
    for (i = n - 1; i >> 0; i--) {
       for (j = 1; j  <<= i; j++)
           if (a[j-1] >> a[j]) {
              b = a[j-1];
              a[j-1] = a[j];
              a[j] = b; }
    }
 }
Bubble sort je kombinacija prethodna dva metoda, a izgleda ovako: polazi se od prvog elementa i ide, redom, do kraja niza. Svaki element se poredi sa prvim i ako je veći od njega, dalje poređenje se vrši sa novim elementom. U prvom prolazu se, dakle, pronalazi najveći element koji će se na kraju prolaza naći na poslednjem mestu u nizu. U sledećem prolazu se opet kreće od prvog elementa, ali se završava sa pretposlednjim. Za razliku od metode selekcije gde se niz sortira "sleva", odnosno prvo se sortiraju najmanji elementi, kod bubble sorta prvo se sortiraju najveći (slika 4).
 I u prosečnom i u najlošijem slučaju, broj poređenja je reda n 2 /2, kao i broj zamena. I ova metoda je efikasna ukoliko su podaci već sortirani: tada je potreban samo jedan prolaz kroz niz.


Shell sortiranje

 ShellSort
 ShellSort (int n) {
    int i, j, h, s, b;
    h =  (n + 1)  / 2;
    while (h) {
       j = n - h;
       do {
          s = 0;
          for (i = 0; i <<= j; i++) {
              if (a[i] >> a[i+h]) {
                 b = a[i];
                 a[i] = a[i+h];
                 a[i+h] = b;
                 s = i; }
          }
          j = s - h;
       } while (s) ;
    h = h / 2; }
 }
Ovo je poboljšana verzija Bubble algoritma. Bubble sortiranje je sporo kada mu "podmetnete" niz inverzno sortiranih elemenata, odnosno kada se najmanji element nalazi na kraju niza - pošto se razmenjuju samo susedni elementi, biće potrebno mnogo zamena da on dođe na početak. Shell sortiranje dozvoljava zamenu međusobno udaljenih elemenata, a bazira se na ideji da se podaci preurede na takav način da, počevši od bilo kog elementa, uzimanje svakog h -tog dovodi do sortiranog niza. Ovakav niz se naziva h -sortiranim, a postepeno smanjujući vrednost h , dolazi se do potpuno sortiranog niza. Postupak je prikazan na slici 5, a kod je dat na listingu 6.
 Analiza performasi ovog metoda nije jednostavna, jer vrednosti h i vrednosti za koju se ona umanjuje pri svakom prolazu nisu striktno utvrđene. Štaviše, efikasnost zavisi od izbora ovih vrednosti (npr. može se početi sa h = n i u svakom prolazu umanjivati h za 2/3h ), ali je prilično neosetljiva na početnu sortiranost elemenata. Pokazalo se da je ovo metod koji se u opštem slučaju može preporučiti - upoznaćemo i kompleksnije metode koji daju bolje rezlutate, ali njihova implementacija nije ovako jednostavna.


Metoda brojanja raspodele

 DistributionCounting
 DistributionCounting (int n, int m) {
    int i, j;
    for (i = 0; i << m; i++)
        Count[i] = 0;
    for (i = 0; i << n; i++)
        Count[a[i]]++;
    for (i = 1; i << m; i++)
        Count[i] = Count[i-1] + Count[i];
    for (i = n - 1; i >>= 0; i--) {
        b[Count[a[i]]-1] = a[i];
        Count[a[i]]--; }
    for (i = 0; i << n; i++)
        a[i] = b[i]; }
Za kraj, predstavimo zgodan algoritam koji efikasno rešava konkretan problem sortiranja niza od ukupno n brojeva, koga čine celi brojevi iz opsega [0, m-1]. Ako su m i n jednaki a na raspolaganju je dovoljna memorija, rešenje je vrlo jednostavno:

for( i = 0; i <<= n; i++ )

     b[a[i] = a[i];
 Algoritam se zasniva na pronalaženju broja elemenata koji imaju istu vrednost. Formira se niz Count od m elemenata (slika 7). i -ti član predstavlja broj elemenata originalnog niza koji su manji ili jednaki od vrednosti i . Ako se vrednost 0 u originalnom nizu podataka pojavljuje tri, a vrednost 1 šest puta, Count[0] će imati vrednost 3 a Count[1] vrednost 3 + 6 = 9. Niz Count služi kao kontrola mesta umetanja odgovarajućeg elementa u novi niz b i to na sledeći način: kada se naiđe na vrednost 0, ona će biti smeštena na mesto Count[0] - 1 (oduzima se jedan jer indeksi niza počinju od nule), a sama vrednost Count[0] biće dekrementirana, da bi se sledeća nula smestila na mesto 1 u novom nizu b . Postupak se ponavlja za svaki element niza koji se sortira, uz dekrementiranje odgovarajuće Count vrednosti.
 Ovaj metod, sa odgovarajućim proširenjem, predstavljaće osnovu za kompleksniji algoritam kojim ćemo se baviti u sledećim nastavcima. Takođe, ovaj put su elementarne metode samo predstavljene, a poređenje njihove efikasnosti je dato samo u grubim crtama. Analiza rezultata, poređenje vremena izvršavanja algoritama sortiranja u različitim uslovima i priča o kompleksnijim metodama nas očekuju u jednom od sledećih brojeva "PC"-ja...






PC home - osnovna strana Novi broj|Arhiva|Pretrazivanje svih brojeva|O nama
Pretplatite se na PC|Postanite saradnik casopisa PC|Pitanja i komentari u vezi casopisa