Mata Kuliah: Struktur Data dan Algoritma
Kode: SDA201
SKS: 3 SKS (2 Teori + 1 Praktikum)
Pertemuan: 07
Topik: Rekursi Lanjut dan Divide-and-Conquer
Capaian Pembelajaran: Sub-CPMK-3.1, Sub-CPMK-3.2 — Mampu menganalisis dan mengoptimalkan algoritma rekursif serta menerapkan paradigma divide-and-conquer dalam penyelesaian masalah
Estimasi Waktu Belajar: 7 jam
Referensi Utama: Cormen Ch.4; Weiss Ch.1.3; Hubbard Ch.4
Setelah menyelesaikan modul ini, mahasiswa diharapkan mampu:
Definisi: Rekursi adalah teknik pemrograman di mana sebuah fungsi memanggil dirinya sendiri untuk menyelesaikan masalah dengan memecahnya menjadi sub-masalah yang lebih kecil.
Setiap fungsi rekursif memiliki dua komponen penting:
| Komponen | Deskripsi | Fungsi |
|---|---|---|
| Base Case | Kondisi penghenti rekursi | Mencegah pemanggilan tak terbatas |
| Recursive Case | Pemanggilan fungsi ke dirinya sendiri | Memecah masalah menjadi lebih kecil |
returnType fungsiRekursif(parameter) {
// Base case: kondisi berhenti
if (kondisiBerhenti) {
return nilaiDasar;
}
// Recursive case: pemanggilan diri sendiri
return fungsiRekursif(parameterYangLebihKecil);
}
#include <iostream>
using namespace std;
// Fungsi faktorial rekursif
int faktorial(int n) {
// Base case
if (n <= 1) {
return 1;
}
// Recursive case
return n * faktorial(n - 1);
}
int main() {
cout << "5! = " << faktorial(5) << endl; // Output: 120
return 0;
}
Tracing adalah teknik untuk memahami alur eksekusi fungsi rekursif dengan melacak setiap pemanggilan.

Gambar 1.1: Visualisasi tracing rekursi faktorial(5)
Tabel Tracing faktorial(5):
| Call | n | Kondisi | Aksi | Return |
|---|---|---|---|---|
| faktorial(5) | 5 | n > 1 | 5 * faktorial(4) | 5 * 24 = 120 |
| faktorial(4) | 4 | n > 1 | 4 * faktorial(3) | 4 * 6 = 24 |
| faktorial(3) | 3 | n > 1 | 3 * faktorial(2) | 3 * 2 = 6 |
| faktorial(2) | 2 | n > 1 | 2 * faktorial(1) | 2 * 1 = 2 |
| faktorial(1) | 1 | n ≤ 1 | Base case | 1 |
Soal: Tuliskan fungsi rekursif untuk menghitung jumlah digit dari sebuah bilangan positif.
Penyelesaian:
#include <iostream>
using namespace std;
int jumlahDigit(int n) {
// Base case: satu digit tersisa
if (n < 10) {
return n;
}
// Recursive case: digit terakhir + jumlah digit sisanya
return (n % 10) + jumlahDigit(n / 10);
}
int main() {
cout << "Jumlah digit 12345 = " << jumlahDigit(12345) << endl;
// Output: 15 (1+2+3+4+5)
return 0;
}
Analisis:
Soal: Implementasikan fungsi rekursif untuk menghitung pangkat a^n.
Penyelesaian:
#include <iostream>
using namespace std;
// Versi sederhana: O(n)
int pangkat(int a, int n) {
if (n == 0) return 1; // Base case
return a * pangkat(a, n - 1); // Recursive case
}
// Versi efisien menggunakan divide-and-conquer: O(log n)
int pangkatCepat(int a, int n) {
if (n == 0) return 1;
if (n % 2 == 0) {
int half = pangkatCepat(a, n / 2);
return half * half;
} else {
return a * pangkatCepat(a, n - 1);
}
}
int main() {
cout << "2^10 = " << pangkat(2, 10) << endl; // 1024
cout << "2^10 = " << pangkatCepat(2, 10) << endl; // 1024
return 0;
}
Definisi: Recurrence relation adalah persamaan yang mendefinisikan kompleksitas waktu fungsi rekursif dalam bentuk kompleksitas untuk input yang lebih kecil.
T(n) = a·T(n/b) + f(n)
Di mana:
T(n) = waktu untuk menyelesaikan masalah berukuran na = jumlah subproblemn/b = ukuran setiap subproblemf(n) = waktu untuk membagi dan menggabungkanLangkah-langkah:
Contoh: Faktorial

