Menu
Coddy logo textTech

Heap Sort

最終更新

ヒープソートは配列を二分ヒープとして扱います。まず max-heap を構築し、最大の要素が根(インデックス 0)に来るようにします。次に、根を未整列の最後の要素と繰り返し交換して最大値をその場に固定し、新しい根を下方向にふるい落としてヒープ条件を回復します。上の再生ボタンを押すと、ヒープの構築と取り出しの様子を見られます。

ヒープソートはマージソートと同様に O(n log n) の時間を保証しますが、追加領域は O(1) だけでその場(in-place)でソートします。安定ではなく、クイックソートよりキャッシュ効率が悪くなりがちなので、保証された上界と一定メモリの両方が重要な場合によく選ばれます。

時間計算量と空間計算量

ケース計算量備考
最良ケースO(n log n)構築 + n 回の取り出し
平均ケースO(n log n)ランダムな順序
最悪ケースO(n log n)保証される
空間O(1)その場
安定いいえふるい落としが等しい要素を並べ替える

ステップごと

ステップ何が起きるか
1配列から max-heap を構築する(最後の親からふるい落とす)。
2根(最大値)をヒープの最後の要素と交換する。
3ヒープを1つ縮める - その最後の位置はこれで整列済み。
4新しい根を下方向にふるい落として max-heap 条件を回復する。
5ヒープの要素が1つになるまで繰り返す。

具体例

[3, 1, 6, 5, 2, 4] のソート。バー | は縮小するヒープと整列済みの末尾の境界を示します:

パス配列操作
ヒープ構築[6, 5, 4, 1, 2, 3]最後の親からふるい落として max-heap を構築する。6 が根に来る。
1[5, 3, 4, 1, 2 | 6]6 を最後の位置と交換し、ヒープを縮めて 3 をふるい落とす。
2[4, 3, 2, 1 | 5, 6]5 を取り出し、2 をふるい落として 4 を根に上げる。
3[3, 1, 2 | 4, 5, 6]4 を取り出し、1 をふるい落として 3 を根に上げる。
4[2, 1 | 3, 4, 5, 6]3 を取り出す。2 はすでにヒープ条件を満たしている。
5[1 | 2, 3, 4, 5, 6]2 を取り出す。残りは1要素なので配列は整列済み。

ヒープソートを使うべき場面

使うべき場面避けるべき場面
O(n²) の危険なしに保証された O(n log n) の最悪ケースが必要なとき。等しいキーの順序を保つ安定ソートが必要なとき。
メモリが限られているとき - 追加領域は O(1) だけでその場でソートする。キャッシュ性能が重要でデータがメモリに収まるとき - クイックソートの方が通常速い。
すでにデータ上でヒープ(例: 優先度付きキュー)を保持しているとき。比較回数を最小にしたいとき - マージソートやクイックソートは実際にはより少なく比較することが多い。
信頼できない入力がクイックソートの最悪ケースを引き起こしうるが、ランダム化できないとき。データがほぼ整列済みのとき - 挿入ソートがほぼ線形時間で動作する。

Heap Sortのコード

Python, JavaScript, Java, C++, Cによるクリーンで実行可能なHeap Sortの実装です。言語を選んでコードをコピーするか、Coddyプレイグラウンドに読み込んだ状態で開けます。

PythonでのHeap Sortのコード

Python
1def heap_sort(a):2    n = len(a)3    # Build a max-heap, deepest parent first4    for i in range(n // 2 - 1, -1, -1):5        sift_down(a, i, n)6    # Repeatedly move the max to the end and shrink the heap7    for end in range(n - 1, 0, -1):8        a[0], a[end] = a[end], a[0]9        sift_down(a, 0, end)10    return a11
12
13def sift_down(a, i, size):14    while True:15        largest = i16        left, right = 2 * i + 1, 2 * i + 217        if left < size and a[left] > a[largest]:18            largest = left19        if right < size and a[right] > a[largest]:20            largest = right21        if largest == i:22            return23        a[i], a[largest] = a[largest], a[i]24        i = largest25
26
27nums = [12, 11, 13, 5, 6, 7]28print("Before:", nums)29heap_sort(nums)30print("After: ", nums)
このコードをPythonプレイグラウンドで実行

ヒープソートに関するよくある質問

ヒープソートの時間計算量は?
ヒープソートは最良・平均・最悪のいずれのケースでも O(n log n) です。ヒープの構築は O(n) で、n 回の取り出しのそれぞれが O(log n) かかります。追加領域は O(1) です。
ヒープソートは安定ですか?
いいえ。ふるい落とし操作は等しい要素を互いに追い越すことがあるため、ヒープソートは等しいキーの相対順序を保ちません。
ヒープソートはいつ使うべきですか?
追加メモリが O(1) だけで保証された O(n log n) の最悪ケースが必要なときにヒープソートを使いましょう。マージソートの O(n) バッファなしにクイックソートの O(n²) の危険を避けられますが、その代償として安定性とキャッシュ性能を犠牲にします。
ヒープソートとクイックソートの違いは?
どちらもその場でソートしますが、クイックソートには O(n²) の最悪ケースがある一方、ヒープソートは O(n log n) を保証します。実際にはクイックソートの方がキャッシュ局所性が良く交換も少ないため通常は速く、ヒープソートは主に最悪ケースの上界を保証しなければならないときに選ばれます。
ヒープソートは優先度付きキューとどう関係しますか?
二分ヒープは優先度付きキューの標準的な実装であり、ヒープソートは本質的にそのキューから最大値を繰り返し取り出すことです。データをすでにヒープで保持しているなら、要素を1つずつ取り出すだけで整列済みの順序が無償で得られます。
ヒープソートには max-heap と min-heap のどちらが必要ですか?
その場で昇順にソートするには max-heap を使います。各パスで最大の要素が末尾に交換され、整列済みの末尾が右から伸びていきます。min-heap はその場で降順を生成し、別の配列に取り出せば昇順になります。
Coddy programming languages illustration

Coddy でアルゴリズムをマスターしよう

始める