Mata Kuliah: Struktur Data dan Algoritma
Kode: SDA201
SKS: 3 SKS (2 Teori + 1 Praktikum)
Pertemuan: 10
Topik: Algoritma Sorting (Bagian 2) - Sorting Lanjut
Capaian Pembelajaran: Sub-CPMK-4.2, Sub-CPMK-4.3 — Mampu mengimplementasikan algoritma sorting lanjut (merge sort, quick sort) dan membandingkan serta memilih algoritma sorting berdasarkan karakteristik data
Estimasi Waktu Belajar: 7 jam
Referensi Utama: Cormen Ch.7-8; Weiss Ch.7.4-7.7; Hubbard Ch.13
Setelah menyelesaikan modul ini, mahasiswa diharapkan mampu:
Pada Pertemuan 9, kita telah mempelajari algoritma sorting dasar: bubble sort, selection sort, dan insertion sort. Ketiga algoritma tersebut memiliki kompleksitas waktu O(n²) pada kasus rata-rata dan terburuk.
Untuk dataset yang besar (misalnya jutaan elemen), kompleksitas O(n²) menjadi tidak praktis:
| Jumlah Elemen (n) | O(n²) Operasi | O(n log n) Operasi | Rasio |
|---|---|---|---|
| 1,000 | 1,000,000 | ~10,000 | 100× |
| 10,000 | 100,000,000 | ~133,000 | 750× |
| 1,000,000 | 1,000,000,000,000 | ~20,000,000 | 50,000× |
Dalam konteks sistem pertahanan, bayangkan mengurutkan data radar yang mendeteksi ribuan objek terbang. Algoritma O(n²) akan terlalu lambat untuk pengolahan real-time.
Definisi: Divide-and-conquer adalah paradigma desain algoritma yang memecah masalah besar menjadi sub-masalah yang lebih kecil, menyelesaikan sub-masalah secara rekursif, kemudian menggabungkan solusi sub-masalah menjadi solusi masalah asal.
Tiga langkah utama divide-and-conquer:

Gambar 10.1: Ilustrasi paradigma divide-and-conquer
Dengan paradigma divide-and-conquer, kita dapat mencapai kompleksitas O(n log n), yang jauh lebih efisien untuk dataset besar. Dua algoritma utama yang akan kita pelajari:
| Algoritma | Kompleksitas Waktu | Kompleksitas Ruang | Stable | In-place |
|---|---|---|---|---|
| Merge Sort | O(n log n) selalu | O(n) | Ya | Tidak |
| Quick Sort | O(n log n) rata-rata | O(log n) | Tidak | Ya |
Definisi: Merge sort adalah algoritma sorting yang menggunakan strategi divide-and-conquer dengan membagi array menjadi dua bagian, mengurutkan masing-masing bagian secara rekursif, kemudian menggabungkan (merge) kedua bagian yang sudah terurut.
Langkah-langkah Merge Sort:

Gambar 10.2: Visualisasi proses merge sort pada array [38, 27, 43, 3, 9, 82, 10]
Operasi merge adalah inti dari merge sort. Fungsi ini menggabungkan dua array yang sudah terurut menjadi satu array terurut.
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1; // Ukuran subarray kiri
int n2 = right - mid; // Ukuran subarray kanan
// Buat array temporary
int* L = new int[n1];
int* R = new int[n2];
// Copy data ke array temporary
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[left..right]
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 L[] jika ada
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// Copy sisa elemen R[] jika ada
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
delete[] L;
delete[] R;
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
// Cari titik tengah untuk membagi array
int mid = left + (right - left) / 2;
// Urutkan kedua bagian secara rekursif
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Gabungkan kedua bagian yang sudah terurut
merge(arr, left, mid, right);
}
}
// Fungsi wrapper untuk kemudahan penggunaan
void mergeSort(int arr[], int n) {
mergeSort(arr, 0, n - 1);
}
Kompleksitas Waktu:
Recurrence relation untuk merge sort:
T(n) = 2T(n/2) + O(n)
Menggunakan Master Theorem:
| Kasus | Kompleksitas |
|---|---|
| Best Case | O(n log n) |
| Average Case | O(n log n) |
| Worst Case | O(n log n) |
Kompleksitas Ruang: O(n) untuk array temporary dalam proses merge.
Soal: Tunjukkan langkah-langkah merge sort untuk array [5, 2, 8, 1]!
Penyelesaian:
Langkah 1 - Divide:
[5, 2, 8, 1]
↓
[5, 2] dan [8, 1]
↓
[5] [2] dan [8] [1] ← Base case tercapai
Langkah 2 - Merge (bottom-up):
Merge [5] dan [2]:
- Bandingkan 5 dan 2 → pilih 2
- Sisa 5
- Hasil: [2, 5]
Merge [8] dan [1]:
- Bandingkan 8 dan 1 → pilih 1
- Sisa 8
- Hasil: [1, 8]
Merge [2, 5] dan [1, 8]:
- Bandingkan 2 dan 1 → pilih 1
- Bandingkan 2 dan 8 → pilih 2
- Bandingkan 5 dan 8 → pilih 5
- Sisa 8
- Hasil: [1, 2, 5, 8] ✓
Soal: Berapa jumlah perbandingan yang dilakukan saat merge dua subarray [1, 3, 5] dan [2, 4, 6]?
Penyelesaian:
Proses merge:
L = [1, 3, 5], R = [2, 4, 6]
i = 0, j = 0
Perbandingan 1: L[0]=1 vs R[0]=2 → pilih 1, i++
Perbandingan 2: L[1]=3 vs R[0]=2 → pilih 2, j++
Perbandingan 3: L[1]=3 vs R[1]=4 → pilih 3, i++
Perbandingan 4: L[2]=5 vs R[1]=4 → pilih 4, j++
Perbandingan 5: L[2]=5 vs R[2]=6 → pilih 5, i++
(L habis, copy sisa R)
Jawaban: 5 perbandingan
Catatan: Untuk merge dua array dengan ukuran n1 dan n2, jumlah perbandingan maksimum adalah n1 + n2 - 1.
Soal: Apa yang terjadi jika kita mengganti kondisi L[i] <= R[j] menjadi L[i] < R[j] dalam fungsi merge?
Penyelesaian:
Dengan mengubah <= menjadi <, algoritma tetap benar (menghasilkan array terurut), tetapi merge sort akan menjadi tidak stable.
Stable sort berarti elemen dengan nilai sama mempertahankan urutan relatif aslinya.
Contoh:
Array asal: [(A,5), (B,3), (C,5)] // (nama, nilai)
L = [(A,5)], R = [(C,5)]
Dengan <=: A diambil dulu → [(A,5), (C,5)] ✓ Stable
Dengan <: C diambil dulu → [(C,5), (A,5)] ✗ Tidak stable
Dalam aplikasi seperti mengurutkan data mahasiswa berdasarkan nilai, stabilitas penting untuk mempertahankan urutan alfabet jika nilai sama.
Soal: Implementasikan merge sort untuk mengurutkan array dalam urutan menurun (descending)!
Penyelesaian:
void mergeDescending(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int* L = new int[n1];
int* R = new int[n2];
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];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
// Ubah kondisi: ambil yang LEBIH BESAR
if (L[i] >= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
delete[] L;
delete[] R;
}
void mergeSortDescending(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSortDescending(arr, left, mid);
mergeSortDescending(arr, mid + 1, right);
mergeDescending(arr, left, mid, right);
}
}
Perubahan kunci: Pada fungsi merge, kondisi L[i] <= R[j] diubah menjadi L[i] >= R[j] agar elemen yang lebih besar dipilih terlebih dahulu.
Soal: Hitunglah jumlah rekursi dan operasi merge yang terjadi saat merge sort memproses array dengan 8 elemen!
Penyelesaian:
Untuk n = 8 elemen:
Struktur rekursi (pohon rekursi):
Level 0: mergeSort(0-7) → 1 panggilan
Level 1: mergeSort(0-3), mergeSort(4-7) → 2 panggilan
Level 2: mergeSort(0-1), (2-3), (4-5), (6-7) → 4 panggilan
Level 3: 8 panggilan untuk single element → 8 panggilan
Total panggilan rekursif: 1 + 2 + 4 + 8 = 15 panggilan
Jumlah operasi merge:
Total operasi merge: 4 + 2 + 1 = 7 operasi merge
Formula umum:
Definisi: Quick sort adalah algoritma sorting divide-and-conquer yang memilih sebuah elemen sebagai pivot, kemudian mempartisi array sehingga elemen yang lebih kecil dari pivot berada di kiri dan yang lebih besar di kanan.
Langkah-langkah Quick Sort:

Gambar 10.3: Visualisasi proses quick sort dengan pivot terakhir
Fungsi partition adalah jantung dari quick sort. Berikut implementasi dengan skema Lomuto (pivot di akhir):
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Pilih elemen terakhir sebagai pivot
int i = low - 1; // Index elemen terkecil
for (int j = low; j < high; j++) {
// Jika elemen saat ini lebih kecil dari pivot
if (arr[j] < pivot) {
i++;
swap(arr[i], arr[j]);
}
}
// Letakkan pivot di posisi yang benar
swap(arr[i + 1], arr[high]);
return i + 1; // Return posisi pivot
}
Ilustrasi proses partition:

Gambar 10.4: Langkah-langkah proses partition dengan skema Lomuto
void quickSort(int arr[], int low, int high) {
if (low < high) {
// Partition dan dapatkan posisi pivot
int pi = partition(arr, low, high);
// Rekursif pada subarray kiri dan kanan
quickSort(arr, low, pi - 1); // Elemen sebelum pivot
quickSort(arr, pi + 1, high); // Elemen setelah pivot
}
}
// Fungsi wrapper
void quickSort(int arr[], int n) {
quickSort(arr, 0, n - 1);
}
Pemilihan pivot sangat mempengaruhi performa quick sort:
| Strategi | Deskripsi | Kelebihan | Kekurangan |
|---|---|---|---|
| First | Pilih elemen pertama | Simpel | Buruk untuk data terurut |
| Last | Pilih elemen terakhir | Simpel | Buruk untuk data terurut |
| Random | Pilih elemen acak | Rata-rata baik | Overhead random |
| Median-of-Three | Median dari first, middle, last | Hindari worst case | Sedikit lebih kompleks |
Implementasi Median-of-Three:
int medianOfThree(int arr[], int low, int high) {
int mid = low + (high - low) / 2;
// Urutkan arr[low], arr[mid], arr[high]
if (arr[low] > arr[mid])
swap(arr[low], arr[mid]);
if (arr[low] > arr[high])
swap(arr[low], arr[high]);
if (arr[mid] > arr[high])
swap(arr[mid], arr[high]);
// Pindahkan median ke posisi high-1
swap(arr[mid], arr[high - 1]);
return arr[high - 1];
}
int partitionMedian(int arr[], int low, int high) {
int pivot = medianOfThree(arr, low, high);
int i = low;
int j = high - 1;
while (true) {
while (arr[++i] < pivot);
while (arr[--j] > pivot);
if (i >= j) break;
swap(arr[i], arr[j]);
}
swap(arr[i], arr[high - 1]);
return i;
}
Kompleksitas Waktu:
| Kasus | Kondisi | Kompleksitas |
|---|---|---|
| Best Case | Pivot selalu membagi array menjadi dua bagian sama | O(n log n) |
| Average Case | Pivot membagi secara “cukup baik” | O(n log n) |
| Worst Case | Pivot selalu terkecil/terbesar (array sudah terurut) | O(n²) |
Recurrence untuk kasus terbaik:
T(n) = 2T(n/2) + O(n) = O(n log n)
Recurrence untuk kasus terburuk:
T(n) = T(n-1) + O(n) = O(n²)
Kompleksitas Ruang: O(log n) untuk call stack dalam kasus rata-rata.
Soal: Lakukan partition pada array [7, 2, 1, 6, 8, 5, 3, 4] dengan pivot = 4 (elemen terakhir)!
Penyelesaian:
Array awal: [7, 2, 1, 6, 8, 5, 3, 4]
pivot = 4, i = -1
j=0: arr[0]=7 > 4, tidak swap
[7, 2, 1, 6, 8, 5, 3, 4], i=-1
j=1: arr[1]=2 < 4, i++, swap(arr[0], arr[1])
[2, 7, 1, 6, 8, 5, 3, 4], i=0
j=2: arr[2]=1 < 4, i++, swap(arr[1], arr[2])
[2, 1, 7, 6, 8, 5, 3, 4], i=1
j=3: arr[3]=6 > 4, tidak swap
[2, 1, 7, 6, 8, 5, 3, 4], i=1
j=4: arr[4]=8 > 4, tidak swap
[2, 1, 7, 6, 8, 5, 3, 4], i=1
j=5: arr[5]=5 > 4, tidak swap
[2, 1, 7, 6, 8, 5, 3, 4], i=1
j=6: arr[6]=3 < 4, i++, swap(arr[2], arr[6])
[2, 1, 3, 6, 8, 5, 7, 4], i=2
Akhir: swap(arr[i+1], arr[high]) = swap(arr[3], arr[7])
[2, 1, 3, 4, 8, 5, 7, 6]
↑
pivot di posisi 3
Hasil: Array setelah partition: [2, 1, 3, 4, 8, 5, 7, 6], pivot index = 3
Soal: Mengapa quick sort memiliki worst case O(n²) pada array yang sudah terurut jika menggunakan pivot pertama atau terakhir?
Penyelesaian:
Misalkan array [1, 2, 3, 4, 5] dengan pivot terakhir:
Iterasi 1:
Iterasi 2:
Pola: Setiap kali, hanya 1 elemen yang “diproses” (pivot), sisanya masuk ke satu subarray.
Total perbandingan:
Total = (n-1) + (n-2) + … + 1 = n(n-1)/2 = O(n²)
Solusi: Gunakan random pivot atau median-of-three untuk menghindari kasus ini.
Soal: Implementasikan quick sort dengan random pivot selection!
Penyelesaian:
#include <cstdlib>
#include <ctime>
int randomPartition(int arr[], int low, int high) {
// Generate random index antara low dan high
int randomIndex = low + rand() % (high - low + 1);
// Tukar elemen random dengan elemen terakhir
swap(arr[randomIndex], arr[high]);
// Lakukan partition biasa dengan pivot di akhir
return partition(arr, low, high);
}
void quickSortRandom(int arr[], int low, int high) {
if (low < high) {
int pi = randomPartition(arr, low, high);
quickSortRandom(arr, low, pi - 1);
quickSortRandom(arr, pi + 1, high);
}
}
int main() {
srand(time(0)); // Seed random number generator
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSortRandom(arr, 0, n - 1);
cout << "Array terurut: ";
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
return 0;
}
Output:
Array terurut: 1 5 7 8 9 10
Soal: Hitunglah kedalaman rekursi maksimum quick sort untuk array dengan n=16 elemen dalam kasus terbaik dan terburuk!
Penyelesaian:
Kasus Terbaik (partition selalu di tengah):
Level 0: 16 elemen
Level 1: 2 × 8 elemen
Level 2: 4 × 4 elemen
Level 3: 8 × 2 elemen
Level 4: 16 × 1 elemen (base case)
Kedalaman maksimum = log₂(16) = 4 level
Kasus Terburuk (partition selalu di ujung):
Level 0: 16 elemen → pivot di posisi 0 atau 15
Level 1: 15 elemen
Level 2: 14 elemen
...
Level 15: 1 elemen (base case)
Kedalaman maksimum = 16 - 1 = 15 level
Implikasi:
Soal: Modifikasi quick sort agar menggunakan insertion sort untuk subarray yang ukurannya kurang dari threshold tertentu (hybrid sorting)!
Penyelesaian:
const int THRESHOLD = 10; // Threshold untuk switch ke insertion sort
void insertionSort(int arr[], int low, int high) {
for (int i = low + 1; i <= high; i++) {
int key = arr[i];
int j = i - 1;
while (j >= low && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
void hybridQuickSort(int arr[], int low, int high) {
// Jika subarray kecil, gunakan insertion sort
if (high - low + 1 <= THRESHOLD) {
insertionSort(arr, low, high);
return;
}
if (low < high) {
int pi = partition(arr, low, high);
hybridQuickSort(arr, low, pi - 1);
hybridQuickSort(arr, pi + 1, high);
}
}
Alasan efektif:
Teknik ini digunakan dalam implementasi quick sort di library standar seperti std::sort di C++.
| Aspek | Merge Sort | Quick Sort |
|---|---|---|
| Kompleksitas Waktu (Worst) | O(n log n) | O(n²) |
| Kompleksitas Waktu (Average) | O(n log n) | O(n log n) |
| Kompleksitas Ruang | O(n) | O(log n) |
| Stable | Ya | Tidak |
| In-place | Tidak | Ya |
| Cache Efficiency | Kurang baik | Lebih baik |
| Parallelizable | Sangat baik | Cukup baik |
Gunakan Merge Sort ketika:
Gunakan Quick Sort ketika:

Gambar 10.5: Perbandingan karakteristik merge sort dan quick sort
Dalam praktik, quick sort sering lebih cepat dari merge sort meskipun keduanya O(n log n) karena:
Soal: Sebuah sistem radar perlu mengurutkan 10.000 objek berdasarkan jarak. Objek dengan jarak sama harus mempertahankan urutan waktu deteksi. Algoritma mana yang lebih tepat?
Penyelesaian:
Merge Sort adalah pilihan yang tepat karena:
Stabilitas diperlukan: Objek dengan jarak sama harus mempertahankan urutan waktu deteksi → merge sort adalah stable sort
Kompleksitas terjamin: Dalam sistem radar real-time, kita tidak bisa mentolerir worst case O(n²) → merge sort menjamin O(n log n)
Ukuran data: 10.000 objek × misalnya 32 byte per objek = 320 KB, masih manageable untuk overhead memori O(n)
Quick sort tidak cocok karena:
Soal: Bandingkan jumlah operasi swap antara merge sort dan quick sort untuk array [3, 1, 4, 1, 5, 9, 2, 6]!
Penyelesaian:
Merge Sort: Merge sort tidak menggunakan swap secara langsung, melainkan copy ke array temporary dan copy kembali.
Total “move” operations:
Quick Sort (pivot terakhir): Mari trace:
[3, 1, 4, 1, 5, 9, 2, 6], pivot=6
Partition: [3, 1, 4, 1, 5, 2, 6, 9] → 2 swaps (2↔9, 6↔di posisi)
[3, 1, 4, 1, 5, 2], pivot=2
Partition: [1, 1, 2, 4, 5, 3] → 3 swaps
[1, 1], pivot=1: 1 swap
[4, 5, 3], pivot=3: 2 swaps
Total swaps (approximate): 8-10 swaps
Kesimpulan: Quick sort melakukan lebih sedikit data movement, yang menjelaskan kenapa sering lebih cepat dalam praktik.
Soal: Implementasikan versi iteratif dari quick sort menggunakan stack eksplisit!
Penyelesaian:
#include <stack>
using namespace std;
void quickSortIterative(int arr[], int n) {
// Stack untuk menyimpan pasangan (low, high)
stack<pair<int, int>> stk;
// Push range awal
stk.push({0, n - 1});
while (!stk.empty()) {
// Pop range dari stack
int low = stk.top().first;
int high = stk.top().second;
stk.pop();
if (low < high) {
// Partition
int pi = partition(arr, low, high);
// Push subarray yang lebih besar dulu (untuk efisiensi stack)
// Ini memastikan subarray yang lebih kecil diproses dulu
if (pi - low < high - pi) {
// Subarray kiri lebih kecil
stk.push({pi + 1, high}); // Push kanan dulu
stk.push({low, pi - 1}); // Push kiri (diproses dulu)
} else {
// Subarray kanan lebih kecil
stk.push({low, pi - 1}); // Push kiri dulu
stk.push({pi + 1, high}); // Push kanan (diproses dulu)
}
}
}
}
Keuntungan versi iteratif:
Teorema: Setiap algoritma comparison-based sorting memerlukan minimal Ω(n log n) perbandingan dalam kasus terburuk.
Penjelasan:

Gambar 10.6: Decision tree untuk sorting 3 elemen [a, b, c]
| Algoritma | Mencapai Lower Bound? |
|---|---|
| Bubble Sort | Tidak (O(n²)) |
| Selection Sort | Tidak (O(n²)) |
| Insertion Sort | Tidak (O(n²) worst) |
| Merge Sort | Ya (O(n log n)) |
| Quick Sort | Ya (O(n log n) average) |
| Heap Sort | Ya (O(n log n)) |
Catatan: Heap Sort akan dipelajari pada Pertemuan 13.
Soal: Berapa jumlah perbandingan minimum yang diperlukan untuk mengurutkan 5 elemen menggunakan comparison-based sorting?
Penyelesaian:
Untuk n = 5 elemen:
Verifikasi:
Jawaban: Minimal 7 perbandingan diperlukan untuk mengurutkan 5 elemen.
Soal: Jelaskan mengapa counting sort, radix sort, dan bucket sort dapat mencapai O(n) meskipun lower bound adalah Ω(n log n)!
Penyelesaian:
Lower bound Ω(n log n) berlaku hanya untuk comparison-based sorting — algoritma yang hanya menggunakan perbandingan antar elemen untuk menentukan urutan.
Counting Sort, Radix Sort, Bucket Sort bukan comparison-based:
Trade-off: Algoritma non-comparison memerlukan asumsi tambahan tentang data (range terbatas, distribusi tertentu, dll).
Faktor yang perlu dipertimbangkan:

Gambar 10.7: Flowchart pemilihan algoritma sorting
Soal: Sistem database militer perlu mengurutkan 10 juta record berdasarkan timestamp. Data disimpan di hard disk karena terlalu besar untuk RAM. Algoritma apa yang paling tepat?
Penyelesaian:
Merge Sort adalah pilihan terbaik untuk external sorting karena:
Sequential access pattern: Merge sort membaca dan menulis data secara berurutan, yang sangat efisien untuk disk (minimize seek time)
Predictable I/O: Pola akses dapat diprediksi, memungkinkan optimasi I/O
Divide naturally: Data dapat dibagi menjadi chunk yang fit di RAM, diurutkan di RAM, kemudian di-merge
Proses External Merge Sort:
1. Baca chunk data yang muat di RAM (misalnya 100MB)
2. Urutkan chunk di RAM dengan quick sort
3. Tulis sorted chunk ke disk (run)
4. Ulangi untuk semua data
5. Merge semua run secara bertahap
Quick sort tidak cocok karena memerlukan random access, yang sangat lambat pada disk.
Soal: Implementasikan fungsi yang memilih algoritma sorting optimal berdasarkan karakteristik data!
Penyelesaian:
#include <algorithm>
#include <cstdlib>
enum SortType { INSERTION, MERGE, QUICK };
// Hitung tingkat "keterurutan" data
double calculateSortedness(int arr[], int n) {
int inversions = 0;
for (int i = 0; i < n - 1; i++) {
if (arr[i] > arr[i + 1]) {
inversions++;
}
}
return 1.0 - (double)inversions / (n - 1);
}
SortType selectSortingAlgorithm(int arr[], int n,
bool needStable,
bool memoryLimited) {
// Kriteria 1: Ukuran kecil → insertion sort
if (n < 50) {
return INSERTION;
}
// Kriteria 2: Perlu stabilitas → merge sort
if (needStable) {
return MERGE;
}
// Kriteria 3: Data hampir terurut → insertion sort
double sortedness = calculateSortedness(arr, n);
if (sortedness > 0.9) { // 90% terurut
return INSERTION;
}
// Kriteria 4: Memori terbatas → quick sort
if (memoryLimited) {
return QUICK;
}
// Default: quick sort (biasanya tercepat)
return QUICK;
}
void adaptiveSort(int arr[], int n, bool needStable = false,
bool memoryLimited = false) {
SortType type = selectSortingAlgorithm(arr, n, needStable,
memoryLimited);
switch (type) {
case INSERTION:
insertionSort(arr, n);
break;
case MERGE:
mergeSort(arr, 0, n - 1);
break;
case QUICK:
quickSort(arr, 0, n - 1);
break;
}
}
Soal: Implementasikan 3-way quick sort untuk menangani array dengan banyak elemen duplikat secara efisien!
Penyelesaian:
3-way quick sort (Dutch National Flag) mempartisi array menjadi tiga bagian: < pivot, = pivot, > pivot.
void threeWayPartition(int arr[], int low, int high,
int& lt, int& gt) {
int pivot = arr[low]; // Pilih pivot
lt = low; // arr[low..lt-1] < pivot
gt = high; // arr[gt+1..high] > pivot
int i = low; // arr[lt..i-1] = pivot
while (i <= gt) {
if (arr[i] < pivot) {
swap(arr[lt], arr[i]);
lt++;
i++;
} else if (arr[i] > pivot) {
swap(arr[i], arr[gt]);
gt--;
// i tidak di-increment karena arr[i] baru perlu dicek
} else {
// arr[i] == pivot
i++;
}
}
}
void quickSort3Way(int arr[], int low, int high) {
if (low >= high) return;
int lt, gt;
threeWayPartition(arr, low, high, lt, gt);
// arr[low..lt-1] < pivot
// arr[lt..gt] = pivot (sudah di posisi benar)
// arr[gt+1..high] > pivot
quickSort3Way(arr, low, lt - 1);
quickSort3Way(arr, gt + 1, high);
}
Keunggulan untuk data dengan banyak duplikat:
Contoh: Array [4, 4, 4, 4, 4, 4, 4]
Soal: Analisis kompleksitas space untuk rekursi quick sort dan jelaskan teknik tail call optimization!
Penyelesaian:
Kompleksitas Space Quick Sort:
Standard recursive quick sort memerlukan O(n) space dalam worst case karena call stack.
Worst case (partition sangat tidak seimbang):
quickSort([1,2,3,...,n])
└─ quickSort([1,2,3,...,n-1])
└─ quickSort([1,2,3,...,n-2])
└─ ...
└─ quickSort([1])
Stack depth: n-1 → O(n) space
Tail Call Optimization:
Kita bisa mengoptimasi dengan selalu melakukan rekursi pada subarray yang lebih kecil terlebih dahulu, dan menggunakan iterasi untuk yang lebih besar:
void quickSortOptimized(int arr[], int low, int high) {
while (low < high) {
int pi = partition(arr, low, high);
// Rekursi pada bagian yang lebih kecil
// Iterasi (tail call) pada bagian yang lebih besar
if (pi - low < high - pi) {
// Subarray kiri lebih kecil
quickSortOptimized(arr, low, pi - 1);
low = pi + 1; // "Tail call" menjadi iterasi
} else {
// Subarray kanan lebih kecil
quickSortOptimized(arr, pi + 1, high);
high = pi - 1; // "Tail call" menjadi iterasi
}
}
}
Hasil optimasi:
Soal: Implementasikan parallel merge sort menggunakan threading untuk mempercepat sorting pada sistem multi-core!
Penyelesaian:
#include <thread>
#include <vector>
const int PARALLEL_THRESHOLD = 10000; // Minimum untuk paralel
void parallelMergeSort(int arr[], int left, int right, int depth = 0) {
if (left >= right) return;
int mid = left + (right - left) / 2;
// Batasi kedalaman paralel untuk menghindari terlalu banyak thread
// dan untuk array yang cukup besar
if (depth < 3 && (right - left) > PARALLEL_THRESHOLD) {
// Buat thread untuk subarray kiri
thread leftThread(parallelMergeSort, arr, left, mid, depth + 1);
// Proses subarray kanan di thread saat ini
parallelMergeSort(arr, mid + 1, right, depth + 1);
// Tunggu thread kiri selesai
leftThread.join();
} else {
// Sequential untuk array kecil atau kedalaman sudah cukup
parallelMergeSort(arr, left, mid, depth + 1);
parallelMergeSort(arr, mid + 1, right, depth + 1);
}
// Merge (bisa juga diparalelkan untuk array sangat besar)
merge(arr, left, mid, right);
}
int main() {
const int n = 1000000;
int* arr = new int[n];
// Isi dengan data random
for (int i = 0; i < n; i++) {
arr[i] = rand();
}
auto start = chrono::high_resolution_clock::now();
parallelMergeSort(arr, 0, n - 1);
auto end = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
cout << "Waktu: " << duration.count() << " ms" << endl;
delete[] arr;
return 0;
}
Catatan penting:
Soal: Sebuah sistem pertahanan udara perlu mengurutkan objek radar berdasarkan prioritas ancaman. Data meliputi: ID, jarak, kecepatan, jenis objek. Desain struktur data dan algoritma sorting yang optimal!
Penyelesaian:
#include <iostream>
#include <cstring>
using namespace std;
struct ObjekRadar {
int id;
double jarak; // km
double kecepatan; // km/jam
char jenis[20]; // pesawat, rudal, drone, dll
int prioritas; // Dihitung berdasarkan faktor lain
// Hitung prioritas ancaman
void hitungPrioritas() {
// Semakin dekat dan cepat = semakin berbahaya
double timeToImpact = jarak / kecepatan; // jam
// Base priority dari waktu hingga impact
prioritas = (int)(1000 / (timeToImpact + 0.001));
// Modifier berdasarkan jenis
if (strcmp(jenis, "rudal") == 0) {
prioritas *= 3;
} else if (strcmp(jenis, "jet_tempur") == 0) {
prioritas *= 2;
} else if (strcmp(jenis, "drone") == 0) {
prioritas = (int)(prioritas * 1.5);
}
}
};
// Comparator untuk sorting descending (prioritas tinggi dulu)
bool comparePrioritas(const ObjekRadar& a, const ObjekRadar& b) {
return a.prioritas > b.prioritas;
}
// Quick sort khusus untuk ObjekRadar
int partitionRadar(ObjekRadar arr[], int low, int high) {
int pivot = arr[high].prioritas;
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j].prioritas > pivot) { // Descending
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[high]);
return i + 1;
}
void sortObjekRadar(ObjekRadar arr[], int low, int high) {
if (low < high) {
int pi = partitionRadar(arr, low, high);
sortObjekRadar(arr, low, pi - 1);
sortObjekRadar(arr, pi + 1, high);
}
}
int main() {
ObjekRadar objek[5] = {
{1001, 50, 800, "rudal", 0},
{1002, 100, 300, "pesawat", 0},
{1003, 30, 600, "drone", 0},
{1004, 80, 1200, "jet_tempur", 0},
{1005, 150, 400, "helikopter", 0}
};
// Hitung prioritas
for (int i = 0; i < 5; i++) {
objek[i].hitungPrioritas();
}
// Urutkan berdasarkan prioritas
sortObjekRadar(objek, 0, 4);
// Tampilkan hasil
cout << "=== PRIORITAS ANCAMAN ===" << endl;
for (int i = 0; i < 5; i++) {
cout << "Rank " << (i+1) << ": ID " << objek[i].id
<< " | " << objek[i].jenis
<< " | Jarak: " << objek[i].jarak << " km"
<< " | Kecepatan: " << objek[i].kecepatan << " km/jam"
<< " | Prioritas: " << objek[i].prioritas << endl;
}
return 0;
}
Output (contoh):
=== PRIORITAS ANCAMAN ===
Rank 1: ID 1001 | rudal | Jarak: 50 km | Kecepatan: 800 km/jam | Prioritas: 48000
Rank 2: ID 1004 | jet_tempur | Jarak: 80 km | Kecepatan: 1200 km/jam | Prioritas: 30000
Rank 3: ID 1003 | drone | Jarak: 30 km | Kecepatan: 600 km/jam | Prioritas: 30000
Rank 4: ID 1002 | pesawat | Jarak: 100 km | Kecepatan: 300 km/jam | Prioritas: 3000
Rank 5: ID 1005 | helikopter | Jarak: 150 km | Kecepatan: 400 km/jam | Prioritas: 2666
Soal: Implementasikan Introsort (introspective sort) yang menggabungkan quick sort, heap sort, dan insertion sort untuk performa optimal di semua kasus!
Penyelesaian:
Introsort adalah algoritma yang digunakan dalam std::sort C++:
#include <cmath>
#include <algorithm>
// Insertion sort untuk array kecil
void insertionSortRange(int arr[], int low, int high) {
for (int i = low + 1; i <= high; i++) {
int key = arr[i];
int j = i - 1;
while (j >= low && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
// Heapify untuk heap sort
void heapify(int arr[], int n, int i, int offset) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[offset + left] > arr[offset + largest])
largest = left;
if (right < n && arr[offset + right] > arr[offset + largest])
largest = right;
if (largest != i) {
swap(arr[offset + i], arr[offset + largest]);
heapify(arr, n, largest, offset);
}
}
// Heap sort untuk subarray
void heapSortRange(int arr[], int low, int high) {
int n = high - low + 1;
// Build heap
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i, low);
// Extract elements
for (int i = n - 1; i > 0; i--) {
swap(arr[low], arr[low + i]);
heapify(arr, i, 0, low);
}
}
// Median of three
int medianOfThreeIntro(int arr[], int low, int high) {
int mid = low + (high - low) / 2;
if (arr[mid] < arr[low])
swap(arr[mid], arr[low]);
if (arr[high] < arr[low])
swap(arr[high], arr[low]);
if (arr[high] < arr[mid])
swap(arr[high], arr[mid]);
return arr[mid];
}
// Partition untuk introsort
int partitionIntro(int arr[], int low, int high) {
int pivot = medianOfThreeIntro(arr, low, high);
int i = low - 1;
int j = high + 1;
while (true) {
do { i++; } while (arr[i] < pivot);
do { j--; } while (arr[j] > pivot);
if (i >= j) return j;
swap(arr[i], arr[j]);
}
}
const int SIZE_THRESHOLD = 16; // Threshold untuk insertion sort
void introsortUtil(int arr[], int low, int high, int depthLimit) {
int size = high - low + 1;
// Gunakan insertion sort untuk array kecil
if (size < SIZE_THRESHOLD) {
insertionSortRange(arr, low, high);
return;
}
// Jika kedalaman rekursi melebihi limit, gunakan heap sort
// Ini mencegah worst case O(n²) dari quick sort
if (depthLimit == 0) {
heapSortRange(arr, low, high);
return;
}
// Gunakan quick sort dengan median-of-three
int pi = partitionIntro(arr, low, high);
introsortUtil(arr, low, pi, depthLimit - 1);
introsortUtil(arr, pi + 1, high, depthLimit - 1);
}
void introsort(int arr[], int n) {
// Depth limit = 2 * floor(log2(n))
int depthLimit = 2 * (int)floor(log2(n));
introsortUtil(arr, 0, n - 1, depthLimit);
}
int main() {
int arr[] = {10, 7, 8, 9, 1, 5, 3, 4, 2, 6};
int n = sizeof(arr) / sizeof(arr[0]);
introsort(arr, n);
cout << "Hasil introsort: ";
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
return 0;
}
Komponen Introsort:
| Situasi | Algoritma | Alasan |
|---|---|---|
| Array kecil (< 16) | Insertion Sort | Overhead rendah, cache efficient |
| Kedalaman normal | Quick Sort | Cepat rata-rata |
| Kedalaman berlebihan | Heap Sort | Menjamin O(n log n) |
Keuntungan Introsort:
Kerjakan soal-soal berikut untuk latihan tambahan.
Apa output dari merge sort berikut jika kita trace langkah per langkah?
int arr[] = {9, 3, 7, 5, 6, 4, 8, 2};
mergeSort(arr, 0, 7);
Hitunglah jumlah perbandingan yang dilakukan quick sort untuk array [1, 2, 3, 4, 5] dengan pivot terakhir!
Implementasikan fungsi untuk menghitung jumlah inversion dalam array menggunakan modifikasi merge sort!
Jelaskan bagaimana cara memodifikasi quick sort agar worst case space complexity-nya menjadi O(log n)!
Buatlah implementasi merge sort untuk mengurutkan linked list!
| Konsep | Deskripsi | Kompleksitas |
|---|---|---|
| Merge Sort | Divide-and-conquer: bagi, urutkan rekursif, merge | O(n log n) selalu, O(n) space |
| Quick Sort | Pilih pivot, partition, rekursi | O(n log n) avg, O(n²) worst |
| Partition | Susun elemen relatif terhadap pivot | O(n) |
| Pivot Selection | First, last, random, median-of-three | Mempengaruhi performa |
| Lower Bound | Comparison-based sorting minimal Ω(n log n) | Teorema decision tree |
| Stable Sort | Mempertahankan urutan relatif elemen sama | Merge sort stable, quick sort tidak |
| In-place | Tidak memerlukan memori tambahan signifikan | Quick sort in-place, merge sort tidak |
| 3-way Quick Sort | Tiga partisi untuk data dengan banyak duplikat | Efisien untuk duplikat |
| Introsort | Hybrid: quick sort + heap sort + insertion sort | O(n log n) terjamin |
S1: Array diurutkan melalui 7 operasi merge menghasilkan [2, 3, 4, 5, 6, 7, 8, 9]
S2: Dengan pivot terakhir pada array sudah terurut: 4+3+2+1 = 10 perbandingan
S3: Hitung inversion saat merge: jika L[i] > R[j], semua L[i..n1] membentuk inversion dengan R[j]
S4: Gunakan tail call optimization: selalu rekursi pada subarray lebih kecil, iterasi pada yang lebih besar
S5: Gunakan slow-fast pointer untuk menemukan tengah linked list, merge dua sorted list dengan membandingkan node
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