Gambar 2.1: Pohon rekursi untuk T(n) = 2T(n/2) + n
Soal: Tentukan kompleksitas waktu dari recurrence relation: T(n) = 2T(n/2) + n menggunakan metode pohon rekursi.
Penyelesaian:
Langkah 1: Gambar pohon rekursi
Langkah 2: Hitung jumlah level
Langkah 3: Hitung total cost
Master Theorem: Untuk recurrence relation berbentuk T(n) = aT(n/b) + f(n), di mana a ≥ 1 dan b > 1:
| Kasus | Kondisi | Kompleksitas |
|---|---|---|
| Kasus 1 | f(n) = O(n^(log_b(a) - ε)) untuk ε > 0 | T(n) = Θ(n^(log_b(a))) |
| Kasus 2 | f(n) = Θ(n^(log_b(a))) | T(n) = Θ(n^(log_b(a)) · log n) |
| Kasus 3 | f(n) = Ω(n^(log_b(a) + ε)) untuk ε > 0 | T(n) = Θ(f(n)) |
Langkah-langkah:

Gambar 3.1: Flowchart penggunaan Master Theorem
Soal: Gunakan Master Theorem untuk menentukan kompleksitas T(n) = 4T(n/2) + n.
Penyelesaian:
Langkah 1: Identifikasi parameter
Langkah 2: Hitung log_b(a)
Langkah 3: Bandingkan f(n) dengan n²
Langkah 4: Kasus 1 berlaku
Soal: Tentukan kompleksitas T(n) = 2T(n/2) + n menggunakan Master Theorem.
Penyelesaian:
Soal: Tentukan kompleksitas T(n) = 3T(n/4) + n log n.
Penyelesaian:
Langkah 1: Identifikasi
Langkah 2: Hitung log_b(a)
Langkah 3: Bandingkan
Langkah 4: Kasus 3 berlaku
Definisi: Tail recursion adalah bentuk rekursi di mana pemanggilan rekursif adalah operasi terakhir yang dilakukan oleh fungsi sebelum mengembalikan nilai.
Regular Recursion (Non-tail):
int faktorial(int n) {
if (n <= 1) return 1;
return n * faktorial(n - 1); // Operasi * setelah rekursi
}
Tail Recursion:
int faktorialTail(int n, int accumulator = 1) {
if (n <= 1) return accumulator;
return faktorialTail(n - 1, n * accumulator); // Rekursi terakhir
}
| Aspek | Regular Recursion | Tail Recursion |
|---|---|---|
| Stack Frame | Menumpuk | Dapat di-reuse |
| Memory | O(n) | O(1) dengan TCO |
| Stack Overflow | Mungkin | Dihindari |
| Compiler Optimization | Terbatas | TCO applicable |
Catatan: TCO (Tail Call Optimization) adalah optimasi compiler yang mengubah tail recursion menjadi loop, menghindari penumpukan stack frame.

