Sorting adalah proses menyusun elemen data sesuai urutan tertentu, biasanya menaik (ascending). Banyak algoritma lain—seperti binary search, deduplikasi, dan penyusunan laporan—bergantung pada data yang sudah terurut. Pada chapter ini kita akan mengimplementasikan enam algoritma klasik di Kotlin: tiga algoritma sederhana dengan kompleksitas O(n²) (bubble, selection, insertion) dan tiga algoritma efisien dengan kompleksitas O(n log n) (merge, quick, heap). Pembaca akan melihat mengapa insertion sort unggul untuk data hampir terurut, dan kapan sorted() bawaan Kotlin menjadi pilihan paling praktis.
Algoritma O(n²): Sederhana tapi Lambat #
Tiga algoritma berikut mudah dipahami dan diimplementasikan, namun kompleksitas waktunya tumbuh kuadratik terhadap ukuran data. Mereka cocok untuk data kecil (kurang dari 100 elemen) atau sebagai dasar pembelajaran.
| Algoritma | Cara kerja | Best case | Worst case |
|---|---|---|---|
| Bubble sort | Tukar pasangan tetangga yang tidak urut | O(n) (sudah urut) | O(n²) |
| Selection sort | Pilih minimum, tukar ke depan | O(n²) | O(n²) |
| Insertion sort | Sisipkan elemen ke posisi tepat | O(n) (hampir urut) | O(n²) |
Algoritma O(n log n): Efisien untuk Data Besar #
| Algoritma | Strategi | Average | Worst | Stable |
|---|---|---|---|---|
| Merge sort | Bagi dua, urutkan, gabung | O(n log n) | O(n log n) | Ya |
| Quick sort | Pivot partisi | O(n log n) | O(n²) | Tidak |
| Heap sort | Bangun max-heap, ekstrak maks | O(n log n) | O(n log n) | Tidak |
Implementasi Enam Algoritma Sort #
// Demo Sorting Algorithms: bubble, selection, insertion, merge, quick, heap sort.
// === Bubble sort: O(n^2) ===
fun bubbleSort(arr: IntArray): IntArray {
val a = arr.copyOf()
val n = a.size
for (i in 0 until n - 1) {
for (j in 0 until n - i - 1) {
if (a[j] > a[j + 1]) {
val t = a[j]; a[j] = a[j + 1]; a[j + 1] = t
}
}
}
return a
}
// === Selection sort: O(n^2) ===
fun selectionSort(arr: IntArray): IntArray {
val a = arr.copyOf()
val n = a.size
for (i in 0 until n - 1) {
var minIdx = i
for (j in i + 1 until n) {
if (a[j] < a[minIdx]) minIdx = j
}
if (minIdx != i) {
val t = a[i]; a[i] = a[minIdx]; a[minIdx] = t
}
}
return a
}
// === Insertion sort: O(n^2), efisien untuk data hampir sorted ===
fun insertionSort(arr: IntArray): IntArray {
val a = arr.copyOf()
for (i in 1 until a.size) {
val key = a[i]
var j = i - 1
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j]
j--
}
a[j + 1] = key
}
return a
}
// === Merge sort: O(n log n) ===
fun mergeSort(arr: IntArray): IntArray {
if (arr.size <= 1) return arr.copyOf()
val mid = arr.size / 2
val left = mergeSort(arr.sliceArray(0 until mid))
val right = mergeSort(arr.sliceArray(mid until arr.size))
return merge(left, right)
}
private fun merge(left: IntArray, right: IntArray): IntArray {
val result = IntArray(left.size + right.size)
var i = 0; var j = 0; var k = 0
while (i < left.size && j < right.size) {
if (left[i] <= right[j]) {
result[k++] = left[i++]
} else {
result[k++] = right[j++]
}
}
while (i < left.size) result[k++] = left[i++]
while (j < right.size) result[k++] = right[j++]
return result
}
// === Quick sort: avg O(n log n), worst O(n^2) ===
fun quickSort(arr: IntArray): IntArray {
val a = arr.copyOf()
quickSortRec(a, 0, a.lastIndex)
return a
}
private fun quickSortRec(arr: IntArray, low: Int, high: Int) {
if (low >= high) return
val pivot = arr[high]
var i = low - 1
for (j in low until high) {
if (arr[j] <= pivot) {
i++
val t = arr[i]; arr[i] = arr[j]; arr[j] = t
}
}
val t = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = t
val p = i + 1
quickSortRec(arr, low, p - 1)
quickSortRec(arr, p + 1, high)
}
// === Heap sort: O(n log n) ===
fun heapSort(arr: IntArray): IntArray {
val a = arr.copyOf()
val n = a.size
// Build max heap
for (i in n / 2 - 1 downTo 0) heapify(a, n, i)
// Extract elements one by one
for (i in n - 1 downTo 1) {
val t = a[0]; a[0] = a[i]; a[i] = t
heapify(a, i, 0)
}
return a
}
private fun heapify(arr: IntArray, n: Int, i: Int) {
var largest = i
val left = 2 * i + 1
val right = 2 * i + 2
if (left < n && arr[left] > arr[largest]) largest = left
if (right < n && arr[right] > arr[largest]) largest = right
if (largest != i) {
val t = arr[i]; arr[i] = arr[largest]; arr[largest] = t
heapify(arr, n, largest)
}
}
fun main() {
println("=== Demo Sorting Algorithms ===")
val data = intArrayOf(64, 34, 25, 12, 22, 11, 90, 5, 77, 30)
println("Data asli: ${data.toList()}")
println()
println("--- Algoritma O(n^2) ---")
println("Bubble sort: ${bubbleSort(data).toList()}")
println("Selection sort: ${selectionSort(data).toList()}")
println("Insertion sort: ${insertionSort(data).toList()}")
println()
println("--- Algoritma O(n log n) ---")
println("Merge sort: ${mergeSort(data).toList()}")
println("Quick sort: ${quickSort(data).toList()}")
println("Heap sort: ${heapSort(data).toList()}")
println()
println("--- Kotlin stdlib sorted() ---")
println("sorted(): ${data.sorted()}")
println()
println("--- String sorting ---")
val names = listOf("Budi", "andi", "Siti", "dewi", "Rina")
println("Names: $names")
println("sorted (case-sensitive): ${names.sorted()}")
println("sortedIgnoringCase: ${names.sortedWith(String.CASE_INSENSITIVE_ORDER)}")
println()
println("--- Sort by key (stable) ---")
data class Produk(val nama: String, val harga: Int)
val produk = listOf(
Produk("Buku", 15000),
Produk("Pulpen", 5000),
Produk("Penggaris", 3000),
Produk("Buku Tulis", 15000)
)
println("Sort by harga (asc): ${produk.sortedBy { it.harga }}")
println("Sort by harga (desc): ${produk.sortedByDescending { it.harga }}")
println("Sort by nama: ${produk.sortedBy { it.nama }}")
}Output yang Dihasilkan:
=== Demo Sorting Algorithms ===
Data asli: [64, 34, 25, 12, 22, 11, 90, 5, 77, 30]
--- Algoritma O(n^2) ---
Bubble sort: [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
Selection sort: [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
Insertion sort: [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
--- Algoritma O(n log n) ---
Merge sort: [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
Quick sort: [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
Heap sort: [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
--- Kotlin stdlib sorted() ---
sorted(): [5, 11, 12, 22, 25, 30, 34, 64, 77, 90]
--- String sorting ---
Names: [Budi, andi, Siti, dewi, Rina]
sorted (case-sensitive): [Budi, Rina, Siti, andi, dewi]
sortedIgnoringCase: [andi, Budi, dewi, Rina, Siti]
--- Sort by key (stable) ---
Sort by harga (asc): [Produk(nama=Penggaris, harga=3000), Produk(nama=Pulpen, harga=5000), Produk(nama=Buku, harga=15000), Produk(nama=Buku Tulis, harga=15000)]
Sort by harga (desc): [Produk(nama=Buku, harga=15000), Produk(nama=Buku Tulis, harga=15000), Produk(nama=Pulpen, harga=5000), Produk(nama=Penggaris, harga=3000)]
Sort by nama: [Produk(nama=Buku, harga=15000), Produk(nama=Buku Tulis, harga=15000), Produk(nama=Penggaris, harga=3000), Produk(nama=Pulpen, harga=5000)]Bedah Kode Baris-per-Baris #
Mari kita amati bagian kunci:
bubbleSort(baris 4-15): Loop luarimengurangi jumlah pasangan yang diperiksa tiap iterasi. Loop dalamjmembandingkana[j]dengana[j+1]dan menukar bila tidak urut. Setiap iterasi luar “menggelembungkan” elemen terbesar ke akhir array.selectionSort(baris 18-31): Loop dalam mencari indeksminIdxdari elemen minimum di rentang[i..n). Setelah loop, satu kali pertukaran menempatkan minimum di posisii. Jumlah pertukaran hanyaO(n), tetapi jumlah perbandingan tetapO(n²).insertionSort(baris 34-46): Untuk setiap elemen ke-i, sisipkan ke posisi tepat di bagian kiri yang sudah terurut. Loopwhilemenggeser elemen yang lebih besar ke kanan hingga menemukan posisi yang sesuai. Sangat cepat untuk data hampir terurut karena loop dalam hanya berjalan sedikit.mergeSort(baris 49-70): Membagi array menjadi dua bagian rekursif hingga tinggal satu elemen (base casesize <= 1). Functionmergemenggabungkan dua array terurut dengan dua pointer berjalan paralel. KompleksitasO(n log n)di semua kasus karena pembagian selalu setara.quickSortRec(baris 79-93): Memilih pivot sebagai elemen terakhir (arr[high]). Loopfor (j in low until high)mempartisi elemen menjadi<= pivotdi kiri dan> pivotdi kanan. Indeksimelacak batas antara dua partisi. Setelah partisi, pivot ditukar ke posisii+1, lalu rekursi pada dua sisi. Worst caseO(n²)terjadi bila pivot selalu ekstrem (misalnya data sudah terurut dan pivot selalu elemen terbesar).heapSort(baris 96-119): Fase pertama membangun max-heap dengan memanggilheapifydari indeksn/2 - 1ke0. Fase kedua menukar root (maksimum) dengan elemen akhir, mengecilkan ukuran heap, lalu memanggilheapifypada root untuk memulihkan sifat heap.heapifymembandingkan parent dengan kedua anak, menukar dengan yang terbesar bila perlu, lalu rekursi ke bawah.
Function sorted() Bawaan Kotlin #
Kotlin stdlib menyediakan sorted(), sortedBy { }, dan sortedWith(comparator) yang stabil dan optimal untuk kasus umum. Implementasi internal memakai TimSort—varian merge sort adaptif dengan kompleksitas O(n log n). Untuk produksi, lebih baik gunakan API ini daripada implementasi manual.
Penutup #
Pemilihan algoritma sorting bergantung pada ukuran data, sifat data (sudah hampir urut atau acak), dan kebutuhan stabilitas. Untuk data kecil dan pembelajaran, bubble/selection/insertion cukup; untuk data besar, gunakan merge/quick/heap atau langsung sorted() bawaan Kotlin. Pada chapter berikutnya kita akan membahas recursion dan backtracking sebagai fondasi banyak algoritma lanjutan.
Referensi #
Referensi Lanjutan #
- Kotlin Collections overview
— pengantar koleksi Kotlin yang merangkum
sorted(),sortedBy(), dansortedDescending()