Binarna pretraga

Lekcija 1 od 4 · Binarna pretraga

Binarna pretraga pronalazi traženi broj u sortiranom nizu tako što ga svaki put poredi sa srednjim elementom i odbacuje polovinu niza. U nizu od milion elemenata dovoljno je 20 koraka, dok linearna pretraga može da napravi milion.

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

Linearna pretraga

Najjednostavniji način da proverimo da li se broj x nalazi u nizu je da redom pogledamo sve elemente:

#include <iostream>
using namespace std;

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

    int poz = -1;                    // -1 znači: nije pronađen
    for (int i = 0; i < n; i++) {
        if (a[i] == x) {
            poz = i;
            break;                   // našli smo ga, prekidamo petlju
        }
    }
    cout << poz << endl;
    return 0;
}

Linearna pretraga radi i za nesortiran niz, ali u najgorem slučaju pregleda svih n elemenata – složenost je O(n). Ako u istom nizu tražimo mnogo brojeva (na primer 105 upita u nizu od 105 elemenata), to je 1010 koraka – predugo.

Ideja binarne pretrage

Kada je niz sortiran, dovoljno je pogledati srednji element a[s]:

  • ako je a[s] == x – pronašli smo ga;
  • ako je x < a[s] – traženi broj može biti samo levo od sredine, pa desnu polovinu odbacujemo;
  • ako je x > a[s] – može biti samo desno, pa odbacujemo levu polovinu.

Isto kao kada tražiš reč u rečniku: otvoriš ga na sredini i odmah znaš da li treba da ideš napred ili nazad.

Traženje broja 23 u nizu 2 5 8 12 16 23 38 56 72 91 (pozicije 0–9)
Korakldsa[s]Odluka
10941623 > 16 → l = 5
25975623 < 56 → d = 6
356523pronađen na poziciji 5

Program

#include <iostream>
using namespace std;

int main() {
    int n, x;
    cin >> n;
    int a[100000];
    for (int i = 0; i < n; i++) cin >> a[i];   // niz je sortiran rastuće
    cin >> x;

    int l = 0, d = n - 1, poz = -1;   // tražimo u intervalu [l, d]
    while (l <= d) {                  // dok interval nije prazan
        int s = l + (d - l) / 2;      // sredina intervala
        if (a[s] == x) {
            poz = s;                  // pronađen
            break;
        } else if (x < a[s]) {
            d = s - 1;                // nastavljamo u [l, s-1]
        } else {
            l = s + 1;                // nastavljamo u [s+1, d]
        }
    }
    cout << poz << endl;
    return 0;
}
Zašto l + (d − l) / 2? Matematički je isto što i (l + d) / 2, ali kada su l i d veliki brojevi (blizu 2·109), zbir l + d prekoračuje opseg tipa int. Ovaj oblik to sprečava – važno kod binarne pretrage po rešenju.

Koliko koraka?

Posle svakog koraka interval se bar prepolovi: n → n/2 → n/4 → … → 1. Broj koraka je oko log₂ n – složenost je O(log n).

nLinearna pretraga (najviše)Binarna pretraga (najviše)
1 0001 00010
1 000 0001 000 00020
1 000 000 0001 000 000 00030

Ako niz nije sortiran, prvo ga sortiramo (sort, O(n log n)) – to se isplati kada je upita mnogo.

Varijanta: prvo pojavljivanje

Ako se x javlja više puta, osnovna verzija vraća neku od njegovih pozicija. Često nam treba prva. Tada, kada nađemo x, zapamtimo poziciju i nastavimo da tražimo levo:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, x;
    cin >> n;
    vector<int> a(n);
    for (int &v : a) cin >> v;
    cin >> x;

    int l = 0, d = n - 1, poz = -1;
    while (l <= d) {
        int s = l + (d - l) / 2;
        if (a[s] >= x) {
            if (a[s] == x) poz = s;   // kandidat; možda postoji i raniji
            d = s - 1;                // nastavi levo
        } else {
            l = s + 1;
        }
    }
    cout << poz << endl;
    return 0;
}

Za ovakve zadatke C++ ima gotove funkcije lower_bound i upper_bound – o njima je sledeća lekcija.

Primer: znak u sortiranoj niski

Binarna pretraga radi nad bilo kojim sortiranim nizom, pa i nad niskom čiji su znaci poređani po ASCII kodovima. Program proverava da li sortirana niska (najviše 20 znakova) sadrži znak @:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    cin >> s;                         // znaci su sortirani po ASCII kodu
    char trazeni = '@';
    int l = 0, d = (int)s.size() - 1, poz = -1;
    while (l <= d) {
        int m = (l + d) / 2;
        if (s[m] == trazeni) { poz = m; break; }
        if (trazeni < s[m]) d = m - 1;
        else l = m + 1;
    }
    if (poz >= 0) cout << "Znak @ je na poziciji " << poz << endl;
    else cout << "Znak nije pronađen" << endl;
    return 0;
}

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

Mnogo upita

Dat je sortiran niz od n različitih brojeva i q upita (1 ≤ n, q ≤ 105). Za svaki upit x ispiši njegovu poziciju (od 1) ili 0 ako ga nema.

Ulaz: n, zatim niz, pa q i q brojeva.

Primer

Ulaz

5
3 8 10 15 20
4
10 3 4 20

Izlaz

3
1
0
5
ZADATAK 02Težina: Srednje

Koliko je manjih?

Dat je niz od n brojeva (nije sortiran) i q upita. Za svaki upit x ispiši koliko elemenata niza je strogo manje od x.

Primer

Ulaz

6
5 1 9 3 3 7
3
3 10 0

Izlaz

1
6
0
Česte greške i saveti
  • Binarna pretraga radi samo nad sortiranim nizom.
  • Uslov petlje je l <= d; sa l < d se propušta interval od jednog elementa.
  • Granice se pomeraju na s − 1 i s + 1. Ako napišeš d = s uz uslov l <= d, petlja može da se vrti beskonačno.
  • Za velike granice koristi s = l + (d − l) / 2.

Proveri znanje

Otvori kviz

Binarna pretraga

1. Koji uslov mora da ispunjava niz za binarnu pretragu?




2. Koliko najviše koraka treba binarnoj pretrazi u nizu od milion elemenata?




3. Traži se 7, a srednji element je 12. Šta radimo?




4. Zašto se piše s = l + (d − l) / 2?