Gambar 4.1: Perbandingan stack usage antara regular dan tail recursion
Soal: Konversi fungsi sum array rekursif menjadi tail recursive.
Penyelesaian:
Versi Non-tail:
int sumArray(int arr[], int n) {
if (n <= 0) return 0;
return arr[n-1] + sumArray(arr, n-1);
}
Versi Tail Recursive:
int sumArrayTail(int arr[], int n, int accumulator = 0) {
if (n <= 0) return accumulator;
return sumArrayTail(arr, n - 1, accumulator + arr[n-1]);
}
// Penggunaan
int main() {
int data[] = {1, 2, 3, 4, 5};
cout << "Sum = " << sumArrayTail(data, 5) << endl; // Output: 15
return 0;
}
Penjelasan:
accumulator menyimpan hasil perhitungan parsialSoal: Implementasikan fungsi Fibonacci dengan tail recursion.
Penyelesaian:
#include <iostream>
using namespace std;
// Versi non-tail: O(2^n) waktu, O(n) space
int fibNonTail(int n) {
if (n <= 1) return n;
return fibNonTail(n-1) + fibNonTail(n-2);
}
// Versi tail recursive: O(n) waktu, O(1) space dengan TCO
int fibTailHelper(int n, int a = 0, int b = 1) {
if (n == 0) return a;
if (n == 1) return b;
return fibTailHelper(n - 1, b, a + b);
}
int fibTail(int n) {
return fibTailHelper(n);
}
int main() {
cout << "Fib(10) Non-tail: " << fibNonTail(10) << endl; // 55
cout << "Fib(10) Tail: " << fibTail(10) << endl; // 55
return 0;
}
Tracing fibTailHelper(5, 0, 1):
| Call | n | a | b | Action |
|---|---|---|---|---|
| 1 | 5 | 0 | 1 | Continue |
| 2 | 4 | 1 | 1 | Continue |
| 3 | 3 | 1 | 2 | Continue |
| 4 | 2 | 2 | 3 | Continue |
| 5 | 1 | 3 | 5 | Return b = 5 |
Definisi: Memoization adalah teknik optimasi yang menyimpan hasil komputasi yang sudah dilakukan untuk menghindari perhitungan berulang.
// O(2^n) - sangat lambat!
int fibNaive(int n) {
if (n <= 1) return n;
return fibNaive(n-1) + fibNaive(n-2);
}
Permasalahan: Perhitungan berulang yang sama!

Gambar 5.1: Overlapping subproblems pada Fibonacci rekursif
#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map<int, long long> memo;
// O(n) waktu, O(n) space
long long fibMemo(int n) {
// Base case
if (n <= 1) return n;
// Cek apakah sudah pernah dihitung
if (memo.find(n) != memo.end()) {
return memo[n]; // Kembalikan hasil yang tersimpan
}
// Hitung dan simpan
memo[n] = fibMemo(n-1) + fibMemo(n-2);
return memo[n];
}
int main() {
cout << "Fib(50) = " << fibMemo(50) << endl; // Cepat!
return 0;
}
| Metode | Kompleksitas Waktu | Kompleksitas Ruang | Fib(40) Time |
|---|---|---|---|
| Naive Recursion | O(2^n) | O(n) | ~1 menit |
| Memoization | O(n) | O(n) | < 1 ms |
| Tail Recursion | O(n) | O(1)* | < 1 ms |
| Iteratif | O(n) | O(1) | < 1 ms |
*dengan TCO
Soal: Implementasikan fungsi untuk menghitung jumlah cara naik tangga n anak tangga, di mana setiap langkah bisa naik 1 atau 2 anak tangga, menggunakan memoization.
Penyelesaian:
#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map<int, long long> memo;
// Jumlah cara naik n anak tangga
long long climbStairs(int n) {
// Base cases
if (n <= 0) return 0;
if (n == 1) return 1;
if (n == 2) return 2;
// Check memo
if (memo.find(n) != memo.end()) {
return memo[n];
}
// Rekursi dengan memoization
memo[n] = climbStairs(n-1) + climbStairs(n-2);
return memo[n];
}
int main() {
cout << "Cara naik 5 tangga: " << climbStairs(5) << endl; // 8
cout << "Cara naik 10 tangga: " << climbStairs(10) << endl; // 89
return 0;
}
Analisis:
Soal: Dalam sistem pertahanan, perlu dihitung jumlah rute berbeda dari pojok kiri atas ke pojok kanan bawah pada grid m×n, di mana hanya bisa bergerak ke kanan atau ke bawah. Implementasikan dengan memoization.
Penyelesaian:
#include <iostream>
#include <map>
using namespace std;
map<pair<int,int>, long long> memo;
// Menghitung jumlah rute unik pada grid m x n
long long uniquePaths(int m, int n) {
// Base cases
if (m == 1 || n == 1) return 1;
if (m == 0 || n == 0) return 0;
// Check memo
pair<int,int> key = {m, n};
if (memo.find(key) != memo.end()) {
return memo[key];
}
// Jumlah rute = rute dari atas + rute dari kiri
memo[key] = uniquePaths(m-1, n) + uniquePaths(m, n-1);
return memo[key];
}
int main() {
// Contoh: Grid patroli 3x7
cout << "Jumlah rute 3x7: " << uniquePaths(3, 7) << endl; // 28
cout << "Jumlah rute 10x10: " << uniquePaths(10, 10) << endl; // 48620
return 0;
}
Aplikasi Pertahanan:
Definisi: Divide-and-conquer adalah paradigma algoritma yang memecah masalah menjadi sub-masalah yang lebih kecil, menyelesaikan setiap sub-masalah secara rekursif, lalu menggabungkan solusinya.
Tiga Langkah Divide-and-Conquer:
| Langkah | Deskripsi | Contoh (Merge Sort) |
|---|---|---|
| Divide | Bagi masalah menjadi sub-masalah | Bagi array menjadi 2 bagian |
| Conquer | Selesaikan sub-masalah secara rekursif | Sort setiap bagian |
| Combine | Gabungkan solusi sub-masalah | Merge 2 array terurut |

