Mata Kuliah: Struktur Data dan Algoritma
Kode: SDA201
SKS: 3 SKS (2 Teori + 1 Praktikum)
Pertemuan: 02
Topik: Analisis Algoritma dan Kompleksitas
Capaian Pembelajaran: Sub-CPMK-1.2 — Mampu menganalisis kompleksitas waktu dan ruang menggunakan notasi Big-O, Big-Ω, Big-Θ
Estimasi Waktu Belajar: 7 jam
Referensi Utama: Cormen Ch.2-3; Weiss Ch.2; Goodrich Ch.4
Setelah mempelajari modul ini, mahasiswa diharapkan mampu:
Algoritma adalah urutan langkah-langkah komputasi yang terdefinisi dengan baik untuk menyelesaikan suatu masalah, menerima input dan menghasilkan output yang diinginkan dalam waktu terbatas.
Dalam konteks pemrograman, algoritma adalah “resep” yang memberitahu komputer cara menyelesaikan masalah. Namun, untuk masalah yang sama, bisa ada banyak algoritma berbeda dengan performa yang sangat bervariasi.
Bayangkan Anda memiliki dua algoritma untuk mencari data dari database personel militer:
Keduanya menghasilkan hasil yang benar, tetapi perbedaan waktu sangat signifikan. Dalam operasi pertahanan, keterlambatan informasi dapat berakibat fatal.
Waktu eksekusi program dipengaruhi oleh:
Analisis algoritma fokus pada faktor ke-5, karena ini yang dapat dikontrol programmer terlepas dari hardware atau bahasa pemrograman.
| Aspek | Analisis Empiris | Analisis Teoritis |
|---|---|---|
| Metode | Mengukur waktu eksekusi aktual | Menghitung operasi dasar |
| Ketergantungan Hardware | Tinggi | Tidak ada |
| Input yang Dibutuhkan | Harus ada data aktual | Bisa untuk semua ukuran n |
| Hasil | Waktu dalam detik/milidetik | Fungsi pertumbuhan |
| Kegunaan | Benchmarking sistem tertentu | Perbandingan algoritma universal |
Pertanyaan: Jelaskan mengapa kita tidak cukup hanya mengukur waktu eksekusi aktual untuk membandingkan dua algoritma!
Penyelesaian:
Mengukur waktu eksekusi aktual memiliki beberapa kelemahan fundamental:
Ketergantungan Hardware: Algoritma yang sama akan berjalan berbeda di komputer berbeda. Algoritma A mungkin lebih cepat di komputer X tapi lebih lambat di komputer Y.
Variasi Kondisi: Waktu eksekusi dipengaruhi program lain yang berjalan, suhu CPU, penggunaan memori, dll.
Keterbatasan Input: Kita hanya bisa mengukur dengan input tertentu. Bagaimana dengan input yang belum pernah diuji?
Tidak Scalable: Hasil pengukuran untuk n=1000 tidak bisa langsung diprediksi untuk n=1.000.000.
Oleh karena itu, analisis teoritis menggunakan notasi asimptotik diperlukan untuk mendapatkan gambaran universal tentang efisiensi algoritma.
Operasi dasar adalah instruksi komputasi yang diasumsikan membutuhkan waktu konstan, terlepas dari ukuran input.
Operasi dasar meliputi:
Dalam analisis algoritma, kita menggunakan model RAM (Random Access Machine):
int findMax(int arr[], int n) {
int max = arr[0]; // 2 operasi: akses array + assignment
for (int i = 1; i < n; i++) { // 1 + (n-1) + (n-1) operasi
if (arr[i] > max) { // 2 operasi per iterasi: akses + perbandingan
max = arr[i]; // 2 operasi: akses + assignment
}
}
return max; // 1 operasi
}
Analisis detail:
max: 2 operasii = 1: 1 operasii < n: (n-1) kali perbandingani++: (n-1) kaliarr[i] dan perbandingan: 2(n-1) operasiTotal (worst case): 2 + 1 + (n-1) + (n-1) + 2(n-1) + 2(n-1) + 1 = 6n - 2 operasi
Pertanyaan: Hitung jumlah operasi dasar pada kode berikut untuk array berukuran n:
int sum = 0;
for (int i = 0; i < n; i++) {
sum = sum + arr[i];
}
Penyelesaian:
| Baris | Operasi | Jumlah Eksekusi | Total |
|---|---|---|---|
int sum = 0 |
Assignment | 1 | 1 |
int i = 0 |
Assignment | 1 | 1 |
i < n |
Perbandingan | n+1 | n+1 |
i++ |
Increment | n | n |
arr[i] |
Akses array | n | n |
sum + arr[i] |
Penjumlahan | n | n |
sum = ... |
Assignment | n | n |
Total operasi = 1 + 1 + (n+1) + n + n + n + n = 5n + 3
Pertanyaan: Analisis jumlah operasi pada nested loop berikut:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cout << i * j << " ";
}
}
Penyelesaian:
Loop luar berjalan n kali. Untuk setiap iterasi loop luar, loop dalam berjalan n kali.
Operasi dalam loop terdalam (per eksekusi):
i * j: 1 operasicout: 1 operasiTotal operasi dalam loop terdalam: n × n × 2 = 2n²
Operasi loop overhead:
Total ≈ 5n² + 3n
Untuk n besar, suku dominan adalah n².
Ketika menganalisis algoritma, kita tertarik pada bagaimana waktu eksekusi tumbuh seiring bertambahnya ukuran input n. Untuk n yang sangat besar:
Contoh: Jika T(n) = 3n² + 5n + 100
Suku n² mendominasi ~99.8% dari total!
Definisi Formal: f(n) = O(g(n)) jika terdapat konstanta positif c dan n₀ sedemikian sehingga f(n) ≤ c·g(n) untuk semua n ≥ n₀.
Interpretasi: Big-O memberikan batas atas pertumbuhan fungsi. Jika T(n) = O(n²), maka untuk input cukup besar, waktu eksekusi paling banyak tumbuh secara kuadratik.

