Brute force sering dianggap solusi “pemalas”, tetapi sebenarnya ini adalah senjata paling penting dalam toolbox problem-solver. Brute force memberi baseline yang pasti benar, dapat dipakai untuk verifikasi solusi cepat, dan kadang memang cukup — soal dengan n ≤ 10 atau n ≤ 20 sering dapat diselesaikan dengan enumerasi penuh. Pada Chapter ini kita akan mempelajari dua pola brute force paling umum: enumerasi subset (semua himpunan bagian) dan enumerasi permutasi (semua urutan). Kita juga akan melihat kapan berhenti pada brute force dan kapan harus lanjut ke solusi optimal.
Kapan Brute Force Cukup #
Aturan: Lihat Constraint #
Brute force layak dipakai ketika n kecil. Berikut pedoman praktis:
| Kompleksitas Brute | Batas n yang Masih Aman | Contoh Soal |
|---|---|---|
| O(n!) | n ≤ 10 | TSP, permutasi penuh |
| O(2^n) | n ≤ 20 | Subset sum, knapsack kecil |
| O(n^4) | n ≤ 50 | Perhitungan matriks kecil |
| O(n^3) | n ≤ 200 | 3-SUM, perhitungan triple |
Mekanisme: Mengapa Brute Force Sering Cukup #
Banyak soal “preliminary round” di kompetisi sengaja memuat subtask dengan n kecil agar brute force dapat skor parsial. Bahkan bila total soal butuh optimal, memulai dari brute force memberi kerangka kerja yang benar yang dapat kita optimasi secara bertahap.
Pola 1: Enumerasi Subset dengan Bitmask #
Aturan #
Diberikan n item, kita ingin mencoba semua 2^n himpunan bagian. Setiap himpunan bagian direpresentasikan sebagai bitmask integer 0..2^n - 1. Bit ke-i bernilai 1 berarti item ke-i termasuk subset.
Kode: Subset Sum via Bitmask #
#include <iostream>
#include <vector>
bool subset_sum_brute(const std::vector<int>& a, int target) {
int n = static_cast<int>(a.size());
for (int mask = 0; mask < (1 << n); ++mask) {
long long sum = 0;
for (int i = 0; i < n; ++i) {
if (mask & (1 << i)) sum += a[i];
}
if (sum == target) return true;
}
return false;
}
int main() {
std::vector<int> a = {3, 7, 11, 14, 19};
for (int t : {10, 21, 25, 30, 100}) {
std::cout << "subset_sum target=" << t << ": "
<< (subset_sum_brute(a, t) ? "ADA" : "TIDAK") << '\n';
}
return 0;
}Output yang Dihasilkan:
subset_sum target=10: ADA
subset_sum target=21: ADA
subset_sum target=25: ADA
subset_sum target=30: ADA
subset_sum target=100: TIDAK- Baris
for (int mask = 0; mask < (1 << n); ++mask): iterasi semua2^nkemungkinan bitmask.1 << nadalah 2 pangkat n. - Baris
if (mask & (1 << i)): cek apakah bit ke-imenyala dimask. Bila ya, item ke-imasuk subset. - Baris
if (sum == target) return true;: berhenti lebih awal saat ditemukan. Optimasi praktis tanpa mengubah kompleksitas terburuk. - Hasil
10 → ADA: subset{3, 7}= 10. Sesuai. - Hasil
25 → ADA: subset{11, 14}= 25. Sesuai. - Hasil
30 → ADA: subset{11, 19}= 30. Sesuai.
Pola 2: Enumerasi Permutasi #
Aturan #
Untuk masalah yang sensitif terhadap urutan (misalnya TSP, job scheduling), kita harus mencoba semua n! urutan. C++ menyediakan std::next_permutation yang menghasilkan permutasi berikutnya dalam urutan leksikografis.
Kode: TSP Mini dengan Permutasi #
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
int tsp_brute(const std::vector<std::vector<int>>& dist) {
int n = static_cast<int>(dist.size());
std::vector<int> order(n);
for (int i = 0; i < n; ++i) order[i] = i;
int best = INT_MAX;
do {
int total = 0;
for (int i = 0; i + 1 < n; ++i) total += dist[order[i]][order[i+1]];
best = std::min(best, total);
} while (std::next_permutation(order.begin(), order.end()));
return best;
}
int main() {
std::vector<std::vector<int>> dist = {
{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0}
};
std::cout << "TSP minimum = " << tsp_brute(dist) << '\n';
return 0;
}Output yang Dihasilkan:
TSP minimum = 50- Baris
for (int i = 0; i < n; ++i) order[i] = i;: inisialisasi urutan pertama.std::next_permutationbutuh array sorted sebagai titik mulai. - Baris
do { ... } while (std::next_permutation(...)): gunakando-whileagar permutasi awal juga diproses.std::next_permutationmengembalikanfalsesaat kembali ke urutan awal. - Baris
for (int i = 0; i + 1 < n; ++i) total += dist[order[i]][order[i+1]];: jumlahkan jarak antar kota berurutan dalam permutasi saat ini. - Baris
best = std::min(best, total);: simpan biaya minimum lintas semua permutasi. - Hasil
50: rute2 → 0 → 1 → 3=dist[2][0] + dist[0][1] + dist[1][3]= 15 + 10 + 25 = 50. Brute force tidak mengasumsikan titik mulai tertentu, sehingga menemukan rute mulai dari kota mana pun.
Pola 3: Brute Force dengan Pruning (Cepat) #
Aturan #
Kita dapat menambahkan pruning sederhana untuk memotong cabang pencarian yang pasti tidak menghasilkan jawaban lebih baik. Ini adalah jembatan menuju backtracking yang lebih sistematis.
Kode: Subset Sum dengan Pruning Awal #
#include <iostream>
#include <vector>
#include <algorithm>
bool subset_with_prune(const std::vector<int>& a, int target) {
std::vector<int> sorted_a = a;
std::sort(sorted_a.rbegin(), sorted_a.rend()); // descending
long long total = 0;
for (int x : sorted_a) total += x;
if (total < target) return false; // pruning global
// DFS dengan pruning
std::function<bool(int, long long)> dfs = [&](int i, long long cur) -> bool {
if (cur == target) return true;
if (cur > target) return false; // pruning: sudah lewat
if (i == (int)sorted_a.size()) return false;
// Coba ambil atau skip
if (dfs(i + 1, cur + sorted_a[i])) return true;
if (dfs(i + 1, cur)) return true;
return false;
};
return dfs(0, 0);
}
int main() {
std::vector<int> a = {3, 7, 11, 14, 19};
for (int t : {10, 21, 25, 30, 100}) {
std::cout << "prune target=" << t << ": "
<< (subset_with_prune(a, t) ? "ADA" : "TIDAK") << '\n';
}
return 0;
}Output yang Dihasilkan:
prune target=10: ADA
prune target=21: ADA
prune target=25: ADA
prune target=30: ADA
prune target=100: TIDAK- Baris
std::sort(sorted_a.rbegin(), sorted_a.rend());: urutkan menurun. Tujuannya: nilai besar dicoba duluan, sehingga pruningcur > targetlebih sering memotong. - Baris
if (total < target) return false;: pruning global — bila jumlah semua elemen kurang dari target, tidak mungkin ada subset yang cocok. - Baris
if (cur > target) return false;: pruning lokal — bila jumlah sementara sudah melebihi target (semua nilai non-negatif), cabang ini pasti gagal. - Baris
if (dfs(i + 1, cur + sorted_a[i])) return true;: rekursi untuk “ambil item ke-i”. - Baris
if (dfs(i + 1, cur)) return true;: rekursi untuk “skip item ke-i”. - Hasil identik dengan bitmask: verifikasi bahwa pruning tidak mengubah jawaban, hanya mempercepat.
Ringkasan #
- Brute force layak dipakai saat
nkecil:n ≤ 10untukO(n!),n ≤ 20untukO(2^n). - Enumerasi subset paling alami menggunakan bitmask
0..2^n-1. Cek bit ke-idenganmask & (1 << i). - Enumerasi permutasi paling mudah memakai
std::next_permutationdengando-while. - Tambahkan pruning sederhana (sort menurun + cek batas) untuk memotong cabang gagal lebih awal.
- Brute force bukan kegagalan — ini baseline yang pasti benar dan alat verifikasi untuk solusi cepat.