Stack dan Queue adalah dua struktur data abstrak yang membatasi operasi pada koleksi. Stack beroperasi Last-In-First-Out (LIFO): elemen terakhir yang dimasukkan adalah yang pertama dikeluarkan. Queue beroperasi First-In-First-Out (FIFO): elemen pertama yang dimasukkan adalah yang pertama dikeluarkan. Pada chapter ini kita akan mengimplementasikan keduanya di Kotlin menggunakan ArrayDeque dari standard library, lalu menerapkannya pada dua masalah nyata: cek bracket seimbang dengan stack, dan simulasi antrian loket dengan queue.

Operasi Dasar #

StrukturOperasi utamaKompleksitas
Stackpush, pop, peekO(1)
Queueenqueue, dequeue, peekO(1)

Kedua struktur mendukung operasi O(1) untuk insert dan remove di ujung tertentu. Perbedaannya adalah ujung mana yang dipakai: stack menggunakan ujung yang sama untuk push dan pop (atas), sementara queue menggunakan ujung berbeda untuk enqueue (belakang) dan dequeue (depan).

Implementasi dengan ArrayDeque #

kotlin
// Demo Stack dan Queue: implementasi dengan ArrayDeque dari standard library.

// === Stack: LIFO (Last In First Out) ===
// Push via addLast, Pop via removeLast
class Stack<T> {
    private val deque = ArrayDeque<T>()

    fun push(item: T) {
        deque.addLast(item)
    }

    fun pop(): T? = if (deque.isEmpty()) null else deque.removeLast()

    fun peek(): T? = deque.lastOrNull()

    fun isEmpty(): Boolean = deque.isEmpty()

    fun size(): Int = deque.size

    fun toList(): List<T> = deque.toList()
}

// === Queue: FIFO (First In First Out) ===
// Enqueue via addLast, Dequeue via removeFirst
class Queue<T> {
    private val deque = ArrayDeque<T>()

    fun enqueue(item: T) {
        deque.addLast(item)
    }

    fun dequeue(): T? = if (deque.isEmpty()) null else deque.removeFirst()

    fun peek(): T? = deque.firstOrNull()

    fun isEmpty(): Boolean = deque.isEmpty()

    fun size(): Int = deque.size

    fun toList(): List<T> = deque.toList()
}

// === Aplikasi Stack: cek bracket seimbang ===
fun bracketSeimbang(ekspresi: String): Boolean {
    val stack = Stack<Char>()
    val pasangan = mapOf(')' to '(', '}' to '{', ']' to '[')
    for (ch in ekspresi) {
        when (ch) {
            '(', '{', '[' -> stack.push(ch)
            ')', '}', ']' -> {
                val top = stack.pop() ?: return false
                if (pasangan[ch] != top) return false
            }
        }
    }
    return stack.isEmpty()
}

// === Aplikasi Queue: simulasi antrian loket ===
data class Pelanggan(val nama: String, val waktuLayanan: Int)

fun simulasiAntrian(pelanggan: List<Pelanggan>): List<String> {
    val queue = Queue<Pelanggan>()
    pelanggan.forEach { queue.enqueue(it) }

    val log = mutableListOf<String>()
    var totalTunggu = 0
    while (!queue.isEmpty()) {
        val p = queue.dequeue()!!
        log.add("Melayani ${p.nama} (waktu layanan ${p.waktuLayanan}s, tunggu ${totalTunggu}s)")
        totalTunggu += p.waktuLayanan
    }
    log.add("Total waktu layanan: ${totalTunggu}s")
    return log
}