Gambar 2.1: Visualisasi notasi Big-O — f(n) dibatasi oleh c·g(n) untuk n ≥ n₀
Definisi Formal: f(n) = Ω(g(n)) jika terdapat konstanta positif c dan n₀ sedemikian sehingga f(n) ≥ c·g(n) untuk semua n ≥ n₀.
Interpretasi: Big-Ω memberikan batas bawah. Jika T(n) = Ω(n), maka waktu eksekusi paling sedikit tumbuh secara linear.
Definisi Formal: f(n) = Θ(g(n)) jika dan hanya jika f(n) = O(g(n)) dan f(n) = Ω(g(n)).
Interpretasi: Big-Θ memberikan batas ketat. Jika T(n) = Θ(n²), maka waktu eksekusi tumbuh tepat secara kuadratik (tidak lebih cepat, tidak lebih lambat).
| Notasi | Analogi | Makna |
|---|---|---|
| O(g(n)) | ≤ | Batas atas (paling buruk) |
| Ω(g(n)) | ≥ | Batas bawah (paling baik) |
| Θ(g(n)) | = | Batas ketat (exact order) |
Pertanyaan: Buktikan bahwa 3n² + 5n + 100 = O(n²)!
Penyelesaian:
Menurut definisi Big-O, kita perlu menemukan c dan n₀ sehingga: 3n² + 5n + 100 ≤ c·n² untuk semua n ≥ n₀
Langkah 1: Untuk n ≥ 1:
Langkah 2: Substitusi: 3n² + 5n + 100 ≤ 3n² + 5n² + 100n² = 108n²
Langkah 3: Pilih c = 108 dan n₀ = 1
Verifikasi untuk n = 1:
Verifikasi untuk n = 10:
Kesimpulan: 3n² + 5n + 100 = O(n²) dengan c = 108 dan n₀ = 1. ∎
Pertanyaan: Buktikan bahwa n² ≠ O(n)!
Penyelesaian:
Kita akan membuktikan dengan kontradiksi.
Asumsi: Misalkan n² = O(n), maka ada c > 0 dan n₀ > 0 sehingga: n² ≤ c·n untuk semua n ≥ n₀
Manipulasi aljabar: n² ≤ c·n n ≤ c (untuk n > 0, bagi kedua sisi dengan n)
Kontradiksi: Ini berarti n dibatasi oleh konstanta c untuk semua n ≥ n₀. Namun, n bisa tumbuh tanpa batas!
Pilih n = c + 1:
(c+1)² = c² + 2c + 1 > c² + c = c·n
Ini kontradiksi dengan asumsi n² ≤ c·n.
Kesimpulan: n² ≠ O(n). ∎
Pertanyaan: Jika f(n) = 5n³ + 2n² + 100, tentukan notasi Big-O yang paling ketat!
Penyelesaian:
Identifikasi suku dominan: 5n³ (pangkat tertinggi)
Untuk Big-O yang ketat, kita pilih fungsi dengan pangkat sama:
f(n) = 5n³ + 2n² + 100 = O(n³)
Verifikasi:
Total: 5n³ + 2n² + 100 ≤ 107n³
Dengan c = 107 dan n₀ = 1, terbukti f(n) = O(n³). ∎
Konstanta pengali dapat diabaikan: O(c·f(n)) = O(f(n))
Contoh: O(5n) = O(n), O(100n²) = O(n²)
Alasan: Konstanta tidak mempengaruhi tingkat pertumbuhan.
Suku dengan pertumbuhan lebih lambat dapat diabaikan: O(f(n) + g(n)) = O(max(f(n), g(n)))
Contoh: O(n² + n) = O(n²), O(n³ + 1000n²) = O(n³)
Untuk dua segmen kode berurutan: T(n) = T₁(n) + T₂(n) → O(max(T₁(n), T₂(n)))
// Segmen 1: O(n)
for (int i = 0; i < n; i++) { /* ... */ }
// Segmen 2: O(n²)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) { /* ... */ }
}
// Total: O(n² + n) = O(n²)
Untuk loop bersarang: T(n) = T₁(n) × T₂(n) → O(T₁(n) × T₂(n))
// Loop luar: n kali
for (int i = 0; i < n; i++) {
// Loop dalam: n kali per iterasi luar
for (int j = 0; j < n; j++) {
/* O(1) operation */
}
}
// Total: O(n × n) = O(n²)
Pertanyaan: Sederhanakan notasi Big-O berikut: O(3n³ + 100n² + n + 5000)
Penyelesaian:
Langkah 1: Identifikasi suku dominan
Langkah 2: Terapkan aturan suku dominan O(3n³ + 100n² + n + 5000) = O(3n³)
Langkah 3: Terapkan aturan konstanta O(3n³) = O(n³)
Pertanyaan: Tentukan kompleksitas waktu dari kode berikut:
void process(int n) {
// Bagian A
for (int i = 0; i < n; i++) {
cout << i << endl;
}
// Bagian B
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cout << i + j << endl;
}
}
// Bagian C
for (int i = 0; i < 1000; i++) {
cout << "Hello" << endl;
}
}
Penyelesaian:
Bagian A: Single loop dari 0 sampai n-1
Bagian B: Nested loop, masing-masing n kali
Bagian C: Loop dengan batas konstan (1000)
Total (aturan penjumlahan): T(n) = O(n) + O(n²) + O(1) = O(max(n, n², 1)) = O(n²)
Pertanyaan: Tentukan kompleksitas dari kode berikut:
void mystery(int n) {
for (int i = 1; i < n; i = i * 2) {
for (int j = 0; j < n; j++) {
cout << i << " " << j << endl;
}
}
}
Penyelesaian:
Analisis loop luar:
Analisis loop dalam:
Total (aturan perkalian): T(n) = O(log n) × O(n) = O(n log n)
Berikut adalah kelas-kelas kompleksitas dari yang paling efisien hingga paling tidak efisien:
| Notasi | Nama | Contoh Algoritma |
|---|---|---|
| O(1) | Konstanta | Akses array dengan indeks |
| O(log n) | Logaritmik | Binary search |
| O(n) | Linear | Linear search, traversal array |
| O(n log n) | Linearitmik | Merge sort, Quick sort (avg) |
| O(n²) | Kuadratik | Bubble sort, Selection sort |
| O(n³) | Kubik | Matrix multiplication (naif) |
| O(2ⁿ) | Eksponensial | Subset generation (brute force) |
| O(n!) | Faktorial | Permutation generation |