Gambar 6.1: Paradigma Divide-and-Conquer
| Algoritma | Divide | Conquer | Combine | Kompleksitas |
|---|---|---|---|---|
| Binary Search | Bagi 2 | Search di separuh | - | O(log n) |
| Merge Sort | Bagi 2 | Sort keduanya | Merge | O(n log n) |
| Quick Sort | Partition | Sort keduanya | - | O(n log n)* |
| Tower of Hanoi | n-1 disk | Pindah 1 disk | n-1 disk | O(2^n) |
*average case
Definisi: Binary search adalah algoritma pencarian pada array terurut yang membagi search space menjadi setengah pada setiap iterasi.
Prasyarat: Array harus sudah terurut (sorted)!
#include <iostream>
using namespace std;
int binarySearch(int arr[], int left, int right, int target) {
// Base case: tidak ditemukan
if (left > right) return -1;
// Divide: hitung titik tengah
int mid = left + (right - left) / 2; // Hindari overflow
// Conquer
if (arr[mid] == target) {
return mid; // Ditemukan!
}
else if (target < arr[mid]) {
return binarySearch(arr, left, mid - 1, target); // Cari di kiri
}
else {
return binarySearch(arr, mid + 1, right, target); // Cari di kanan
}
}
int main() {
int data[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
int n = 10;
int target = 23;
int result = binarySearch(data, 0, n-1, target);
if (result != -1) {
cout << "Ditemukan di index " << result << endl;
} else {
cout << "Tidak ditemukan" << endl;
}
return 0;
}
Recurrence Relation: T(n) = T(n/2) + O(1)
Menggunakan Master Theorem:
Soal: Implementasikan binary search iteratif.
Penyelesaian:
int binarySearchIterative(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
}
else if (target < arr[mid]) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return -1; // Tidak ditemukan
}
Soal: Implementasikan fungsi untuk mencari posisi pertama dan terakhir dari elemen target dalam array terurut menggunakan binary search.
Penyelesaian:
#include <iostream>
using namespace std;
// Cari posisi pertama (leftmost)
int findFirst(int arr[], int n, int target) {
int left = 0, right = n - 1, result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
result = mid;
right = mid - 1; // Terus cari ke kiri
}
else if (target < arr[mid]) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return result;
}
// Cari posisi terakhir (rightmost)
int findLast(int arr[], int n, int target) {
int left = 0, right = n - 1, result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
result = mid;
left = mid + 1; // Terus cari ke kanan
}
else if (target < arr[mid]) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return result;
}
int main() {
int arr[] = {2, 4, 4, 4, 4, 6, 8, 10};
int n = 8;
int target = 4;
cout << "First occurrence: " << findFirst(arr, n, target) << endl; // 1
cout << "Last occurrence: " << findLast(arr, n, target) << endl; // 4
return 0;
}
Tower of Hanoi: Pindahkan n disk dari tiang sumber (A) ke tiang tujuan (C) dengan aturan:
- Hanya boleh memindahkan satu disk pada satu waktu
- Disk yang lebih besar tidak boleh diletakkan di atas disk yang lebih kecil
- Gunakan tiang bantu (B) sebagai tempat sementara