fun main() {
    println("=== Demo Stack dan Queue ===")

    println("--- Stack (LIFO) ---")
    val stack = Stack<Int>()
    stack.push(10)
    stack.push(20)
    stack.push(30)
    println("Setelah push 10, 20, 30: ${stack.toList()}")
    println("Peek (atas): ${stack.peek()}")
    println("Pop: ${stack.pop()}")
    println("Pop: ${stack.pop()}")
    println("Sisa: ${stack.toList()}")

    println()
    println("--- Queue (FIFO) ---")
    val queue = Queue<String>()
    queue.enqueue("Budi")
    queue.enqueue("Siti")
    queue.enqueue("Dewi")
    println("Setelah enqueue Budi, Siti, Dewi: ${queue.toList()}")
    println("Peek (depan): ${queue.peek()}")
    println("Dequeue: ${queue.dequeue()}")
    println("Dequeue: ${queue.dequeue()}")
    println("Sisa: ${queue.toList()}")

    println()
    println("--- Aplikasi: cek bracket ---")
    val ekspresi = listOf("(a + b) * [c - {d / e}]", "((())", "([]})", "()", "")
    for (e in ekspresi) {
        println("\"$e\" -> ${if (bracketSeimbang(e)) "seimbang" else "tidak seimbang"}")
    }

    println()
    println("--- Aplikasi: simulasi antrian loket ---")
    val pelanggan = listOf(
        Pelanggan("Budi", 5),
        Pelanggan("Siti", 3),
        Pelanggan("Dewi", 7),
        Pelanggan("Andi", 2)
    )
    simulasiAntrian(pelanggan).forEach { println(it) }
}

Output yang Dihasilkan:

text
=== Demo Stack dan Queue ===
--- Stack (LIFO) ---
Setelah push 10, 20, 30: [10, 20, 30]
Peek (atas): 30
Pop: 30
Pop: 20
Sisa: [10]

--- Queue (FIFO) ---
Setelah enqueue Budi, Siti, Dewi: [Budi, Siti, Dewi]
Peek (depan): Budi
Dequeue: Budi
Dequeue: Siti
Sisa: [Dewi]

--- Aplikasi: cek bracket ---
"(a + b) * [c - {d / e}]" -> seimbang
"((())" -> tidak seimbang
"([]})" -> tidak seimbang
"()" -> seimbang
"" -> seimbang

--- Aplikasi: simulasi antrian loket ---
Melayani Budi (waktu layanan 5s, tunggu 0s)
Melayani Siti (waktu layanan 3s, tunggu 5s)
Melayani Dewi (waktu layanan 7s, tunggu 8s)
Melayani Andi (waktu layanan 2s, tunggu 15s)
Total waktu layanan: 17s

Bedah Kode Baris-per-Baris #

Mari kita amati bagian kunci:

  1. Baris 6-22 (Stack): Implementasi stack dengan ArrayDeque. push memakai addLast (tambah di ujung), pop memakai removeLast (ambil dari ujung yang sama). Inilah inti LIFO.
  2. Baris 25-41 (Queue): Implementasi queue juga dengan ArrayDeque, tetapi enqueue memakai addLast (tambah di belakang) dan dequeue memakai removeFirst (ambil dari depan). Inilah inti FIFO.
  3. Baris 45-58 (bracketSeimbang): Stack cocok untuk mencocokkan pasangan bracket karena bracket terakhir dibuka harus yang pertama ditutup. Saat menemukan bracket tutup, kita pop stack dan bandingkan dengan pasangan yang diharapkan.
  4. Baris 61-75 (simulasiAntrian): Queue cocok untuk antrian karena pelanggan dilayani sesuai urutan kedatangan. Variabel totalTunggu mengakumulasi waktu tunggu yang harus ditanggung pelanggan berikutnya.
  5. Baris 93-97 (pemakaian queue): Pelanggan Budi datang pertama dan dilayani pertama dengan waktu tunggu 0. Siti menunggu 5 detik (waktu layanan Budi), Dewi menunggu 8 detik (5+3), dan seterusnya.

Aplikasi Praktis #

StrukturAplikasi umum
StackUndo/redo editor, pemanggilan function, evaluasi ekspresi postfix, DFS
QueueAntrian pesan, BFS, scheduling task, buffering stream

Stack adalah struktur alami untuk semua yang melibatkan “kembali ke keadaan sebelumnya” (undo, back button, return dari function). Queue adalah struktur alami untuk semua yang melibatkan “siapa datang lebih dulu dilayani lebih dulu” (antrian, scheduling).

Penutup #

Stack dan Queue membatasi operasi pada koleksi untuk menegakkan disiplin akses LIFO atau FIFO. Kotlin ArrayDeque memberikan implementasi yang efisien untuk keduanya. Pada chapter berikutnya kita akan membahas Hash Table: struktur data yang menukar memori dengan akses rata-rata O(1).

Referensi #

Referensi Lanjutan #