Binary search adalah algoritma pencarian yang memanfaatkan sifat data terurut untuk mengurangi ukuran masalah menjadi separuh pada setiap langkah. Alih-alih memeriksa elemen satu per satu, binary search membandingkan target dengan elemen tengah, lalu membuang setengah dari ruang pencarian. Hasilnya: kompleksitas turun dari O(n) menjadi O(log n). Pada data satu juta elemen, binary search hanya butuh paling banyak 20 perbandingan dibanding satu juta pada linear search. Pada Chapter ini kita akan mempelajari prinsip divide and conquer pada pencarian, dua bentuk implementasi (iteratif dan rekursif), serta fungsi STL seperti std::binary_search, std::lower_bound, dan std::upper_bound.
Prasyarat: Data Terurut #
Binary search hanya bekerja pada data terurut. Bila data tidak terurut, hasil binary search tidak terdefinisi — algoritma mungkin mengembalikan indeks acak atau “tidak ketemu” padahal target ada. Pastikan array terurut secara menaik (atau menurun, dengan penyesuaian logika) sebelum menerapkan binary search.
Aturan dan Mekanisme: Divide and Conquer #
Misalkan array a terurut menaik dengan indeks [0, n-1]. Kita mencari target:
- Inisialisasi
lo = 0,hi = n - 1. - Selama
lo <= hi:- Hitung
mid = lo + (hi - lo) / 2. Hindari(lo + hi) / 2yang dapat overflow untuk angka besar. - Bila
a[mid] == target: ketemu. - Bila
a[mid] < target: target pasti di setengah kanan →lo = mid + 1. - Bila
a[mid] > target: target pasti di setengah kiri →hi = mid - 1.
- Hitung
- Bila loop selesai tanpa ketemu, target tidak ada.
Setiap iterasi membuang setengah elemen, sehingga jumlah iterasi maksimum adalah ceil(log2(n)).
Implementasi Iteratif #
Kode: Binary Search Iteratif #
#include <iostream>
#include <vector>
#include <optional>
std::optional<size_t> binary_search(const std::vector<int>& a, int target) {
size_t lo = 0;
size_t hi = a.size(); // exclusive upper bound initially, adjust below
if (a.empty()) return std::nullopt;
hi = a.size() - 1;
while (lo <= hi) {
size_t mid = lo + (hi - lo) / 2;
if (a[mid] == target) return mid;
else if (a[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return std::nullopt;
}
int main() {
std::vector<int> a = {1, 3, 5, 7, 9, 11, 13};
for (int t : {9, 1, 13, 8}) {
auto res = binary_search(a, t);
std::cout << "cari " << t << ": ";
if (res) std::cout << "ketemu di indeks " << *res << '\n';
else std::cout << "tidak ketemu\n";
}
return 0;
}Output yang Dihasilkan:
cari 9: ketemu di indeks 4
cari 1: ketemu di indeks 0
cari 13: ketemu di indeks 6
cari 8: tidak ketemu- Baris
hi = a.size() - 1;: kita memakai rentang inklusif[lo, hi]. Bila array kosong, segera kembalikannulloptuntuk menghindari underflowsize_t. - Baris
size_t mid = lo + (hi - lo) / 2;: rumus alternatif dari(lo + hi) / 2yang aman dari overflow. Untuksize_t, ekspresilo + (hi - lo) / 2tidak meluap selamahi >= lo. - Baris
lo = mid + 1;: target lebih besar daria[mid], jadi kita membuangmiddan seluruh elemen di kiri. Setengah kanan[mid+1, hi]tetap dieksplorasi. - Baris
hi = mid - 1;: cermin dari kasus di atas — buangmiddan setengah kanan, eksplorasi[lo, mid-1]. - Kondisi
while (lo <= hi): loop berhenti bilalo > hi, artinya ruang pencarian kosong.
Implementasi Rekursif #
Binary search cocok diekspresikan secara rekursif karena secara natural mengikuti pola divide and conquer. Kedalaman rekursi hanya O(log n) sehingga stack overflow tidak menjadi masalah praktis bahkan untuk n sangat besar.
Kode: Binary Search Rekursif #
#include <iostream>
#include <vector>
#include <optional>
std::optional<size_t> bs_rec(
const std::vector<int>& a, int target,
size_t lo, size_t hi)
{
if (lo > hi) return std::nullopt; // base case: ruang kosong
size_t mid = lo + (hi - lo) / 2;
if (a[mid] == target) return mid;
else if (a[mid] < target)
return bs_rec(a, target, mid + 1, hi);
else
return bs_rec(a, target, lo, mid - 1);
}
int main() {
std::vector<int> a = {1, 3, 5, 7, 9, 11, 13};
auto res = bs_rec(a, 9, 0, a.size() - 1);
if (res) std::cout << "ketemu di indeks " << *res << '\n';
else std::cout << "tidak ketemu\n";
return 0;
}Output yang Dihasilkan:
ketemu di indeks 4- Parameter
lo, hi: dibawa sebagai argumen agar tidak perlu memodifikasi state global. Setiap pemanggilan rekursif membawa sub-rentang yang lebih kecil. - Baris
if (lo > hi) return std::nullopt;: base case. Tidak ada elemen untuk diperiksa. - Baris
return bs_rec(a, target, mid + 1, hi);: recursive case untuk setengah kanan. Kompleksitas ruang O(log n) untuk stack frame. - Kompleksitas: waktu O(log n), ruang O(log n) (rekursif) vs O(1) (iteratif).
Fungsi STL untuk Binary Search #
C++ menyediakan empat fungsi di <algorithm> yang memanfaatkan binary search:
| Fungsi | Mengembalikan | Kegunaan |
|---|---|---|
std::binary_search(f, l, v) | bool | Apakah v ada? |
std::lower_bound(f, l, v) | iterator | Elemen pertama ≥ v |
std::upper_bound(f, l, v) | iterator | Elemen pertama > v |
std::equal_range(f, l, v) | pair iterator | Rentang [lower, upper) |
Kode: Empat Fungsi STL #
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> a = {1, 3, 3, 3, 5, 7, 9};
int v = 3;
bool ada = std::binary_search(a.begin(), a.end(), v);
std::cout << "binary_search(3): " << (ada ? "ya" : "tidak") << '\n';
auto lb = std::lower_bound(a.begin(), a.end(), v);
std::cout << "lower_bound(3): indeks "
<< std::distance(a.begin(), lb) << '\n';
auto ub = std::upper_bound(a.begin(), a.end(), v);
std::cout << "upper_bound(3): indeks "
<< std::distance(a.begin(), ub) << '\n';
auto [lo, hi] = std::equal_range(a.begin(), a.end(), v);
std::cout << "equal_range(3): ["
<< std::distance(a.begin(), lo) << ", "
<< std::distance(a.begin(), hi) << ")\n";
return 0;
}Output yang Dihasilkan:
binary_search(3): ya
lower_bound(3): indeks 1
upper_bound(3): indeks 4
equal_range(3): [1, 4)- Baris
std::binary_search: hanya memberitahu ada/tidak, tanpa posisi. Implementasi di baliknya menggunakanlower_bound. - Baris
std::lower_bound: posisi elemen pertama yang≥ v. Untukv = 3pada array1 3 3 3 5 7 9, elemen pertama≥ 3ada di indeks1. - Baris
std::upper_bound: posisi elemen pertama yang> v. Untukv = 3, elemen pertama> 3(yaitu5) ada di indeks4. - Baris
std::equal_range: mengembalikan pasangan(lower_bound, upper_bound)sekaligus. Selisihhi - lo= jumlah elemen yang sama denganv(di sini 3 elemen).
Binary Search dengan Komparator Kustom #
STL menerima komparator biner opsional. Misalnya, untuk array terurut menurun atau membandingkan field tertentu pada struct.
Kode: Binary Search pada vector<string> Sortir Kustom #
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int main() {
// Array terurut menurun
std::vector<int> a = {9, 7, 5, 3, 1};
auto cmp = std::greater<int>{};
bool ada = std::binary_search(a.begin(), a.end(), 5, cmp);
std::cout << "5 ada? " << (ada ? "ya" : "tidak") << '\n';
auto lb = std::lower_bound(a.begin(), a.end(), 5, cmp);
std::cout << "lower_bound(5): indeks "
<< std::distance(a.begin(), lb) << '\n';
return 0;
}Output yang Dihasilkan:
5 ada? ya
lower_bound(5): indeks 2- Baris
auto cmp = std::greater<int>{};: komparator “lebih besar”. Karena array terurut menurun, kita harus memberi tahu STL bahwa elemen dianggap “lebih kecil” bila nilainya lebih besar. - Baris
std::binary_search(a.begin(), a.end(), 5, cmp): STL memakaicmpuntuk menentukan urutan. Hasilnya benar —5ada di indeks2. - Kompleksitas: sama O(log n), hanya komparator yang diganti.
Ringkasan #
- Binary search bekerja pada data terurut dengan kompleksitas O(log n).
- Implementasi iteratif: ruang O(1). Implementasi rekursif: ruang O(log n).
- Selalu gunakan
mid = lo + (hi - lo) / 2untuk menghindari integer overflow. - STL menyediakan
binary_search,lower_bound,upper_bound,equal_range— pilih sesuai kebutuhan. - Komparator kustom (
std::greater, lambda) memungkinkan binary search pada data terurut menurun atau berbasis fieldstruct.