Gambar 8.1: Ilustrasi Tower of Hanoi dengan 3 disk
Strategi Divide-and-Conquer:
#include <iostream>
using namespace std;
int moveCount = 0;
void hanoi(int n, char source, char destination, char auxiliary) {
if (n == 1) {
// Base case: pindahkan disk terkecil
cout << "Pindahkan disk 1 dari " << source
<< " ke " << destination << endl;
moveCount++;
return;
}
// Langkah 1: Pindahkan n-1 disk dari source ke auxiliary
hanoi(n - 1, source, auxiliary, destination);
// Langkah 2: Pindahkan disk terbesar dari source ke destination
cout << "Pindahkan disk " << n << " dari " << source
<< " ke " << destination << endl;
moveCount++;
// Langkah 3: Pindahkan n-1 disk dari auxiliary ke destination
hanoi(n - 1, auxiliary, destination, source);
}
int main() {
int n = 3;
cout << "Tower of Hanoi dengan " << n << " disk:\n" << endl;
hanoi(n, 'A', 'C', 'B');
cout << "\nTotal langkah: " << moveCount << endl;
return 0;
}
Output untuk n=3:
Pindahkan disk 1 dari A ke C
Pindahkan disk 2 dari A ke B
Pindahkan disk 1 dari C ke B
Pindahkan disk 3 dari A ke C
Pindahkan disk 1 dari B ke A
Pindahkan disk 2 dari B ke C
Pindahkan disk 1 dari A ke C
Total langkah: 7
Recurrence Relation: T(n) = 2T(n-1) + 1
Solusi:
Kompleksitas: O(2^n) - eksponensial!
| n (disk) | Langkah | Waktu (1 langkah/detik) |
|---|---|---|
| 5 | 31 | 31 detik |
| 10 | 1.023 | ~17 menit |
| 20 | 1.048.575 | ~12 hari |
| 64* | ~1.8 × 10^19 | ~585 miliar tahun |
*Legenda: para biksu di Hanoi memindahkan 64 disk emas
Soal: Berapa langkah minimum yang diperlukan untuk menyelesaikan Tower of Hanoi dengan 5 disk?
Penyelesaian:
Rumus: Langkah = 2^n - 1
Untuk n = 5:
Soal: Modifikasi fungsi Tower of Hanoi untuk menghitung jumlah langkah tanpa mencetak setiap langkah.
Penyelesaian:
#include <iostream>
using namespace std;
long long hanoiCount(int n) {
if (n == 1) return 1;
return 2 * hanoiCount(n - 1) + 1;
}
// Versi dengan rumus langsung (lebih efisien)
long long hanoiFormula(int n) {
return (1LL << n) - 1; // 2^n - 1
}
int main() {
for (int n = 1; n <= 20; n++) {
cout << "Disk " << n << ": " << hanoiFormula(n) << " langkah" << endl;
}
return 0;
}
Soal: Dalam latihan logistik militer, ada 4 kontainer yang harus dipindahkan mengikuti aturan Tower of Hanoi. Berapa total waktu yang diperlukan jika setiap pemindahan membutuhkan 15 menit?
Penyelesaian:
#include <iostream>
using namespace std;
int main() {
int n = 4; // Jumlah kontainer
int waktuPerPindah = 15; // menit
// Hitung jumlah langkah
long long langkah = (1LL << n) - 1; // 2^4 - 1 = 15
// Hitung total waktu
long long totalMenit = langkah * waktuPerPindah;
int jam = totalMenit / 60;
int menit = totalMenit % 60;
cout << "Jumlah kontainer: " << n << endl;
cout << "Jumlah pemindahan: " << langkah << endl;
cout << "Waktu per pemindahan: " << waktuPerPindah << " menit" << endl;
cout << "Total waktu: " << jam << " jam " << menit << " menit" << endl;
return 0;
}
Output:
Jumlah kontainer: 4
Jumlah pemindahan: 15
Waktu per pemindahan: 15 menit
Total waktu: 3 jam 45 menit
| Rekursi | Iterasi dengan Stack |
|---|---|
| Kode lebih sederhana | Kode lebih kompleks |
| Stack implisit (call stack) | Stack eksplisit |
| Risiko stack overflow | Kontrol ukuran stack |
| Overhead function call | Lebih efisien |
Langkah-langkah:
#include <iostream>
#include <stack>
using namespace std;
// Versi rekursif
int faktorialRekursif(int n) {
if (n <= 1) return 1;
return n * faktorialRekursif(n - 1);
}
// Versi iteratif dengan stack eksplisit
int faktorialStack(int n) {
stack<int> s;
// Push semua nilai ke stack (simulasi rekursi turun)
while (n > 1) {
s.push(n);
n--;
}
// Pop dan kalikan (simulasi rekursi naik)
int result = 1;
while (!s.empty()) {
result *= s.top();
s.pop();
}
return result;
}
int main() {
cout << "Rekursif: 5! = " << faktorialRekursif(5) << endl;
cout << "Stack: 5! = " << faktorialStack(5) << endl;
return 0;
}
Catatan: Struktur data tree akan dibahas pada Pertemuan 11. Contoh ini diberikan sebagai preview aplikasi konversi rekursi ke iterasi.
#include <iostream>
#include <stack>
using namespace std;
struct Node {
int data;
Node* left;
Node* right;
Node(int val) : data(val), left(nullptr), right(nullptr) {}
};
// Inorder rekursif
void inorderRekursif(Node* root) {
if (root == nullptr) return;
inorderRekursif(root->left);
cout << root->data << " ";
inorderRekursif(root->right);
}
// Inorder iteratif dengan stack
void inorderIteratif(Node* root) {
stack<Node*> s;
Node* current = root;
while (current != nullptr || !s.empty()) {
// Traverse ke kiri sebanyak mungkin
while (current != nullptr) {
s.push(current);
current = current->left;
}
// Pop dan proses
current = s.top();
s.pop();
cout << current->data << " ";
// Pindah ke subtree kanan
current = current->right;
}
}
Soal: Konversi fungsi binary search rekursif menjadi iteratif dengan stack eksplisit.
Penyelesaian:
#include <iostream>
#include <stack>
using namespace std;
struct SearchState {
int left;
int right;
};
int binarySearchWithStack(int arr[], int n, int target) {
stack<SearchState> s;
s.push({0, n - 1});
while (!s.empty()) {
SearchState state = s.top();
s.pop();
int left = state.left;
int right = state.right;
if (left > right) continue;
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid; // Ditemukan
}
else if (target < arr[mid]) {
s.push({left, mid - 1}); // Cari di kiri
}
else {
s.push({mid + 1, right}); // Cari di kanan
}
}
return -1; // Tidak ditemukan
}
int main() {
int data[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
int target = 23;
int result = binarySearchWithStack(data, 10, target);
cout << "Index: " << result << endl; // Output: 5
return 0;
}
Soal: Konversi Tower of Hanoi rekursif menjadi iteratif dengan stack.
Penyelesaian:
#include <iostream>
#include <stack>
using namespace std;
struct HanoiState {
int n;
char source;
char destination;
char auxiliary;
int stage; // 0 = sebelum rekursi pertama, 1 = sebelum move, 2 = sebelum rekursi kedua
};
void hanoiIteratif(int n, char source, char destination, char auxiliary) {
stack<HanoiState> s;
s.push({n, source, destination, auxiliary, 0});
while (!s.empty()) {
HanoiState& state = s.top();
if (state.n == 1) {
cout << "Pindahkan disk 1 dari " << state.source
<< " ke " << state.destination << endl;
s.pop();
continue;
}
switch (state.stage) {
case 0:
// Push rekursi pertama
state.stage = 1;
s.push({state.n - 1, state.source, state.auxiliary,
state.destination, 0});
break;
case 1:
// Pindahkan disk terbesar
cout << "Pindahkan disk " << state.n << " dari "
<< state.source << " ke " << state.destination << endl;
state.stage = 2;
// Push rekursi kedua
s.push({state.n - 1, state.auxiliary, state.destination,
state.source, 0});
break;
case 2:
s.pop();
break;
}
}
}
int main() {
cout << "Tower of Hanoi Iteratif (3 disk):\n" << endl;
hanoiIteratif(3, 'A', 'C', 'B');
return 0;
}
Soal: Implementasikan fungsi untuk menghitung GCD (Greatest Common Divisor) menggunakan algoritma Euclidean dengan rekursi dan konversi ke iteratif.
Penyelesaian:
#include <iostream>
using namespace std;
// Rekursif
int gcdRekursif(int a, int b) {
if (b == 0) return a;
return gcdRekursif(b, a % b);
}
// Iteratif (tanpa stack karena tail recursive)
int gcdIteratif(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
int main() {
cout << "GCD(48, 18) rekursif: " << gcdRekursif(48, 18) << endl; // 6
cout << "GCD(48, 18) iteratif: " << gcdIteratif(48, 18) << endl; // 6
return 0;
}
Analisis:
Soal: Implementasikan fungsi rekursif untuk mencetak semua substring dari sebuah string.
Penyelesaian:
#include <iostream>
#include <string>
using namespace std;
void printSubstrings(const string& str, int start, int end) {
// Base case: sudah melewati semua posisi awal
if (start >= str.length()) return;
// Base case: end melewati panjang string, pindah ke start berikutnya
if (end > str.length()) {
printSubstrings(str, start + 1, start + 2);
return;
}
// Print substring dari start ke end
cout << str.substr(start, end - start) << endl;
// Recursive case: perpanjang end
printSubstrings(str, start, end + 1);
}
int main() {
string s = "ABC";
cout << "Semua substring dari \"" << s << "\":" << endl;
printSubstrings(s, 0, 1);
return 0;
}
Output:
A
AB
ABC
B
BC
C
Soal: Implementasikan algoritma Merge Sort rekursif (preview untuk Pertemuan 10).
Penyelesaian:
#include <iostream>
using namespace std;
// Fungsi untuk merge dua subarray yang sudah terurut
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
// Array sementara
int* L = new int[n1];
int* R = new int[n2];
// Copy data ke array sementara
for (int i = 0; i < n1; i++)
L[i] = arr[left + i];
for (int j = 0; j < n2; j++)
R[j] = arr[mid + 1 + j];
// Merge kembali ke arr[]
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// Copy sisa elemen
while (i < n1) {
arr[k] = L[i];
i++; k++;
}
while (j < n2) {
arr[k] = R[j];
j++; k++;
}
delete[] L;
delete[] R;
}
// Fungsi rekursif Merge Sort
void mergeSort(int arr[], int left, int right) {
if (left < right) {
// Divide: cari titik tengah
int mid = left + (right - left) / 2;
// Conquer: sort kedua bagian
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Combine: merge hasil
merge(arr, left, mid, right);
}
}
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
}
int main() {
int arr[] = {38, 27, 43, 3, 9, 82, 10};
int n = sizeof(arr) / sizeof(arr[0]);
cout << "Array awal: ";
printArray(arr, n);
mergeSort(arr, 0, n - 1);
cout << "Array setelah sort: ";
printArray(arr, n);
return 0;
}
Output:
Array awal: 38 27 43 3 9 82 10
Array setelah sort: 3 9 10 27 38 43 82
Soal: Dalam sistem radar pertahanan, data deteksi disimpan dalam array terurut berdasarkan waktu. Implementasikan fungsi untuk mencari range waktu tertentu menggunakan binary search.
Penyelesaian:
#include <iostream>
using namespace std;
struct Deteksi {
int waktu; // timestamp dalam detik
string objek; // jenis objek terdeteksi
};
// Cari index pertama >= target
int lowerBound(Deteksi arr[], int n, int target) {
int left = 0, right = n;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid].waktu < target)
left = mid + 1;
else
right = mid;
}
return left;
}
// Cari index pertama > target
int upperBound(Deteksi arr[], int n, int target) {
int left = 0, right = n;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid].waktu <= target)
left = mid + 1;
else
right = mid;
}
return left;
}
// Cetak deteksi dalam range waktu
void cariDalamRange(Deteksi arr[], int n, int waktuMulai, int waktuAkhir) {
int start = lowerBound(arr, n, waktuMulai);
int end = upperBound(arr, n, waktuAkhir);
cout << "Deteksi dari waktu " << waktuMulai << " sampai " << waktuAkhir << ":" << endl;
for (int i = start; i < end; i++) {
cout << " Waktu: " << arr[i].waktu << ", Objek: " << arr[i].objek << endl;
}
cout << "Total: " << (end - start) << " deteksi" << endl;
}
int main() {
Deteksi data[] = {
{100, "Pesawat"},
{150, "Drone"},
{200, "Pesawat"},
{250, "Helikopter"},
{300, "Drone"},
{350, "Pesawat"},
{400, "Drone"}
};
int n = 7;
// Cari deteksi antara waktu 150 dan 300
cariDalamRange(data, n, 150, 300);
return 0;
}
Output:
Deteksi dari waktu 150 sampai 300:
Waktu: 150, Objek: Drone
Waktu: 200, Objek: Pesawat
Waktu: 250, Objek: Helikopter
Waktu: 300, Objek: Drone
Total: 4 deteksi
Soal: Implementasikan fungsi untuk menghasilkan semua kombinasi elemen dari sebuah array menggunakan rekursi.
Penyelesaian:
#include <iostream>
#include <vector>
using namespace std;
void generateCombinations(int arr[], int n, int start, vector<int>& current) {
// Cetak kombinasi saat ini (jika tidak kosong)
if (!current.empty()) {
cout << "{ ";
for (int i = 0; i < current.size(); i++) {
cout << current[i];
if (i < current.size() - 1) cout << ", ";
}
cout << " }" << endl;
}
// Generate kombinasi dengan menambahkan setiap elemen yang tersisa
for (int i = start; i < n; i++) {
current.push_back(arr[i]);
generateCombinations(arr, n, i + 1, current);
current.pop_back(); // Backtrack
}
}
int main() {
int arr[] = {1, 2, 3};
int n = 3;
vector<int> current;
cout << "Semua kombinasi dari {1, 2, 3}:" << endl;
generateCombinations(arr, n, 0, current);
return 0;
}
Output:
Semua kombinasi dari {1, 2, 3}:
{ 1 }
{ 1, 2 }
{ 1, 2, 3 }
{ 1, 3 }
{ 2 }
{ 2, 3 }
{ 3 }
Tuliskan fungsi rekursif untuk membalik sebuah string. Apa kompleksitasnya?
Jawaban:
string reverseString(string s, int left, int right) {
if (left >= right) return s;
swap(s[left], s[right]);
return reverseString(s, left + 1, right - 1);
}
Kompleksitas: O(n) waktu, O(n) space untuk call stack.
Gunakan Master Theorem untuk menentukan kompleksitas T(n) = 9T(n/3) + n.
Jawaban:
Berapa langkah minimum untuk Tower of Hanoi dengan 10 disk?
Jawaban:
Konversikan fungsi sum 1 sampai n dari rekursif menjadi tail recursive.
Jawaban:
// Non-tail
int sumN(int n) {
if (n == 0) return 0;
return n + sumN(n - 1);
}
// Tail recursive
int sumNTail(int n, int acc = 0) {
if (n == 0) return acc;
return sumNTail(n - 1, acc + n);
}
Jelaskan mengapa memoization efektif untuk Fibonacci tapi tidak untuk faktorial?
Jawaban:
| Topik | Konsep Kunci | Kompleksitas |
|---|---|---|
| Rekursi | Base case + Recursive case | Tergantung algoritma |
| Recurrence Relation | T(n) = aT(n/b) + f(n) | - |
| Master Theorem | 3 kasus berdasarkan perbandingan | - |
| Tail Recursion | Rekursi sebagai operasi terakhir | O(1) space dengan TCO |
| Memoization | Simpan hasil yang sudah dihitung | Reduce dari O(2^n) ke O(n) |
| Divide-and-Conquer | Divide → Conquer → Combine | Biasanya O(n log n) |
| Binary Search | Bagi search space menjadi setengah | O(log n) |
| Tower of Hanoi | Pindahkan n-1, pindah 1, pindahkan n-1 | O(2^n) |
| Rekursi → Iterasi | Gunakan stack eksplisit | Sama, tapi efisien |
| Algoritma | Waktu | Ruang (Rekursif) | Ruang (Iteratif) |
|---|---|---|---|
| Fibonacci Naive | O(2^n) | O(n) | - |
| Fibonacci Memo | O(n) | O(n) | O(n) |
| Fibonacci Tail | O(n) | O(1)* | O(1) |
| Binary Search | O(log n) | O(log n) | O(1) |
| Merge Sort | O(n log n) | O(n + log n) | O(n) |
| Tower of Hanoi | O(2^n) | O(n) | O(n) |
*dengan Tail Call Optimization
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