Gambar 2.2: Perbandingan pertumbuhan berbagai kelas kompleksitas
Asumsikan komputer dapat melakukan 10⁹ operasi per detik:
| n | O(log n) | O(n) | O(n log n) | O(n²) | O(2ⁿ) |
|---|---|---|---|---|---|
| 10 | 3 ns | 10 ns | 33 ns | 100 ns | 1 μs |
| 100 | 7 ns | 100 ns | 664 ns | 10 μs | 10¹⁴ tahun |
| 1000 | 10 ns | 1 μs | 10 μs | 1 ms | ∞ |
| 10⁶ | 20 ns | 1 ms | 20 ms | 17 menit | ∞ |
| 10⁹ | 30 ns | 1 detik | 30 detik | 31 tahun | ∞ |
Catatan: Untuk n besar, algoritma eksponensial praktis tidak dapat diselesaikan!
Bayangkan operasi verifikasi identitas personel:
Pertanyaan: Algoritma A berjalan dalam O(n²) dan algoritma B dalam O(n log n). Untuk n = 1.000.000, berapa kali lebih cepat algoritma B?
Penyelesaian:
Waktu A: n² = (10⁶)² = 10¹² operasi
Waktu B: n log₂ n = 10⁶ × log₂(10⁶) = 10⁶ × 20 = 2 × 10⁷ operasi
Rasio: Waktu A / Waktu B = 10¹² / (2 × 10⁷) = 5 × 10⁴ = 50.000 kali
Algoritma B 50.000 kali lebih cepat untuk n = 1.000.000!
Pertanyaan: Sebuah algoritma memiliki waktu eksekusi T(n) = 5n² + 3n log n + 1000. Tentukan notasi Θ yang tepat!
Penyelesaian:
Untuk menentukan Θ(f(n)), kita perlu membuktikan bahwa T(n) adalah O(f(n)) dan Ω(f(n)).
Identifikasi suku dominan:
Buktikan O(n²): 5n² + 3n log n + 1000 ≤ 5n² + 3n² + n² = 9n² untuk n ≥ 1000 Dengan c = 9 dan n₀ = 1000, T(n) = O(n²) ✓
Buktikan Ω(n²): 5n² + 3n log n + 1000 ≥ 5n² untuk semua n ≥ 1 Dengan c = 5 dan n₀ = 1, T(n) = Ω(n²) ✓
Kesimpulan: T(n) = Θ(n²) ∎
Untuk input berukuran n yang sama, algoritma bisa memiliki waktu eksekusi berbeda tergantung konfigurasi input.
Worst Case T(n): Waktu maksimum untuk input berukuran n
Best Case T(n): Waktu minimum untuk input berukuran n
Average Case T(n): Waktu rata-rata untuk semua kemungkinan input berukuran n
int linearSearch(int arr[], int n, int key) {
for (int i = 0; i < n; i++) {
if (arr[i] == key) {
return i; // Ditemukan
}
}
return -1; // Tidak ditemukan
}
Analisis:
| Case | Kondisi | Jumlah Perbandingan | Kompleksitas |
|---|---|---|---|
| Best | key di posisi pertama | 1 | O(1) |
| Worst | key tidak ada atau di akhir | n | O(n) |
| Average | key di posisi acak | n/2 | O(n) |
Pada insertion sort (akan dibahas di Pertemuan 9), kompleksitasnya:
| Case | Kondisi | Kompleksitas |
|---|---|---|
| Best | Array sudah terurut | O(n) |
| Worst | Array terurut terbalik | O(n²) |
| Average | Array acak | O(n²) |
Dalam praktik, worst case paling sering digunakan karena memberikan garansi performa. Kita bisa yakin algoritma tidak akan lebih lambat dari batas ini.
Average case berguna untuk algoritma yang worst case-nya jarang terjadi (seperti Quick Sort).
Pertanyaan: Analisis best case, worst case, dan average case dari algoritma berikut:
bool hasDuplicate(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (arr[i] == arr[j]) {
return true;
}
}
}
return false;
}
Penyelesaian:
Best Case:
Worst Case:
Average Case:
Pertanyaan: Sebuah algoritma memiliki:
Jika dalam 1000 kali eksekusi, 990 kali mencapai best case dan 10 kali mencapai worst case, tentukan average case!
Penyelesaian:
Pendekatan: Average case adalah weighted average dari semua kemungkinan.
Misalkan:
Average case: T_avg(n) = p × T_best(n) + q × T_worst(n) T_avg(n) = 0.99 × c₁·n + 0.01 × c₂·n²
Analisis asimptotik: Untuk n kecil, suku c₁·n mungkin dominan. Untuk n besar, suku 0.01 × c₂·n² akan mendominasi.
Secara asimptotik, T_avg(n) = O(n²) karena suku n² tetap ada (meskipun dengan koefisien kecil).
Namun, dalam praktik untuk n yang tidak terlalu besar, algoritma ini akan terasa cepat karena 99% kasus mencapai O(n).
// Pola 1: Loop linear
for (int i = 0; i < n; i++) { ... } // O(n)
// Pola 2: Loop dengan increment konstan
for (int i = 0; i < n; i += 2) { ... } // O(n/2) = O(n)
// Pola 3: Loop dengan pengali
for (int i = 1; i < n; i *= 2) { ... } // O(log n)
// Pola 4: Loop dengan pembagi
for (int i = n; i > 0; i /= 2) { ... } // O(log n)
// Pola 1: Nested linear
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) { ... } // O(n²)
}
// Pola 2: Loop dalam bergantung pada i
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) { ... } // O(n²/2) = O(n²)
}
// Pola 3: Loop luar logaritmik
for (int i = 1; i < n; i *= 2) {
for (int j = 0; j < n; j++) { ... } // O(n log n)
}
Untuk loop dalam yang bergantung pada loop luar:
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
// operasi O(1)
}
}
Hitung total iterasi loop dalam:
Total: 0 + 1 + 2 + … + (n-1) = n(n-1)/2 = O(n²)
Pertanyaan: Tentukan kompleksitas waktu dari kode berikut:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j += i) {
cout << i << " " << j << endl;
}
}
Penyelesaian:
Analisis loop dalam untuk setiap nilai i:
Total iterasi: T(n) = n + n/2 + n/3 + … + n/n = n(1 + 1/2 + 1/3 + … + 1/n)
Deret 1 + 1/2 + 1/3 + … + 1/n adalah deret harmonik Hₙ.
Diketahui: Hₙ = Θ(log n)
Kesimpulan: T(n) = n × Θ(log n) = Θ(n log n) ∎
Pertanyaan: Analisis kompleksitas kode berikut:
void complex(int n) {
for (int i = 1; i <= n; i *= 2) {
for (int j = 1; j <= n; j *= 2) {
cout << i * j << endl;
}
}
}
Penyelesaian:
Loop luar:
Loop dalam:
Total: T(n) = log₂(n) × log₂(n) = (log₂n)² = O(log² n) atau O((log n)²)
Untuk algoritma rekursif, kompleksitas dinyatakan dalam relasi rekurens:
void printN(int n) {
if (n <= 0) return; // Base case
cout << n << endl; // O(1)
printN(n - 1); // T(n-1)
}
Relasi rekurens:
Langkah-langkah:
Contoh: Selesaikan T(n) = T(n-1) + 1, T(0) = 1
T(n) = T(n-1) + 1 T(n) = [T(n-2) + 1] + 1 = T(n-2) + 2 T(n) = [T(n-3) + 1] + 2 = T(n-3) + 3 … T(n) = T(n-k) + k
Ketika k = n: T(n) = T(0) + n = 1 + n = O(n)
long factorial(int n) {
if (n <= 1) return 1; // T(1) = O(1)
return n * factorial(n-1); // T(n) = T(n-1) + O(1)
}
Relasi rekurens: T(n) = T(n-1) + c, T(1) = c
Solusi: T(n) = c·n = O(n)
long fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2); // Dua pemanggilan rekursif!
}
Relasi rekurens: T(n) = T(n-1) + T(n-2) + c, T(0) = T(1) = c
Solusi (dapat dibuktikan): T(n) ≈ O(2ⁿ)
Ini menunjukkan implementasi rekursif naif Fibonacci sangat tidak efisien!

