Sortiranje nizova: izbor, umetanje i mehurić

Lekcija 1 od 5 · Sortiranje

Sortirati niz znači poređati njegove elemente po veličini – od najmanjeg ka najvećem (rastuće) ili od najvećeg ka najmanjem (opadajuće). U ovoj lekciji upoznajemo tri jednostavna algoritma: sortiranje izborom, umetanjem i metodom mehurića.

Pre nego što počneš: potrebno predznanje – nizovi u C++, petlje.
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.

Zašto sortiramo?

Sortiran niz je mnogo lakše pretraživati i obrađivati: najmanji i najveći element su na krajevima, jednaki elementi su jedan do drugog, a traženi broj može da se nađe binarnom pretragom u nekoliko koraka umesto prolaskom kroz ceo niz.

Ako u nizu ima jednakih elemenata, umesto „rastuće“ tačnije je reći neopadajuće (svaki sledeći je veći ili jednak prethodnom). Na primer, niz 2 5 5 8 je sortiran neopadajuće.

Sortiranje izborom (selection sort)

Ideja: pronađi najmanji element i zameni ga sa prvim. Zatim u ostatku niza (od drugog elementa) pronađi najmanji i zameni ga sa drugim, i tako dalje. Posle koraka i prvih i + 1 elemenata je na svom konačnom mestu.

Sortiranje izborom niza 5 2 9 1 7
KorakNiz posle korakaŠta se desilo
početak5 2 9 1 7
i = 01 2 9 5 7najmanji je 1, menja mesto sa 5
i = 11 2 9 5 7najmanji u ostatku je 2, već je na mestu
i = 21 2 5 9 7najmanji u ostatku je 5, menja mesto sa 9
i = 31 2 5 7 9najmanji u ostatku je 7, menja mesto sa 9
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int a[1000];
    for (int i = 0; i < n; i++) cin >> a[i];

    for (int i = 0; i < n - 1; i++) {
        int minPoz = i;                       // pozicija najmanjeg u delu a[i..n-1]
        for (int j = i + 1; j < n; j++)
            if (a[j] < a[minPoz]) minPoz = j;
        swap(a[i], a[minPoz]);                // najmanji ide na mesto i
    }

    for (int i = 0; i < n; i++) cout << a[i] << " ";
    cout << endl;
    return 0;
}

Za opadajući poredak dovoljno je tražiti najveći umesto najmanjeg: if (a[j] > a[maxPoz]).

Napomena: u starijim knjigama se često sreće verzija koja menja mesta odmah, čim nađe manji element (if (a[j] < a[i]) swap(a[i], a[j]);). I ona sortira tačno, ali pravi mnogo više zamena. Verzija sa pamćenjem pozicije najmanjeg pravi najviše n − 1 zamena.

Sortiranje umetanjem (insertion sort)

Ideja: kao kada slažeš karte u ruci. Levi deo niza je već sortiran. Uzimaš sledeći element, pomeraš udesno sve veće elemente iz sortiranog dela i ubacuješ ga na oslobođeno mesto.

Sortiranje umetanjem niza 5 2 9 1 7
KorakNiz posle korakaUmetnuti element
početak5 | 2 9 1 7sortiran deo je samo 5
i = 12 5 | 9 1 72 ide ispred 5
i = 22 5 9 | 1 79 ostaje na mestu
i = 31 2 5 9 | 71 ide na početak (9, 5, 2 se pomeraju)
i = 41 2 5 7 97 ide između 5 i 9
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int a[1000];
    for (int i = 0; i < n; i++) cin >> a[i];

    for (int i = 1; i < n; i++) {
        int x = a[i];                 // element koji umećemo
        int j = i - 1;
        while (j >= 0 && a[j] > x) {  // pomeramo veće elemente za jedno mesto udesno
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = x;                 // x ide na oslobođeno mesto
    }

    for (int i = 0; i < n; i++) cout << a[i] << " ";
    cout << endl;
    return 0;
}

Sortiranje umetanjem je vrlo brzo kada je niz skoro sortiran: tada se elementi skoro ne pomeraju.

Metoda mehurića (bubble sort)

