cv.cm

Pemrograman · Algoritme

Penjelasan heapsort: cara kerja, kode, dan kompleksitas

5 menit baca

Jawaban singkat

Heapsort adalah comparison sort yang pertama-tama menata array menjadi max-heap, yaitu binary tree yang disimpan dalam array dengan setiap parent minimal sebesar anak-anaknya. Lalu ia berulang kali menukar elemen terbesar (root) ke akhir array, mengecilkan heap satu elemen, dan memulihkan sifat heap dengan men-sift root baru ke bawah. Waktunya O(n log n) pada semua kasus dan mengurutkan di tempat dengan ruang tambahan O(1), tetapi tidak stabil dan biasanya lebih lambat daripada quicksort dalam praktik.

Buka Urut baris

Ide dasar dengan kata-kata sederhana

Heapsort adalah selection sort dengan cara yang lebih cepat untuk mencari nilai maksimum. Selection sort memindai seluruh bagian belum terurut, O(n) setiap kali. Heapsort menyimpan bagian belum terurut sebagai heap, sehingga nilai maksimum selalu ada di indeks 0 dan memulihkan urutan setelah mengambilnya hanya berbiaya O(log n).

Binary heap disimpan dalam array biasa tanpa pointer. Untuk elemen di indeks i:

  • anak kiri ada di 2*i + 1 dan anak kanan di 2*i + 2;
  • parent ada di (i - 1) // 2;
  • pada max-heap, setiap parent lebih besar atau sama dengan anak-anaknya, sehingga elemen terbesar ada di indeks 0.
  1. Bangun max-heap dari array dengan men-sift down setiap node non-daun, mulai dari yang terakhir dan bergerak ke indeks 0.
  2. Tukar root (nilai maksimum) dengan elemen terakhir heap. Elemen itu kini berada di tempat akhirnya.
  3. Kecilkan heap satu elemen dan sift root baru ke bawah sampai sifat heap berlaku lagi.
  4. Ulangi langkah 2 dan 3 sampai heap tinggal satu elemen.

Contoh langkah demi langkah

Urutkan [4, 10, 3, 5, 1]. Elemen setelah garis sudah terurut dan final.

LangkahAksiArray
Awal[4, 10, 3, 5, 1]
Bangun 1sift down indeks 1 (10): lebih besar dari anak 5 dan 1, tidak berubah[4, 10, 3, 5, 1]
Bangun 2sift down indeks 0 (4): tukar dengan 10, lalu dengan 5[10, 5, 3, 4, 1] max-heap
Ekstrak 1tukar 10 ke akhir, sift 1 ke bawah: tukar dengan 5, lalu dengan 4[5, 4, 3, 1 | 10]
Ekstrak 2tukar 5 ke akhir, sift 1 ke bawah: tukar dengan 4[4, 1, 3 | 5, 10]
Ekstrak 3tukar 4 ke akhir, sift 3 ke bawah: tidak ada anak yang lebih besar[3, 1 | 4, 5, 10]
Ekstrak 4tukar 3 ke akhir[1 | 3, 4, 5, 10] terurut

Implementasi Python

def sift_down(a, root, end):
    """Restore the max-heap property for a[root..end-1]."""
    while True:
        child = 2 * root + 1
        if child >= end:
            return
        if child + 1 < end and a[child + 1] > a[child]:
            child += 1                      # pick the larger child
        if a[root] >= a[child]:
            return
        a[root], a[child] = a[child], a[root]
        root = child

