ヒープソート (Heap Sort)

1. ヒープソートとは

ヒープソートは、1964年にロバート・W・フロイドによって発明された効率的なソートアルゴリズムです。このアルゴリズムは、「ヒープ」というデータ構造を利用して配列をソートします。最悪、平均、最良のケースでも時間計算量が O(n log n) という特徴があり、安定したパフォーマンスを持っています。

2. ヒープとは

ヒープは完全二分木の一種で、以下の特性を持ちます。

  1. 完全二分木構造: すべてのレベルが完全に埋まっているか、最下層のみ左から右へ部分的に埋まっている二分木です。
  2. ヒープ条件:
    • 最大ヒープ: 各ノードの値がその子ノードの値以上
    • 最小ヒープ: 各ノードの値がその子ノードの値以下

ヒープは配列で効率的に表現できるのが特徴です。配列内のインデックス i のノードに対して、

  • 親ノードのインデックス: Math.floor((i - 1) / 2)
  • 左の子ノードのインデックス: 2 * i + 1
  • 右の子ノードのインデックス: 2 * i + 2

3. ヒープソートのアルゴリズム

ヒープソートは大きく分けて2つのフェーズから構成されています。

フェーズ1: ヒープの構築(Build Max Heap)

最初に、ランダムな配列を最大ヒープに変換します。

function buildMaxHeap(array) {
  const n = array.length;
  
  // 葉ノード以外の各ノードに対してmaxHeapify操作を行う
  for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
    maxHeapify(array, i, n);
  }
}

フェーズ2: 最大値の抽出と配置

構築したヒープから、根(最大値)を取り出して配列の末尾に配置し、ヒープのサイズを1つ減らして再構築します。これを繰り返すことで、配列は昇順にソートされます。

function heapSort(array) {
  // ヒープを構築
  buildMaxHeap(array);
  
  // ヒープから要素を1つずつ取り出して配置
  for (let i = array.length - 1; i > 0; i--) {
    // 根(最大値)を配列の末尾に移動
    [array[0], array[i]] = [array[i], array[0]];
    
    // ヒープを再構築
    maxHeapify(array, 0, i);
  }
}

maxHeapify 操作

最大ヒープの条件を維持するための核となる操作です。指定されたノードとその子ノードを比較し、必要に応じて値を交換します。

function maxHeapify(array, i, heapSize) {
  const left = 2 * i + 1;
  const right = 2 * i + 2;
  let largest = i;
  
  // 左の子ノードと比較
  if (left < heapSize && array[left] > array[largest]) {
    largest = left;
  }
  
  // 右の子ノードと比較
  if (right < heapSize && array[right] > array[largest]) {
    largest = right;
  }
  
  // 最大値が現在のノードでない場合、交換して再帰的に処理
  if (largest !== i) {
    [array[i], array[largest]] = [array[largest], array[i]];
    maxHeapify(array, largest, heapSize);
  }
}

4. ヒープソートの動作例

配列 [4, 10, 3, 5, 1] をヒープソートで昇順にソートする過程を見てみましょう。

フェーズ1: ヒープの構築

  1. 初期配列: [4, 10, 3, 5, 1]
  2. インデックス 1(値:10)から maxHeapify 開始:
    • 10 は既に子ノード(5, 1)より大きいので変更なし
  3. インデックス 0(値:4)から maxHeapify 開始:
    • 4 < 10 なので交換: [10, 4, 3, 5, 1]

ヒープ構築完了: [10, 4, 3, 5, 1](最大ヒープ)

フェーズ2: ソート

  1. 根(10)を末尾と交換: [1, 4, 3, 5, 10]
    • ヒープサイズを4に減らし、maxHeapify: [5, 4, 3, 1] | 10
  2. 根(5)を末尾と交換: [1, 4, 3, 5] | 10[3, 4, 1] | 5, 10
    • ヒープサイズを3に減らし、maxHeapify: [4, 3, 1] | 5, 10
  3. 根(4)を末尾と交換: [1, 3, 4] | 5, 10[3, 1] | 4, 5, 10
    • ヒープサイズを2に減らし、maxHeapify: [3, 1] | 4, 5, 10
  4. 根(3)を末尾と交換: [1] | 3, 4, 5, 10[1, 3, 4, 5, 10]

最終的にソートされた配列: [1, 3, 4, 5, 10]

5. ヒープソートのシミュレーション

6. ヒープソートの特徴

利点:

  • 時間計算量: 最悪、平均、最良のケースでも O(n log n)
  • 空間計算量: O(1)(追加のメモリをほとんど使わない)
  • 安定性: パフォーマンスが入力配列の状態に依存しない

欠点:

  • 安定ソートではない: 同じ値の要素の相対的な順序が保持されない
  • キャッシュ効率: 要素間の移動が大きいため、キャッシュ効率が悪い場合がある
  • 比較回数: クイックソートに比べて実際の比較回数が多い傾向

7. ヒープソートの応用

ヒープソートは、以下のような状況で特に有用です:

  1. 限られたメモリ環境: 追加のメモリをほとんど使わないため
  2. 最悪のケースの保証が必要な場合: 常に O(n log n) の時間計算量を保証
  3. 部分ソート: 最大/最小の k 個の要素だけを必要とする場合(k-選択問題)
  4. 優先度キュー: ヒープはデータ構造としても重要で、優先度キューの実装に使用される

8. まとめ

ヒープソートは、ヒープという特殊なデータ構造を利用した効率的なソートアルゴリズムです。二分ヒープの特性を活用することで、一貫したパフォーマンスを発揮し、追加のメモリもほとんど使わないという利点があります。アルゴリズムは比較的シンプルで、実装も容易なため、多くのシステムやライブラリで採用されています。

最悪のケースでも O(n log n) の時間計算量を保証する点は大きな強みですが、実際のパフォーマンスではクイックソートやマージソートに劣ることも多いため、具体的な用途や制約に合わせて適切なソートアルゴリズムを選択することが重要です。

9. その他のソート

タイトルとURLをコピーしました