Gambar 2.3: Pohon rekursi Fibonacci — banyak perhitungan redundan
Pertanyaan: Selesaikan relasi rekurens: T(n) = 2T(n/2) + n, dengan T(1) = 1
Penyelesaian menggunakan metode iterasi:
Langkah 1: Ekspansi T(n) = 2T(n/2) + n = 2[2T(n/4) + n/2] + n = 4T(n/4) + n + n = 4T(n/4) + 2n
Langkah 2: Lanjutkan ekspansi = 4[2T(n/8) + n/4] + 2n = 8T(n/8) + n + 2n = 8T(n/8) + 3n
Langkah 3: Identifikasi pola Setelah k langkah: T(n) = 2^k · T(n/2^k) + k·n
Langkah 4: Base case tercapai ketika n/2^k = 1, yaitu k = log₂(n)
T(n) = 2^(log n) · T(1) + log(n) · n = n · 1 + n log n = n + n log n = O(n log n)
Catatan: Ini adalah kompleksitas Merge Sort yang akan dibahas di Pertemuan 10!
Pertanyaan: Tentukan kompleksitas dari fungsi rekursif berikut:
int mystery(int n) {
if (n <= 1) return 1;
return mystery(n/2) + mystery(n/2) + n;
}
Penyelesaian:
Identifikasi relasi rekurens:
Ini sama dengan Solved Problem 16!
Menggunakan hasil sebelumnya: T(n) = O(n log n)
Verifikasi dengan pohon rekursi:
Total kerja = n × (log n + 1) = O(n log n) ✓
Pertanyaan: Analisis kompleksitas Binary Search rekursif:
int binarySearch(int arr[], int low, int high, int key) {
if (low > high) return -1;
int mid = low + (high - low) / 2;
if (arr[mid] == key) return mid;
else if (arr[mid] > key)
return binarySearch(arr, low, mid - 1, key);
else
return binarySearch(arr, mid + 1, high, key);
}
Penyelesaian:
Relasi rekurens:
T(n) = T(n/2) + c, dengan T(1) = c
Metode iterasi: T(n) = T(n/2) + c = T(n/4) + c + c = T(n/4) + 2c = T(n/8) + 3c … = T(n/2^k) + k·c
Ketika n/2^k = 1 → k = log₂(n): T(n) = T(1) + log₂(n) · c = c + c·log₂(n) = O(log n)
Binary Search sangat efisien karena kompleksitasnya logaritmik!
Banyak algoritma divide-and-conquer memiliki relasi rekurens berbentuk:
T(n) = aT(n/b) + f(n)
Dimana:
Untuk T(n) = aT(n/b) + n^d dimana a ≥ 1, b > 1, d ≥ 0:
| Kondisi | Kompleksitas |
|---|---|
| a < b^d | O(n^d) |
| a = b^d | O(n^d log n) |
| a > b^d | O(n^(log_b a)) |
Contoh 1: T(n) = 2T(n/2) + n
Contoh 2: T(n) = T(n/2) + 1
Contoh 3: T(n) = 4T(n/2) + n
Pertanyaan: Gunakan Master Theorem untuk menyelesaikan: T(n) = 3T(n/2) + n
Penyelesaian:
Identifikasi parameter:
Hitung b^d: b^d = 2^1 = 2
Bandingkan a dan b^d: a = 3 > 2 = b^d
Terapkan kasus ketiga Master Theorem: T(n) = O(n^(log_b a)) = O(n^(log_2 3)) ≈ O(n^1.585)
Kesimpulan: T(n) = O(n^(log₂3)) ≈ O(n^1.585)
Pertanyaan: Dalam sistem radar pertahanan, algoritma deteksi sasaran memiliki kompleksitas T(n) = 8T(n/2) + n² untuk memproses n titik data. Tentukan kompleksitasnya dan jelaskan implikasinya!
Penyelesaian:
Master Theorem:
Kasus ketiga: T(n) = O(n^(log_2 8)) = O(n^3) = O(n³)
Implikasi praktis: Algoritma ini tidak scalable untuk data besar. Untuk 1000 titik data, diperlukan ~10⁹ operasi. Untuk sistem pertahanan real-time, perlu dipertimbangkan:
Amortized analysis menganalisis waktu rata-rata per operasi dalam sekuens operasi, bukan worst case satu operasi.
Ini berguna ketika operasi mahal jarang terjadi.
Pada dynamic array (seperti std::vector di C++), operasi push_back:
Meskipun worst case adalah O(n), amortized cost adalah O(1) karena resize jarang terjadi.
Bayangkan resize terjadi ketika kapasitas penuh, dan kapasitas digandakan:
Total cost untuk n insert ≈ n + n/2 + n/4 + … = O(n) Amortized cost per insert = O(n)/n = O(1)
Pertanyaan: Jelaskan mengapa operasi push_back pada std::vector C++ memiliki amortized complexity O(1) meskipun kadang memerlukan O(n)!
Penyelesaian:
Mekanisme vector:
size (jumlah elemen) dan capacity (ruang dialokasi)Analisis accounting method:
Ketika resize diperlukan setelah n insert:
Kesimpulan: Meskipun satu operasi bisa O(n), total biaya n operasi adalah O(n), sehingga biaya amortized per operasi adalah O(1).
Pertanyaan: Sebuah stack diimplementasikan dengan operasi:
push(x): O(1)pop(): O(1)multiPop(k): pop k elemen, O(min(k, size))Jika dilakukan n operasi campuran, apa amortized cost per operasi?
Penyelesaian:
Analisis potential method:
Definisikan potential function Φ = jumlah elemen di stack.
Amortized cost:
Total amortized cost untuk n operasi:
Meskipun satu multiPop bisa memakan O(n) waktu, amortized cost tetap O(1) karena elemen harus di-push dulu sebelum bisa di-pop!
| Topik | Poin Kunci |
|---|---|
| Analisis Algoritma | Mengevaluasi efisiensi berdasarkan pertumbuhan waktu terhadap ukuran input |
| Big-O | Batas atas (upper bound), f(n) ≤ c·g(n) |
| Big-Ω | Batas bawah (lower bound), f(n) ≥ c·g(n) |
| Big-Θ | Batas ketat (tight bound), Big-O dan Big-Ω sama |
| Aturan Sum | O(f + g) = O(max(f, g)) |
| Aturan Product | O(f × g) = O(f) × O(g) |
| Best/Worst/Avg | Tergantung konfigurasi input; worst case untuk garansi |
| Loop Tunggal | O(n) untuk linear, O(log n) untuk multiplicative |
| Nested Loop | Kalikan kompleksitas tiap level |
| Rekursi | Buat relasi rekurens, selesaikan dengan substitusi |
| Master Theorem | T(n) = aT(n/b) + n^d, bandingkan a dengan b^d |
| Amortized | Rata-rata per operasi dalam sekuens |
| Kompleksitas | Nama | Efisiensi |
|---|---|---|
| O(1) | Konstanta | ⭐⭐⭐⭐⭐ |
| O(log n) | Logaritmik | ⭐⭐⭐⭐ |
| O(n) | Linear | ⭐⭐⭐ |
| O(n log n) | Linearitmik | ⭐⭐⭐ |
| O(n²) | Kuadratik | ⭐⭐ |
| O(2ⁿ) | Eksponensial | ⭐ |
| O(n!) | Faktorial | ☆ |
Tentukan notasi Big-O untuk: f(n) = 100 + 200log(n) + 50n
Jawaban: O(n). Suku 50n mendominasi untuk n besar.
Berapa jumlah operasi pada kode berikut?
for (int i = n; i > 0; i /= 3) {
for (int j = 0; j < i; j++) {
x++;
}
}
Jawaban: O(n). Loop dalam total: n + n/3 + n/9 + … ≈ 1.5n = O(n)
Selesaikan: T(n) = T(n-1) + n, T(1) = 1
Jawaban: T(n) = n + (n-1) + … + 1 = n(n+1)/2 = O(n²)
Gunakan Master Theorem untuk: T(n) = 2T(n/4) + √n
Jawaban: a=2, b=4, d=0.5. b^d = 4^0.5 = 2 = a. Jadi O(√n log n).
Algoritma A: O(n²), Algoritma B: O(n log n). Untuk input kecil (n < 100), A lebih cepat karena konstanta lebih kecil. Kapan sebaiknya switch ke B?
Jawaban: Ketika T_A(n) > T_B(n). Jika T_A(n) = c₁n² dan T_B(n) = c₂n log n, switch ketika n > c₂/c₁ × log n. Perlu diukur secara empiris untuk c₁ dan c₂ spesifik.
This repository is licensed under the Creative Commons Attribution 4.0 International (CC BY 4.0). Commercial use is permitted, provided attribution is given to the author.
© 2026 Anindito