def heapsort(a):
    """Sort list a in place, ascending."""
    n = len(a)
    for i in range(n // 2 - 1, -1, -1):     # build the max-heap
        sift_down(a, i, n)
    for end in range(n - 1, 0, -1):
        a[0], a[end] = a[end], a[0]         # move the max to the end
        sift_down(a, 0, end)
    return a

print(heapsort([4, 10, 3, 5, 1]))   # [1, 3, 4, 5, 10]
print(heapsort([2, 2, 1]))          # [1, 2, 2]

Modul heapq Python menyediakan min-heap pada list biasa. Mengeluarkan setiap elemen menghasilkan list terurut, yang merupakan heapsort singkat (tidak in-place):

import heapq

def heapsort_heapq(items):
    h = list(items)
    heapq.heapify(h)                       # O(n)
    return [heapq.heappop(h) for _ in range(len(h))]

print(heapsort_heapq([4, 10, 3, 5, 1]))     # [1, 3, 4, 5, 10]
print(heapq.nlargest(2, [4, 10, 3, 5, 1]))  # [10, 5]

Kompleksitas waktu dan ruang

KasusWaktuAlasan
Membangun heapO(n)sebagian besar node berada dekat dasar dan hanya di-sift down dalam jarak pendek
TerbaikO(n log n)n − 1 ekstraksi, masing-masing sift-down hingga log₂ n tingkat (kunci berbeda)
Rata-rataO(n log n)pekerjaan yang sama apa pun urutannya
TerburukO(n log n)tinggi tree selalu sekitar log₂ n
Ruang tambahanO(1)heap berada di dalam array input, sift-down berupa perulangan

Membangun heap secara bottom-up adalah O(n), bukan O(n log n), karena jumlah tinggi node pada complete binary tree adalah O(n). Fase ekstraksi yang mendominasi total.

Perbandingan heapsort dengan sort lain

AlgoritmaTerbaikRata-rataTerburukRuang tambahanStabil
HeapsortO(n log n)O(n log n)O(n log n)O(1)Tidak
QuicksortO(n log n)O(n log n)O(n²)O(log n)Tidak
Merge sortO(n log n)O(n log n)O(n log n)O(n)Ya
Selection sortO(n²)O(n²)O(n²)O(1)Tidak
Insertion sortO(n)O(n²)O(n²)O(1)Ya
Timsort (sorted di Python)O(n)O(n log n)O(n log n)O(n)Ya

Heapsort tidak stabil: menukar root ke akhir dapat membawa elemen melewati elemen lain yang berkunci sama. Kekuatan utamanya adalah kasus terburuk O(n log n) yang terjamin bersama ruang tambahan O(1), kombinasi yang tidak ditawarkan quicksort maupun merge sort. Kelemahannya ada pada akses memori: melompat antara indeks i dan 2*i + 1 kurang memanfaatkan cache CPU, sehingga biasanya lebih lambat daripada quicksort yang diimplementasikan dengan baik. Introsort memakai heapsort sebagai cadangan ketika quicksort berekursi terlalu dalam.

Varian dan kesalahan umum

  • Pengurutan parsial: untuk mendapatkan hanya k item terbesar, bangun heap lalu ekstrak k kali, O(n + k log n). Di Python, heapq.nlargest dan heapq.nsmallest melakukannya.
  • Top-k dari aliran data (stream): pertahankan min-heap berukuran k dan push atau ganti setiap item baru, waktu O(n log k) dan memori O(k).
  • Bottom-up heapsort men-sift lubang ke bawah sampai daun terlebih dahulu lalu naik, yang menghemat perbandingan.
  • Smoothsort, varian berbasis heap buatan Dijkstra, berjalan dalam O(n) pada input yang sudah terurut tetapi jarang dipakai karena rumit.

Kapan memakai heapsort

Pakai heapsort ketika Anda butuh kasus terburuk O(n log n) yang terjamin tanpa memori tambahan, misalnya pada sistem embedded atau real-time, atau sebagai cadangan pengaman di dalam sort lain. Untuk kode Python sehari-hari, pakai sorted(), yang stabil dan lebih cepat pada data umum. Heap itu sendiri lebih berguna daripada pengurutannya: priority queue, jalur terpendek Dijkstra, scheduler, penggabungan k list terurut (heapq.merge), dan pencarian k nilai terbesar semuanya dibangun di atasnya. Wawancara sering menanyakan rumus indeks heap, mengapa membangun heap itu O(n), dan masalah top-k.

FAQ

Apakah heapsort stabil?

Tidak. Memindahkan root ke akhir dan men-sift elemen ke bawah dapat mengubah urutan relatif kunci yang sama.

Berapa kompleksitas waktu heapsort?

O(n log n) pada kasus terbaik, rata-rata, dan terburuk. Membangun heap adalah O(n) dan masing-masing dari n − 1 ekstraksi berbiaya O(log n).

Apakah heapsort in-place?

Ya. Heap disimpan di dalam array yang diurutkan, dan bagian terurut tumbuh dari akhir, sehingga hanya butuh ruang tambahan O(1).

Mengapa heapsort lebih lambat daripada quicksort dalam praktik?

Keduanya O(n log n) rata-rata, tetapi heapsort melompat antar posisi array yang berjauhan, yang menyebabkan lebih banyak cache miss, dan ia melakukan lebih banyak perbandingan per elemen daripada quicksort.

Bagaimana cara mengurutkan dengan heapq di Python?

Panggil heapq.heapify(list) lalu heapq.heappop berulang kali, atau pakai heapq.nsmallest(k, items) untuk k terkecil. Untuk pengurutan penuh, sorted() lebih sederhana dan lebih cepat.

Semua tutorial pemrograman