Array dan Linked List adalah dua struktur data dasar yang menjadi pondasi hampir semua struktur data lain. Array menawarkan akses acak O(1) tetapi insert/delete O(n). Linked List menawarkan insert/delete O(1) pada posisi yang diketahui tetapi akses acak O(n). Pada chapter ini kita akan mengimplementasikan LinkedList manual di Kotlin dan membandingkan kinerjanya dengan Array. Pembaca akan melihat kapan memilih satu di atas yang lain berdasarkan pola akses data.
Perbandingan Array vs Linked List #
| Operasi | Array | Linked List |
|---|---|---|
| Akses by index | O(1) | O(n) |
| Insert di awal | O(n) (geser) | O(1) |
| Insert di akhir | O(1) amortized | O(n) (atau O(1) dengan tail pointer) |
| Insert di tengah | O(n) (geser) | O(1) (bila node diketahui) |
| Hapus by index | O(n) (geser) | O(n) (cari) + O(1) (unlink) |
| Memori per elemen | 4 byte (Int) | 16+ byte (data + reference) |
Array menang di akses acak dan kepadatan memori. Linked List menang di insert/delete di posisi yang sudah diketahui (misalnya saat memegang reference ke node).
Implementasi LinkedList Manual #
// Demo Array dan Linked List: perbandingan operasi dan implementasi LinkedList.
// === Array: akses random O(1) ===
fun demoArray() {
val arr = intArrayOf(10, 20, 30, 40, 50)
println("Array: ${arr.toList()}")
println("Akses index 0 (O(1)): ${arr[0]}")
println("Akses index 4 (O(1)): ${arr[4]}")
// Insert di tengah: O(n) karena elemen setelahnya harus digeser
val mutable = arr.toMutableList()
mutable.add(2, 25)
println("Setelah insert 25 di index 2 (O(n)): $mutable")
val arrLarge = IntArray(1000000) { it }
val t0 = System.nanoTime()
val x = arrLarge[500000]
val t1 = System.nanoTime()
println("Akses random elemen ke-500000 (O(1)): $x dalam ${(t1 - t0)} ns")
}
// === Implementasi LinkedList manual ===
class Node<T>(var data: T, var next: Node<T>? = null)
class LinkedList<T> {
private var head: Node<T>? = null
private var size = 0
fun size(): Int = size
// Tambah di akhir: O(n) karena harus traversing ke tail
fun tambahAkhir(data: T) {
val node = Node(data)
if (head == null) {
head = node
} else {
var curr = head
while (curr?.next != null) curr = curr.next
curr?.next = node
}
size++
}
// Tambah di awal: O(1)
fun tambahAwal(data: T) {
val node = Node(data, head)
head = node
size++
}
// Akses by index: O(n) karena harus traversing
fun akses(index: Int): T? {
if (index < 0 || index >= size) return null
var curr = head
repeat(index) { curr = curr?.next }
return curr?.data
}
// Hapus by index: O(n) karena traversing + unlink
fun hapus(index: Int): Boolean {
if (index < 0 || index >= size) return false
if (index == 0) {
head = head?.next
} else {
var prev = head
repeat(index - 1) { prev = prev?.next }
prev?.next = prev?.next?.next
}
size--
return true
}
fun toList(): List<T> {
val result = mutableListOf<T>()
var curr = head
while (curr != null) {
result.add(curr.data)
curr = curr.next
}
return result
}
}
fun main() {
println("=== Demo Array dan Linked List ===")
println("--- Array ---")
demoArray()
println()
println("--- LinkedList ---")
val list = LinkedList<Int>()
list.tambahAkhir(10)
list.tambahAkhir(20)
list.tambahAkhir(30)
println("Setelah tambah akhir 10, 20, 30: ${list.toList()}")
list.tambahAwal(5)
println("Setelah tambah awal 5: ${list.toList()}")
println("Akses index 2 (O(n)): ${list.akses(2)}")
println("Akses index 10 (out of range): ${list.akses(10)}")
list.hapus(1)
println("Setelah hapus index 1: ${list.toList()}")
println("Ukuran: ${list.size()}")
println()
println("--- Perbandingan ukuran besar ---")
val arrLarge = IntArray(100000) { it }
val t0 = System.nanoTime()
val arrEl = arrLarge[99999]
val t1 = System.nanoTime()
println("Array akses index 99999 (O(1)): $arrEl dalam ${(t1 - t0)} ns")
val linkedLarge = LinkedList<Int>()
for (i in 1..1000) list.tambahAkhir(i)
val t2 = System.nanoTime()
val linkedEl = list.akses(999)
val t3 = System.nanoTime()
println("LinkedList akses index 999 (O(n)): $linkedEl dalam ${(t3 - t2)} ns")
}Output yang Dihasilkan:
=== Demo Array dan Linked List ===
--- Array ---
Array: [10, 20, 30, 40, 50]
Akses index 0 (O(1)): 10
Akses index 4 (O(1)): 50
Setelah insert 25 di index 2 (O(n)): [10, 20, 25, 30, 40, 50]
Akses random elemen ke-500000 (O(1)): 500000 dalam 76 ns
--- LinkedList ---
Setelah tambah akhir 10, 20, 30: [10, 20, 30]
Setelah tambah awal 5: [5, 10, 20, 30]
Akses index 2 (O(n)): 20
Akses index 10 (out of range): null
Setelah hapus index 1: [5, 20, 30]
Ukuran: 3
--- Perbandingan ukuran besar ---
Array akses index 99999 (O(1)): 99999 dalam 334 ns
LinkedList akses index 999 (O(n)): 997 dalam 31951 nsBedah Kode Baris-per-Baris #
Mari kita amati bagian kunci:
- Baris 4-15 (
demoArray): Akses elemen array by index adalah satu operasiO(1). Insert di tengahmutable.add(2, 25)memerlukan menggeser elemen setelahnya, sehinggaO(n). - Baris 18-19 (
Node): Setiap node menyimpan data dan reference ke node berikutnya. Linked list tidak menyimpan index; akses harus dari head. - Baris 21-37 (
tambahAkhir): Untuk menambah di akhir, kita harus traverse dari head sampai node terakhir. KompleksitasO(n). Implementasi produksi biasanya menyimpantailpointer untuk membuat operasi iniO(1). - Baris 39-43 (
tambahAwal): Untuk menambah di awal, kita hanya membuat node baru dengannextmenunjuk ke head lama. KompleksitasO(1)—keunggulan utama linked list. - Baris 45-51 (
akses): Akses by index harus traverse dari head. Untuk indexk, butuhklangkah. KompleksitasO(n). Inilah kelemahan utama linked list dibanding array.
Kapan Memilih Apa #
| Pola akses | Pilih |
|---|---|
| Sering akses by index | Array |
| Sering insert/delete di awal/tengah | Linked List |
| Data berukuran besar tetap (image, audio) | Array (kontigu di memori) |
| Banyak modifikasi struktural (queue, deque) | Linked List |
| Memori sangat terbatas | Array (tidak ada overhead reference) |
Penutup #
Array unggul di akses acak dan kepadatan memori; Linked List unggul di insert/delete di posisi yang diketahui. Pemilihan tergantung pola akses data yang dominan dalam aplikasi. Pada chapter berikutnya kita akan membahas Stack dan Queue: dua struktur data yang dibangun di atas array atau linked list dengan operasi yang dibatasi.
Referensi #
Referensi Lanjutan #
- Kotlin Collections overview
— pengantar koleksi Kotlin yang merangkum
ArrayListsebagai implementasiListdefault - ArrayDeque stdlib API
— rujukan
ArrayDequeuntuk operasi push/pop di kedua ujung secara efisien