Pemrograman · Struktur data
Binary heap: priority queue, kode Python, dan kompleksitasnya
Jawaban singkat
Binary heap adalah complete binary tree yang disimpan dalam array di mana setiap induk lebih kecil atau sama dengan anak-anaknya (min-heap) atau lebih besar atau sama dengan mereka (max-heap). Item terkecil selalu berada di indeks 0, sehingga bisa dibaca dalam O(1), dan disisipkan atau dihapus dalam O(log n). Heap adalah cara standar untuk mengimplementasikan priority queue; di Python, gunakan modul heapq.
Masalah apa yang diselesaikan heap
Banyak program berulang kali membutuhkan item paling mendesak dari kumpulan yang terus berubah: event berikutnya dalam simulasi, node terdekat yang belum dikunjungi pada algoritma Dijkstra, tugas dengan prioritas tertinggi, atau k nilai terbesar dalam sebuah stream. Mengurutkan seluruh kumpulan setiap kali insert butuh O(n log n) setiap kali. Sorted list membuat penghapusan murah tetapi insert O(n). Heap membuat insert dan remove-min sama-sama O(log n), persis yang dibutuhkan priority queue.
Ide dasarnya: tree yang disimpan dalam array
Heap adalah complete binary tree: setiap level penuh kecuali mungkin yang terakhir, yang diisi dari kiri. Bentuk itu memungkinkan kita menyimpan tree dalam list biasa tanpa pointer.
| Hubungan | Rumus indeks (berbasis 0) |
|---|---|
| Anak kiri dari i | 2 * i + 1 |
| Anak kanan dari i | 2 * i + 2 |
| Induk dari i | (i - 1) // 2 |
Dua operasi perbaikan menjaga heap property. Sift up: setelah menambahkan item baru di akhir, tukar dengan induknya selama ia lebih kecil. Sift down: setelah memindahkan item terakhir ke root, tukar dengan anak yang lebih kecil selama ia lebih besar dari anak itu. Setiap operasi bergerak di satu jalur root-ke-leaf, jadi paling banyak sekitar log2(n) pertukaran.
Contoh langkah demi langkah: push dan pop
Mulai dari min-heap [2, 5, 3, 9, 6] (2 adalah root, anak-anaknya 5 dan 3, dan 5 punya anak 9 dan 6).
- Push 1: tambahkan di akhir, menjadi
[2, 5, 3, 9, 6, 1]. Indeks 5 punya induk di indeks 2 (nilai 3). - 1 lebih kecil dari 3, tukar:
[2, 5, 1, 9, 6, 3]. Sekarang indeks 2 punya induk di indeks 0 (nilai 2). - 1 lebih kecil dari 2, tukar:
[1, 5, 2, 9, 6, 3]. Sudah sampai root, berhenti. - Pop: hapus root 1, pindahkan item terakhir 3 ke root:
[3, 5, 2, 9, 6]. - Anak root adalah 5 dan 2; yang lebih kecil adalah 2, dan 3 lebih besar, tukar:
[2, 5, 3, 9, 6]. Indeks 2 tidak punya anak, berhenti. Pop mengembalikan 1.
Implementasi min-heap di Python
Kelas ini menunjukkan mekanismenya. Di kode sebenarnya gunakan heapq, seperti pada bagian berikutnya.
class MinHeap:
def __init__(self):
self.a = []
def push(self, x):
a = self.a
a.append(x)
i = len(a) - 1
while i > 0 and a[i] < a[(i - 1) // 2]: # sift up
p = (i - 1) // 2
a[i], a[p] = a[p], a[i]
i = p
def pop(self):
a = self.a
top, last = a[0], a.pop() # IndexError if empty
if a:
a[0], i = last, 0
while True: # sift down
c = 2 * i + 1
if c + 1 < len(a) and a[c + 1] < a[c]:
c += 1 # pick the smaller child
if c >= len(a) or a[i] <= a[c]:
break
a[i], a[c] = a[c], a[i]
i = c
return top
h = MinHeap()
for x in [5, 3, 9, 1, 6, 2]:
h.push(x)
print([h.pop() for _ in range(6)]) # [1, 2, 3, 5, 6, 9]Priority queue dengan heapq Python
Modul heapq bekerja pada list biasa dan selalu menjaganya sebagai min-heap. heapify mengubah list yang sudah ada menjadi heap di tempat dalam O(n). Untuk memakai prioritas, push tuple: Python membandingkan tuple elemen demi elemen, jadi elemen pertama adalah prioritasnya. Tambahkan counter sebagai tie-breaker agar item dengan prioritas sama tidak perlu dibandingkan langsung.
import heapq
from itertools import count
nums = [7, 2, 9, 4, 1]
heapq.heapify(nums)
print(nums[0], heapq.heappop(nums)) # 1 1
print(heapq.nlargest(2, [7, 2, 9, 4])) # [9, 7]
tasks, order = [], count()
heapq.heappush(tasks, (2, next(order), "write report"))
heapq.heappush(tasks, (1, next(order), "fix outage"))
heapq.heappush(tasks, (2, next(order), "reply to email"))
while tasks:
priority, _, name = heapq.heappop(tasks)
print(priority, name)
# 1 fix outage
# 2 write report
# 2 reply to emailUntuk max-heap, Python 3.14 menambahkan heapq.heapify_max, heapq.heappush_max, dan heapq.heappop_max. Pada versi lama, triknya adalah mem-push key yang dinegasikan, misalnya heapq.heappush(h, -score), lalu menegasikannya lagi saat pop.
Kompleksitas waktu dan ruang
| Operasi | Waktu | Alasan |
|---|---|---|
| Melihat minimum | O(1) | selalu ada di indeks 0 |
| Push (insert) | O(log n) | sift up sepanjang satu jalur |
| Pop minimum | O(log n) | sift down sepanjang satu jalur |
| Heapify n item | O(n) | sebagian besar node dekat dasar dan hanya bergeser sedikit |
| Mencari nilai sembarang | O(n) | heap tidak terurut |
| Heapsort | O(n log n) | heapify, lalu n kali pop |
| Ruang | O(n) | satu array, tanpa pointer |
Membangun heap dengan memanggil push sebanyak n kali butuh O(n log n). heapify melakukan sift down dari induk terakhir sampai root dan butuh O(n), jadi pilih itu jika semua item sudah ada.
Varian dan kesalahan umum
- Max-heap: kode yang sama dengan perbandingan dibalik; di Python, pakai fungsi
_max(3.14 ke atas) atau negasikan key. - d-ary heap: setiap node punya d anak. Tree yang lebih dangkal membuat push lebih murah dan pop sedikit lebih mahal.
- Indexed priority queue / decrease-key:
heapqtidak bisa mengubah prioritas item di tempat. Solusi umumnya (dipakai pada algoritma Dijkstra) adalah mem-push entri baru dan melewati entri usang saat di-pop. - Membandingkan item yang tidak bisa dibandingkan: mem-push
(priority, dict)memunculkanTypeErrorketika dua prioritas sama, karena Python lalu membandingkan dict-nya. Tambahkan counter sebagai elemen kedua tuple. - Menganggap list heap itu terurut. Hanya
heap[0]yang dijamin; gunakansorted(heap)jika Anda butuh semuanya berurutan. - Kesalahan off-by-one saat mencampur rumus berbasis 1 dari buku teks (anak
2idan2i + 1) dengan list Python berbasis 0.
Kapan memakai heap dan soal-soal khasnya
Gunakan heap ketika Anda berulang kali hanya butuh item terkecil atau terbesar dari kumpulan yang terus berubah. Gunakan sorted list atau balanced tree jika Anda juga butuh iterasi terurut atau penghapusan sembarang, dan min() biasa jika Anda hanya butuh minimum satu kali.
Penggunaan klasik: shortest path Dijkstra dan minimum spanning tree Prim, menggabungkan k sorted list (heapq.merge), mencari k item terbesar atau terkecil (pertahankan heap berukuran k, O(n log k)), median berjalan (dua heap), task scheduler dan simulasi berbasis event, serta heapsort.
FAQ
Apa bedanya heap dan priority queue?
Priority queue adalah abstract data type: menyisipkan item dan mengeluarkan yang berprioritas tertinggi. Binary heap adalah struktur data paling umum untuk mengimplementasikannya.
Apakah heapq Python itu min-heap atau max-heap?
Fungsi utamanya seperti heappush dan heappop menjaga min-heap, jadi heappop mengembalikan item terkecil. Python 3.14 menambahkan fungsi _max terpisah untuk max-heap; pada versi lama, simpan angka yang dinegasikan.
Mengapa heapify O(n) dan bukan O(n log n)?
Heapify melakukan sift down pada setiap induk mulai dari bawah. Sekitar setengah node adalah leaf dan tidak bergeser, seperempat bergeser paling banyak satu level, dan seterusnya; total kerjanya berjumlah O(n).
Apakah binary heap sama dengan binary search tree?
Tidak. BST mengurutkan subtree kiri dan kanan sehingga key apa pun bisa dicari dalam O(log n). Heap hanya menjamin setiap induk lebih kecil dari anak-anaknya, jadi minimum ditemukan cepat tetapi mencari key lain itu O(n).
Apakah heapsort stabil?
Tidak. Pertukaran saat sift down bisa mengubah urutan key yang sama. Ia mengurutkan di tempat dalam waktu O(n log n) dengan ruang tambahan O(1).