ヒープソートとHeapify — O(n log n)のインプレースソート
ヒープソートが最大ヒープデータ構造とheapify手順を使用して、O(1)追加空間で保証されたO(n log n)時間を達成する仕組みを学びます。
Heap-based Sorts
詳細な説明
ヒープソートの動作原理
ヒープソートは最大ヒープデータ構造を活用して配列をインプレースでソートします。入力データから最大ヒープを構築し、最大要素を繰り返し抽出する2つのフェーズで構成されます。
フェーズ1:最大ヒープの構築
最大ヒープは、各親がその子以上である完全二分木です。配列表現はレベルごとにツリーを格納します:
- ノード
iの親:floor((i-1)/2) - ノード
iの左の子:2*i + 1 - ノード
iの右の子:2*i + 2
heapify を使用したボトムアップのヒープ構築はO(n)時間で行えます(素朴な分析が示唆するO(n log n)ではありません)。
Heapify手順
heapify(arr, size, i) はインデックス i をルートとするサブツリーがヒープ性を満たすことを保証します:
function heapify(arr, size, i):
largest = i
left = 2*i + 1
right = 2*i + 2
if left < size and arr[left] > arr[largest]:
largest = left
if right < size and arr[right] > arr[largest]:
largest = right
if largest != i:
swap(arr[i], arr[largest])
heapify(arr, size, largest)
フェーズ2:要素の抽出
ヒープ構築後、最大要素はインデックス0にあります。最後の未ソート要素とスワップし、ヒープサイズを1減らし、ルートをheapifyします:
for i = n-1 down to 1:
swap(arr[0], arr[i])
heapify(arr, i, 0)
計算量分析
| 指標 | 値 |
|---|---|
| ヒープ構築 | O(n) |
| n回の抽出 x heapify | O(n log n) |
| 合計時間 | O(n log n) — すべてのケース |
| 空間 | O(1) — インプレース |
| 安定 | いいえ |
ヒープ構築がO(n)である理由
直感的には、ほとんどのノードはツリーの下部に近く、わずかな距離のみheapifyが必要です。ノードの半分は葉(heapify不要)、4分の1は最大1回のスワップ、8分の1は最大2回、というように合計はO(n)に収束します。
ユースケース
ヒープソートは、保証されたO(n log n)性能とO(1)追加空間の両方が必要な場合に価値があります — マージソート(O(n)空間)もクイックソート(O(n²)最悪ケース)も提供できない組み合わせです。クイックソートの再帰深度が深くなりすぎた際のフォールバックとしてIntrosortで使用され、部分ソート(k個の最大要素の検索)が必要な優先度キュー実装でも使用されます。