堆是一種完全二叉樹,通常用數組隱式存儲。其核心性質:任意節點的值 ≤(或 ≥)其子節點值。本文以最小堆為例,通過 Java 代碼演示插入上浮、提取最小值下沉、建堆三大核心操作,並給出堆排序完整實現。
2*i + 12*i + 2(i-1)/2完全二叉樹特性保證數組存儲緊湊、無空洞,且支持 O(1) 隨機訪問。
// 最小堆:新元素放末尾,與父節點比較,小於則交換
public void swim(int k) {
while (k > 0 && less(pq[(k-1)/2], pq[k])) {
exch(pq, (k-1)/2, k);
k = (k-1)/2;
}
}
// 輔助方法
private boolean less(Comparable a, Comparable b) { return a.compareTo(b) < 0; }
private void exch(Comparable[] a, int i, int j) { Comparable t = a[i]; a[i] = a[j]; a[j] = t; }
// 最小堆:從根節點開始,與較小的子節點交換
private void sink(int k, int N) {
while (2*k + 1 < N) { // 存在左子節點
int j = 2*k + 1; // 左子節點
if (j+1 < N && less(pq[j+1], pq[j])) j++; // 右子節點更小
if (less(pq[k], pq[j])) break; // 已滿足堆性質
exch(pq, k, j);
k = j;
}
}
// 從最後一個非葉子節點倒序下沉
public Heap(Comparable[] items) {
pq = items;
N = items.length;
for (int k = (N-2)/2; k >= 0; k--) {
sink(k, N);
}
}
為什麼 O(n)? 高度為 h 的節點最多下沉 h 步,節點數約 n/2^{h+1},總操作數 Σ h·n/2^h = O(n)。
public class HeapSort extends BaseSort {
private int N;
@Override
public void sort(Comparable[] a) {
N = a.length;
// 1. 建立最小堆(若需升序用最大堆,這裡演示最小堆得降序)
for (int k = (N-2)/2; k >= 0; k--) sink(a, k, N);
// 2. 逐個提取最小值到數組末尾
while (N > 1) {
exch(a, 0, --N); // 堆頂與當前末尾交換
sink(a, 0, N); // 新堆頂下沉
}
}
private void sink(Comparable[] a, int k, int N) {
while (2*k + 1 < N) {
int j = 2*k + 1;
if (j+1 < N && less(a[j+1], a[j])) j++;
if (less(a[k], a[j])) break;
exch(a, k, j);
k = j;
}
}
}
[5, 3, 8, 1, 2, 7, 4, 6][1, 2, 4, 3, 5, 7, 8, 6](堆頂最小)[6, 2, 4, 3, 5, 7, 8, 1] → 下沉 6 → [2, 3, 4, 6, 5, 7, 8, 1][8, 3, 4, 6, 5, 7, 2, 1] → 下沉 8 → [3, 6, 4, 8, 5, 7, 2, 1][8, 7, 6, 5, 4, 3, 2, 1](降序)less 改為 greater,或構建最大堆堆以「完全二叉樹 + 數組隱式存儲 + 上浮/下沉調整」實現高效優先隊列與原地排序。掌握 swim/sink 核心邏輯與建堆線性時間證明,是數據結構與算法面試的必考點,也是理解優先隊列、Dijkstra、Top-K 問題的基石。