Heap sort memanfaatkan struktur binary heap untuk mengurutkan array dalam O(n log n) waktu dan O(1) memori tambahan. Algoritma ini bekerja dalam dua fase: membangun max-heap dari array awal, lalu mengekstrak elemen maksimum satu per satu hingga array terurut. Heap sort in-place dan menjamin O(n log n) pada semua kasus — best, average, worst. Pada Chapter ini kita akan membedah mekanisme heapify, mengimplementasikan heap sort, dan menjelaskan mengapa algoritma ini tidak stabil. Pada akhir chapter kita akan paham kapan heap sort menjadi pilihan utama: ketika dibutuhkan jaminan worst case O(n log n) dengan memori O(1).
Aturan: Build Heap, Extract Max #
Heap sort bekerja dalam dua fase:
- Build max-heap — susun array sehingga memenuhi sifat max-heap: setiap node lebih besar atau sama dengan anak-anaknya.
- Extract max berulang — tukar root (elemen maksimum) dengan elemen terakhir, kurangi ukuran heap, lalu perbaiki heap dengan sift down pada root.
Setelah n-1 iterasi extract, array terurut ascending.
Mekanisme Max-Heap #
Pada max-heap berbasis array (representasi level-order):
- Anak kiri node
i:2i + 1. - Anak kanan node
i:2i + 2. - Parent node
i:(i - 1) / 2. - Sifat heap:
v[parent(i)] >= v[i]untuk semuai(kecuali root).
Array [10, 9, 8, 7, 4, 3, 2, 5, 6, 1] merepresentasikan pohon heap di atas — setiap parent lebih besar dari anak-anaknya.
Kode: Heap Sort #
#include <iostream>
#include <vector>
#include <algorithm>
void sift_down(std::vector<int>& v, int idx, int n) {
while (true) {
int left = 2 * idx + 1;
int right = 2 * idx + 2;
int largest = idx;
if (left < n && v[left] > v[largest]) largest = left;
if (right < n && v[right] > v[largest]) largest = right;
if (largest == idx) break;
std::swap(v[idx], v[largest]);
idx = largest;
}
}
void heap_sort(std::vector<int>& v) {
int n = static_cast<int>(v.size());
if (n < 2) return;
// Build max-heap
for (int i = n / 2 - 1; i >= 0; --i) {
sift_down(v, i, n);
}
std::cout << "Max-heap setelah build: ";
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
// Extract max
for (int end = n - 1; end > 0; --end) {
std::swap(v[0], v[end]);
sift_down(v, 0, end);
}
}
void print(const std::vector<int>& v) {
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
}
int main() {
std::vector<int> a = {4, 10, 3, 5, 1, 8, 2, 7, 6, 9};
std::cout << "Sebelum sort: "; print(a);
heap_sort(a);
std::cout << "Setelah sort : "; print(a);
return 0;
}Output yang Dihasilkan:
Sebelum sort: 4 10 3 5 1 8 2 7 6 9
Max-heap setelah build: 10 9 8 7 4 3 2 5 6 1
Setelah sort : 1 2 3 4 5 6 7 8 9 10Bedah Kode Baris-per-Baris #
void sift_down(std::vector<int>& v, int idx, int n): perbaiki sifat heap pada nodeidxke bawah, dengan asumsi sub-heap anak kiri dan kanan sudah memenuhi sifat heap. Parameternadalah ukuran logis heap.int left = 2 * idx + 1;danint right = 2 * idx + 2;: indeks anak kiri dan kanan dalam representasi array.int largest = idx;: asumsikan parent adalah yang terbesar.if (left < n && v[left] > v[largest]) largest = left;: bila anak kiri ada dan lebih besar, perbaruilargest.if (right < n && v[right] > v[largest]) largest = right;: sama untuk anak kanan. Perhatikan perbandingan denganlargest, bukan denganidx— ini memastikan kita memilih anak terbesar di antara kedua anak.if (largest == idx) break;: bila parent sudah terbesar, heap valid — berhenti.std::swap(v[idx], v[largest]); idx = largest;: tukar dengan anak terbesar, lanjutkan sift down dari posisi baru.for (int i = n / 2 - 1; i >= 0; --i): loop build heap dari node internal terakhir (n/2 - 1) hingga root. Node dengan indeks≥ n/2adalah daun — sudah memenuhi sifat heap by default.for (int end = n - 1; end > 0; --end): loop extract — dari elemen terakhir hingga elemen kedua.std::swap(v[0], v[end]);: tukar root (maksimum) dengan elemen terakhir heap. Elemen terakhir kini berada pada posisi final.sift_down(v, 0, end);: perbaiki heap dengan ukuranend(elemen yang baru ditukar dikecualikan dari heap).- Output: array terurut ascending. Max-heap awal
[10, 9, 8, 7, 4, 3, 2, 5, 6, 1]sesuai diagram di atas.
Build Max-Heap dalam O(n) #
Walaupun kelihatannya build heap berbiaya O(n log n) (n node × log n per sift_down), analisis lebih cermat menunjukkan kompleksitas O(n). Alasannya: mayoritas node adalah daun (tidak perlu sift down), dan node yang lebih dekat ke root (jarak log n) jumlahnya sedikit.
Analisis Tinggi #
Total kerja build heap:
n/2daun — 0 sift step.n/4node level-1 — paling banyak 1 sift step.n/8node level-2 — paling banyak 2 sift step.- …
- 1 root — paling banyak
log nsift step.
Total: n/4 · 1 + n/8 · 2 + n/16 · 3 + ... ≈ n (konvergen ke konstanta).
Kompleksitas #
| Kasus | Kompleksitas | Penjelasan |
|---|---|---|
| Build heap | O(n) | Mayoritas node adalah daun |
| Extract (n-1 iterasi) | O(n log n) | Setiap extract = O(log n) |
| Total | O(n log n) | Best = average = worst |
| Space | O(1) | In-place, tidak butuh buffer |
| Stable | Tidak | Sift down swap jarak jauh |
Heap sort menjamin O(n log n) pada semua kasus — bahkan pada sorted input, reverse sorted, atau random. Inilah keunggulan utamanya dibanding quick sort (yang O(n²) pada worst case).
Mengapa Heap Sort Tidak Stabil #
Pertukaran swap(v[0], v[end]) pada extract max memindahkan elemen terakhir ke root, lalu sift down menggeser elemen-elemen lain. Pertukaran jarak jauh ini dapat merusak urutan relatif elemen equal. Contoh: input {(Budi,5), (Andi,3), (Citra,5)} yang di-sort by key dapat menghasilkan Citra sebelum Budi.
In-Place dan O(1) Memori #
Heap sort in-place dengan O(1) memori tambahan — hanya variabel idx, left, right, largest, end, dan i. Tidak ada alokasi buffer seperti merge sort. Inilah yang membuat heap sort menjadi pilihan untuk lingkungan dengan memori terbatas.
Komparasi: Heap Sort vs Quick Sort vs Merge Sort #
| Aspek | Heap Sort | Quick Sort | Merge Sort |
|---|---|---|---|
| Best | O(n log n) | O(n log n) | O(n log n) |
| Average | O(n log n) | O(n log n) | O(n log n) |
| Worst | O(n log n) | O(n²) | O(n log n) |
| Space | O(1) | O(log n) | O(n) |
| Stable | Tidak | Tidak | Ya |
| Cache-friendly | Tidak (acak) | Ya (sekuensial) | Ya (sekuensial) |
| In practice | Lambat | Cepat | Sedang |
Penggunaan Heap Sort pada Introsort #
std::sort di libstdc++ mengimplementasikan introsort — hybrid yang memantau kedalaman rekursi quick sort. Bila kedalaman melebihi 2·log₂ n, algoritma switch ke heap sort untuk menghindari worst case O(n²). Strategi ini memberikan:
- Rata-rata kecepatan quick sort (cache-friendly).
- Jaminan worst case
O(n log n)dari heap sort.
Implementasi Alternatif: std::make_heap + std::sort_heap #
STL menyediakan utilitas heap yang langsung memanfaatkan algoritma di atas:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {4, 10, 3, 5, 1, 8, 2, 7, 6, 9};
std::make_heap(v.begin(), v.end());
std::cout << "Setelah make_heap: ";
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
std::sort_heap(v.begin(), v.end());
std::cout << "Setelah sort_heap: ";
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}Output yang Dihasilkan:
Setelah make_heap: 10 9 8 7 4 3 2 5 6 1
Setelah sort_heap: 1 2 3 4 5 6 7 8 9 10std::make_heap: membangun max-heap dari range. KompleksitasO(n).std::sort_heap: mengurutkan range yang sudah berupa heap menjadi sorted ascending. KompleksitasO(n log n).- Catatan:
std::sort_heapmemerlukan input yang sudah berupa heap — memanggilnya pada array biasa menghasilkan output salah.
Aplikasi Heap Sort #
- Memori terbatas —
O(1)auxiliary space, cocok untuk embedded systems. - Worst case terjamin — sistem real-time yang tidak boleh memiliki degradasi performa.
- Priority queue dengan sorted extraction — semua elemen harus diambil dalam urutan terurut.
- Sub-rutin introsort — fallback ketika quick sort memasuki territory
O(n²).
Ringkasan #
- Heap sort bekerja dengan build max-heap
O(n)lalu extract max berulangO(n log n). - Kompleksitas terjamin
O(n log n)pada semua kasus. - In-place
O(1)memori tambahan. - Tidak stabil karena pertukaran jarak jauh pada extract dan sift down.
- Lebih lambat di praktik dibanding quick sort karena akses memori tidak cache-friendly.
- Menjadi fallback pada
std::sort(introsort) untuk mencegah worst case quick sort.
Pada chapter-chapter berikutnya kita akan beralih ke algoritma sorting non-comparison: counting sort, radix sort, dan bucket sort, yang dapat mencapai O(n) pada kondisi tertentu.