Ideja: prolazimo kroz niz i poredimo susedne elemente; ako su u pogrešnom redosledu, menjamo im mesta. Posle prvog prolaza najveći element „ispliva“ na kraj niza, kao mehurić. Ako u nekom prolazu nije bilo nijedne zamene, niz je sortiran i možemo da stanemo.

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int a[1000];
    for (int i = 0; i < n; i++) cin >> a[i];

    for (int prolaz = 0; prolaz < n - 1; prolaz++) {
        bool bilo = false;                         // da li je bilo zamena u ovom prolazu
        for (int j = 0; j < n - 1 - prolaz; j++) { // poslednjih "prolaz" elemenata je već na mestu
            if (a[j] > a[j + 1]) {
                swap(a[j], a[j + 1]);
                bilo = true;
            }
        }
        if (!bilo) break;                          // niz je već sortiran
    }

    for (int i = 0; i < n; i++) cout << a[i] << " ";
    cout << endl;
    return 0;
}

Poređenje i složenost

Sva tri algoritma imaju dve ugnježdene petlje, pa za niz od n elemenata rade oko n²/2 poređenja. Kažemo da im je složenost O(n²) (čita se „o od n na kvadrat“).

AlgoritamBroj poređenjaBroj zamenaSkoro sortiran nizStabilan
Izboruvek oko n²/2najviše n − 1ne pomažene
Umetanjeod n do n²/2do n²/2 pomeranjavrlo brzoda
Mehurićod n do n²/2do n²/2brzo (prekid ako nema zamena)da

Stabilan algoritam ne menja međusobni redosled jednakih elemenata – to je važno kada sortiramo, na primer, učenike po bodovima, a želimo da učenici sa istim brojem bodova ostanu po redosledu prijave.

Koliko traje O(n²) sortiranje (računar uradi oko 108 prostih operacija u sekundi)
nn² (približno operacija)Vreme
1 0001 000 000trenutno
10 000100 000 000oko sekunde
1 000 0001012satima – predugo

Za veće nizove koriste se efikasni algoritmi složenosti O(n log n): sortiranje objedinjavanjem i brzo sortiranje. Na takmičenjima se u praksi koristi gotova funkcija sort.

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: Lako

Tri najbolja rezultata

Učitava se broj n (3 ≤ n ≤ 1000) i zatim n rezultata takmičara. Ispiši tri najbolja (najveća) rezultata, od najvećeg ka manjem.

Primer

Ulaz

6
45 78 12 99 78 60

Izlaz

99 78 78
ZADATAK 02Težina: Lako

Medijana

Dato je n brojeva, n je neparno (1 ≤ n ≤ 999). Medijana je broj koji se nalazi u sredini kada se brojevi poređaju po veličini. Ispiši medijanu.

Primer 1

Ulaz

5
7 1 9 3 5

Izlaz

5

Primer 2

Ulaz

3
10 10 2

Izlaz

10
ZADATAK 03Težina: Srednje

Koliko zamena pravi mehurić?

Za dati niz od n brojeva (1 ≤ n ≤ 1000) ispiši koliko zamena susednih elemenata napravi metoda mehurića dok ne sortira niz rastuće.

Primer 1

Ulaz

5
5 2 9 1 7

Izlaz

5

Primer 2

Ulaz

4
1 2 3 4

Izlaz

0
Česte greške i saveti
  • Unutrašnja petlja sortiranja izborom počinje od i + 1, a ne od 0.
  • Kod mehurića poredimo a[j] i a[j + 1], pa j ide najviše do n − 2 – inače se čita element van niza.
  • Kod umetanja uslov j >= 0 mora biti pre a[j] > x, da se ne bi čitalo a[-1].
  • Za n veće od nekoliko hiljada koristi sort iz biblioteke <algorithm> – algoritmi O(n²) su prespori.

Proveri znanje

Otvori kviz

Jednostavni algoritmi sortiranja

1. Koliko najviše zamena napravi sortiranje izborom (verzija sa pamćenjem pozicije najmanjeg) za niz od n elemenata?




2. Koji algoritam je najbrži za niz koji je već skoro sortiran?




3. Koliko poređenja otprilike radi algoritam složenosti O(n²) za n = 10 000?




4. Šta znači da je sortiranje stabilno?