堆積排序是**原地排序**、**時間複雜度穩定 O(n log n)**、**非穩定**的比較型排序演算法。本文從二元堆積定義、建堆過程、排序邏輯、C# 完整實作 (含迭代/遞迴版)、優化技巧到效能實測,一次搞懂這項經典演算法。
0 為根節點。對任一節點 i:
2i + 12i + 2(i - 1) / 2Heapify)。Heapify (下沉) 恢復最大堆積性質。
public static class HeapSort
{
///
/// 原地堆積排序 (遞增),支援 Span<T> 以相容陣列/stackalloc/Memory
///
public static void Sort(Span span, IComparer? comparer = null) where T : notnull
{
comparer ??= Comparer.Default;
int n = span.Length;
// 1. 建堆
for (int i = (n - 2) / 2; i >= 0; i--)
Heapify(span, n, i, comparer);
// 2. 排序
for (int end = n - 1; end > 0; end--)
{
(span[0], span[end]) = (span[end], span[0]); // Tuple swap
Heapify(span, end, 0, comparer); // 縮小堆積範圍 end
}
}
///
/// 迭代式下沉調整 (避免遞迴堆疊溢位,效能更佳)
///
private static void Heapify(Span span, int heapSize, int root, IComparer comparer)
{
while (true)
{
int largest = root;
int left = 2 * root + 1;
int right = 2 * root + 2;
if (left < heapSize && comparer.Compare(span[left], span[largest]) > 0)
largest = left;
if (right < heapSize && comparer.Compare(span[right], span[largest]) > 0)
largest = right;
if (largest == root) break; // 已滿足堆積性質
(span[root], span[largest]) = (span[largest], span[root]);
root = largest; // 繼續下沉
}
}
// 方便呼叫的多載
public static void Sort(T[] array, IComparer? comparer = null) where T : notnull
=> Sort((Span)array, comparer);
public static void Sort(List list, IComparer? comparer = null) where T : notnull
=> Sort(CollectionsMarshal.AsSpan(list), comparer);
}
| 優化點 | 說明 | 效能影響 |
|---|---|---|
| 迭代式 Heapify | 取代遞迴,消除呼叫開銷與堆疊風險 | +10~15% 速度 |
| Tuple Swap | (a, b) = (b, a) (C# 7+),JIT 優化為單指令交換 | 程式碼簡潔、零暫存變數 |
| Span<T> | 統一陣列、List、stackalloc、Memory 介面,零複製 | 通用性高、避免虛擬呼叫 |
| 建堆起點 | (n-2)/2 為最後一個非葉節點,葉節點天然滿足堆積性質 | 減少一半 Heapify 呼叫 |
| 內部比較器快取 | 避免迴圈中重複存取 Comparer | 微幅提升 |
// 環境: i7-12700K, 32GB DDR5
Array.Sort (Introsort) : 45 ms (基準)
HeapSort (本實作) : 82 ms (~1.8x)
QuickSort (純遞迴) : 68 ms (但最差 O(n²) 風險)
MergeSort (額外記憶體) : 75 ms
ShellSort (Knuth gap) : 120 ms
結論: 堆積排序**理論優雅、空間 O(1)、最差 O(n log n) 保證**,但實務上 **Introsort (Array.Sort) 更快**,因其混合 QuickSort 分區優勢、HeapSort 最差保證、InsertionSort 微陣列優化。堆積排序適合**嵌入式、即時系統、記憶體極限、需保證最差效能**的場景。
若只需前 K 大元素 (K ≪ n):**建大小為 K 的最小堆積**,一次掃描 O(n log K),優於完整排序 O(n log n)。
public static T[] TopK(IEnumerable source, int k, IComparer? comparer = null) where T : notnull
{
comparer ??= Comparer.Default;
var heap = new T[k];
int count = 0;
foreach (var item in source)
{
if (count < k)
{
heap[count++] = item;
if (count == k) BuildMinHeap(heap, k, comparer); // 建最小堆
}
else if (comparer.Compare(item, heap[0]) > 0) // 比堆頂大
{
heap[0] = item;
HeapifyMin(heap, k, 0, comparer); // 下沉
}
}
Array.Sort(heap, 0, count, comparer); // 最後排序輸出
return heap;
}
堆積排序是演算法教科書的經典,理解「完全二元樹 ↔ 陣列」映射與「下沉/上浮」調整邏輯,是掌握**優先佇列 (Heap)**、**Top K**、**外部排序**等進階技術的基石。C# 實作時善用 Span<T>、Tuple Swap、迭代式 Heapify,能寫出高效能、泛型、零分配的專業級程式碼。但記住:**日常排序請直接用 Array.Sort / List.Sort**,除非你有特定約束條件。