Heap adalah tree biner lengkap yang memenuhi properti heap: untuk min-heap, setiap parent lebih kecil dari anak-anaknya; untuk max-heap, parent lebih besar. Heap biasanya diimplementasikan dengan array karena tree-nya lengkap (setiap level terisi penuh kecuali mungkin level terakhir). Pada chapter ini kita akan mengimplementasikan min-heap manual dengan operasi insert dan extract-min, lalu menggunakan java.util.PriorityQueue untuk top-K selection. Pembaca akan melihat mengapa heap optimal untuk antrian prioritas.
Operasi Heap dan Kompleksitas #
| Operasi | Kompleksitas | Penjelasan |
|---|---|---|
peek (lihat root) | O(1) | Akses elemen pertama array |
insert | O(log n) | Sift-up: bandingkan dengan parent, swap bila perlu |
extractMin/extractMax | O(log n) | Sift-down: ganti root dengan elemen terakhir, lalu sift-down |
buildHeap (dari array) | O(n) | Sift-down dari node tengah ke root |
Heap menyeimbangkan insert cepat (O(log n)) dengan akses ke elemen minimum/maksimum yang juga cepat (O(1) peek, O(log n) extract). Inilah alasan heap dipakai untuk priority queue dan algoritma seperti Dijkstra.
Implementasi Min-Heap Manual #
// Demo Heap dan Priority Queue: min-heap implementation + java.util.PriorityQueue.
// === Min-Heap implementation ===
class MinHeap {
private val data = mutableListOf<Int>()
fun size(): Int = data.size
fun isEmpty(): Boolean = data.isEmpty()
fun peek(): Int? = data.firstOrNull()
// Insert: tambah di akhir, lalu sift-up
fun insert(value: Int) {
data.add(value)
siftUp(data.lastIndex)
}
private fun siftUp(idx: Int) {
var i = idx
while (i > 0) {
val parent = (i - 1) / 2
if (data[i] < data[parent]) {
val t = data[i]; data[i] = data[parent]; data[parent] = t
i = parent
} else break
}
}
// Extract min: ambil root, pindahkan elemen terakhir ke root, lalu sift-down
fun extractMin(): Int? {
if (data.isEmpty()) return null
val min = data[0]
val last = data.removeAt(data.lastIndex)
if (data.isNotEmpty()) {
data[0] = last
siftDown(0)
}
return min
}
private fun siftDown(idx: Int) {
var i = idx
val n = data.size
while (true) {
val left = 2 * i + 1
val right = 2 * i + 2
var smallest = i
if (left < n && data[left] < data[smallest]) smallest = left
if (right < n && data[right] < data[smallest]) smallest = right
if (smallest != i) {
val t = data[i]; data[i] = data[smallest]; data[smallest] = t
i = smallest
} else break
}
}
fun toList(): List<Int> = data.toList()
}
// === Top-K via min-heap ===
fun topKLargest(arr: IntArray, k: Int): List<Int> {
val minHeap = MinHeap()
for (x in arr) {
if (minHeap.size() < k) {
minHeap.insert(x)
} else if (minHeap.peek()!! < x) {
minHeap.extractMin()
minHeap.insert(x)
}
}
// K elemen terbesar ada di heap, urutkan descending
val result = mutableListOf<Int>()
while (!minHeap.isEmpty()) result.add(minHeap.extractMin()!!)
return result.reversed()
}
fun main() {
println("=== Demo Heap dan Priority Queue ===")
println("--- Min-Heap manual ---")
val heap = MinHeap()
listOf(10, 5, 20, 3, 8, 15, 1).forEach { heap.insert(it) }
println("Heap internal array: ${heap.toList()}")
println("Peek (min): ${heap.peek()}")
println("Extract min berurutan:")
val sorted = mutableListOf<Int>()
while (!heap.isEmpty()) sorted.add(heap.extractMin()!!)
println("Hasil extract: $sorted")
println()
println("--- java.util.PriorityQueue ---")
val pq = java.util.PriorityQueue<Int>()
listOf(10, 5, 20, 3, 8, 15, 1).forEach { pq.add(it) }
println("Poll berurutan (min-heap default):")
val pqSorted = mutableListOf<Int>()
while (pq.isNotEmpty()) pqSorted.add(pq.poll())
println("$pqSorted")
println()
println("--- Max-heap via reverseOrder ---")
val maxPq = java.util.PriorityQueue<Int>(java.util.Collections.reverseOrder())
listOf(10, 5, 20, 3, 8, 15, 1).forEach { maxPq.add(it) }
val maxSorted = mutableListOf<Int>()
while (maxPq.isNotEmpty()) maxSorted.add(maxPq.poll())
println("Poll (max-heap): $maxSorted")
println()
println("--- Top-K elemen terbesar ---")
val data = intArrayOf(4, 10, 8, 3, 15, 7, 20, 1, 12, 9)
println("Data: ${data.toList()}")
println("Top 3 terbesar: ${topKLargest(data, 3)}")
println("Top 5 terbesar: ${topKLargest(data, 5)}")
}Output yang Dihasilkan:
=== Demo Heap dan Priority Queue ===
--- Min-Heap manual ---
Heap internal array: [1, 5, 3, 10, 8, 20, 15]
Peek (min): 1
Extract min berurutan:
Hasil extract: [1, 3, 5, 8, 10, 15, 20]
--- java.util.PriorityQueue ---
Poll berurutan (min-heap default):
[1, 3, 5, 8, 10, 15, 20]
--- Max-heap via reverseOrder ---
Poll (max-heap): [20, 15, 10, 8, 5, 3, 1]
--- Top-K elemen terbesar ---
Data: [4, 10, 8, 3, 15, 7, 20, 1, 12, 9]
Top 3 terbesar: [20, 15, 12]
Top 5 terbesar: [20, 15, 12, 10, 9]Bedah Kode Baris-per-Baris #
Mari kita amati bagian kunci:
- Baris 5-7 (
MinHeap): Heap disimpan sebagaiMutableList. Untuk node di indexi, parent ada di(i-1)/2, anak kiri di2i+1, anak kanan di2i+2. Inilah properti tree biner lengkap. - Baris 13-23 (
insert+siftUp): Insert menambah elemen di akhir array, lalu sift-up membandingkan dengan parent dan swap bila lebih kecil. Proses berulang sampai parent lebih kecil atau mencapai root. - Baris 25-36 (
extractMin+siftDown): Extract-min mengambil root (elemen terkecil), lalu memindahkan elemen terakhir ke root. Sift-down membandingkan root dengan anak terkecil dan swap bila perlu, berulang sampai property heap pulih. - Baris 55-66 (
topKLargest): Untuk menemukan K elemen terbesar, kita maintain min-heap berukuran K. Bila elemen baru lebih besar dari minimum heap, kita keluarkan minimum dan masukkan elemen baru. KompleksitasO(n log k), jauh lebih baik dari sort fullO(n log n)bilakkecil. - Baris 86-89 (max-heap via reverseOrder):
java.util.PriorityQueueadalah min-heap secara default. Untuk max-heap, gunakanCollections.reverseOrder()sebagai comparator.
Use Case Heap #
| Skenario | Algoritma |
|---|---|
| Top-K elemen | Min-heap berukuran K |
| K elemen terkecil | Max-heap berukuran K |
| Antrian dengan prioritas | PriorityQueue dengan comparator |
| Shortest path (Dijkstra) | Min-heap berdasarkan jarak |
| Median stream data | Dua heap (max-heap untuk setengah bawah, min-heap untuk atas) |
Penutup #
Heap adalah struktur data yang optimal untuk operasi min/max dengan insert yang sering. Min-heap dan max-heap adalah dua varian, dengan java.util.PriorityQueue menyediakan keduanya via comparator. Pada chapter berikutnya kita akan naik ke struktur non-linier yang lebih umum: Graph.
Referensi #
Referensi Lanjutan #
- ArrayDeque stdlib API
— rujukan
ArrayDequeuntuk membangun priority queue sederhana di atas struktur antrian