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)
Korak
l
d
s
a[s]
Odluka
1
0
9
4
16
23 > 16 → l = 5
2
5
9
7
56
23 < 56 → d = 6
3
5
6
5
23
pronađ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).
n
Linearna pretraga (najviše)
Binarna pretraga (najviše)
1 000
1 000
10
1 000 000
1 000 000
20
1 000 000 000
1 000 000 000
30
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
Uputstvo
Za svaki upit uradi binarnu pretragu – ukupno O(q log n). Linearna pretraga bi bila O(q · n) = 1010.
Rešenje (C++)
#include <iostream>
#include <vector>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n;
vector<int> a(n);
for (int &v : a) cin >> v;
cin >> q;
while (q--) {
int x;
cin >> x;
int l = 0, d = n - 1, poz = 0;
while (l <= d) {
int s = l + (d - l) / 2;
if (a[s] == x) { poz = s + 1; break; } // pozicije brojimo od 1
if (x < a[s]) d = s - 1;
else l = s + 1;
}
cout << poz << "\n";
}
return 0;
}
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
Uputstvo
Sortiraj niz. Broj elemenata manjih od x jednak je poziciji prvog elementa koji je >= x. Nađi je binarnom pretragom: ako je a[s] >= x, zapamti s i idi levo, inače desno. Ako takvog elementa nema, odgovor je n.
Rešenje (C++)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n, q;
cin >> n;
vector<int> a(n);
for (int &v : a) cin >> v;
sort(a.begin(), a.end());
cin >> q;
while (q--) {
int x;
cin >> x;
int l = 0, d = n - 1, prvi = n; // prvi element >= x (n ako ga nema)
while (l <= d) {
int s = l + (d - l) / 2;
if (a[s] >= x) { prvi = s; d = s - 1; }
else l = s + 1;
}
cout << prvi << "\n"; // toliko elemenata je ispred njega
}
return 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?
BINARNA PRETRAGA
Neka je dat niz od n elemenata. Neka je npr n=10; int A[10] = {2, 3, -1, 4, 6, 33, 12, 4, 5, 6, 8}
Postavimo sledeći problem: Proveriti da li niz sadrži element 33. Ovo možemo da uradimo tako što ćemo prolaskom kroz sve elemente niza redom proveravati kroz cikluse, da li je tekući element jednak sa 33. Ako ga pronađemo prekidamo ciklus.
Ovo je način koji predstavlja LINEARNU PRETRAGU elementa niza
U slučaju da u nizu nema traženog elementa uradiće se onoliko iteracija, koliko je n. U ovom primeru to je „samo“ puta. Postavlja se pitanje šta u slučuju da je broj elemenata niza mnogo veći. Vreme izvršavanja koda, bi u tom slučaju bilo preveliko. Efikasniji način bi bio korišćenjem BINARNE PRETRAGE.
Pretpostavimo da je učitan niz od n elemenata V[n]. Niz mora biti uređen(sortiran). Ako nije prvo se mora sortirati. Proveravamo da li se X nalazi u nizu. Algoritam se sastoji u tome da prolazeći kroz petlju proverimo da li traženi element jednak srednjem elementu. Ako jeste, pronađen je i pretraga se završava. Ako je traženi element X manji od srednjeg elementa proveravanog niza S onda se on nalazi u levoj polovini niza, a ako je veći onda u desnoj. U sledećoj iteraciji proveravamo samo onu polovinu niza, u kojoj se može naći traženi element. Dakle gornja granica za proveru je prethodna srednja vrednost niza. Ovaj postupak se ponavlja sve dok ne pronađemo traženi element
/* Pretrazujemo interval [l, d] */ int l = 0; int d = n-1; /* Sve dok interval [l, d] nije prazan */ while (l <= d) { /* Srednja pozicija intervala [l, d] */ int s = (l+d)/2; /* Ispitujemo odnos x i srednjeg elementa a[s]*/ if (x == a[s]) /* Element je pronadjen */ return s; else if (x < a[s]) { /* Pretrazujemo interval [l, s-1] */ d = s-1; } else { /* Pretrazujemo interval [s+1, d] */ l = s+1; } } primer 1: Napisati program koji sa standardnog ulaza unosi pomoću funkcije scanf nisku sa ne više od 20 karaktera i proverava da li niska sadrži znak @
Zadatak rešavamo pomoću Linearne pretrage:
#include <iostream> #include<stdio.h> #include<string.h> using namespacestd;
int main() { char a[21]; scanf("%s",&a); bool pronadjen=false; int poz=0; for(int i=0; i<strlen(a); i++){ if(a[i]=='@'){ pronadjen=true; poz=i; break; }
} if(pronadjen){ printf("Karakter @ je na poziciji %d",poz); } else{ printf("karakter nije pronađen"); } return 0; }
Ovde se koristi niz karaktera tipa char, koji je učitan koristeći funkciju scanf. Ovde niska karaktera ne mora da bude uređena za razliku od binarne pretrage koja zahteva da niz prethodno bude sortiran. Obratiti pažnju da je dužina niza dobijena pomoću standardne funkcije strlen iz standardnog zaglavlja <string.h>primer 2: Napisati program koji sa standardnog ulaza unosi pomoću funkcije scanf leksikografski uređenu nisku sa ne više od 20 karaktera i proverava da li niska sadrži znak @. Zadatak uraditi korišćenjem binarne pretrage
rešenje: Pretpostavimo da je uneta niska karaktera uređena po leksikografskom redosledu. Da nije, onda bi prvo to morali da uradimo. Koristimo algoritam Binarne pretrage gore opisan, da utvrdimo da li karakter ‘q’ postoji, i ako postoji na kojoj je poziciji.
#include <iostream> #include<stdio.h> #include<string.h> using namespacestd; /* primer 1: Napisati program koji sa standardnog ulaza unosi pomoću funkcije scanf nisku sa ne više od 20 karaktera i proverava da li niska sadrži znak q */ int main() { char a[21], x; scanf("%s", &a); bool pronadjen=false; int poz=0; x='q'; int n=strlen(a); /* Pretrazujemo interval [l, d] */ int l=0,d=n-1; /* Sve dok interval [l, d] nije prazan */ while(l<=d){ /* Srednja pozicija intervala [l, d] */ int s=(l+d)/2; if (x==a[s]){ /* Element je pronadjen */ poz=s; pronadjen = true; break; } else if(x<a[s]){ /* Pretrazujemo interval [l, s-1] */ d=s-1; } else{ /* Pretrazujemo interval [s+1, d] */ l=s+1; } } if(pronadjen){ printf("Karakter @ je na poziciji %d", poz); } else{ printf("karakter nije pronađen"); } return 0; }
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.