Menu
Coddy logo textTech

ヒープ(二分ヒープ)

最終更新

二分ヒープは、最小値(最小ヒープ)または最大値(最大ヒープ)を根に保つ完全二分木です。この可視化は最小ヒープで、すべての親はその子以下です。値を追加するには次の空いている位置に付け足し、その後、親より小さい間は親と交換して「上へふるい上げ」、ヒープ条件が再び満たされるまで続けます。上の再生ボタンを押して、各新しい値が自分の位置まで浮上する様子を見てください。

ヒープは完全木なので配列にコンパクトに格納されます。ノード i の子は 2i+12i+2 にあります。挿入と最小値の削除は O(log n)(根から葉までの1つの経路)で、最小値の参照は O(1) です。これはまさに優先度付きキューが必要とするものです。

時間計算量と空間計算量

操作計算量備考
最小/最大の参照O(1)常に根にある
挿入(push)O(log n)1つの経路を上へふるい上げ
最小/最大の削除O(log n)1つの経路を下へふるい下げ
ヒープの構築O(n)一度にheapify
空間O(n)配列ベース、ポインタなし

ステップごと(push)

ステップ何が起こるか
1新しい値を末尾(次の空いている葉)に付け足す。
2それを親と比較する。
3小さければ(最小ヒープ)、上へ交換する。
4親より小さくなくなるか、根に達するまで繰り返す。

実例

[5, 3, 8, 1, 4] を1つずつ挿入して最小ヒープを構築する:

Pushふるい上げ後の配列動作
5[5]最初の値が根になる。
3[3, 5]3 < 親 5 なので、根まで上がる。
8[3, 5, 8]8 > 親 5 なので、葉のままとどまる。
1[1, 3, 8, 5]1 < 親 5 で交換、次に 1 < 親 3 で根まで上がる。
4[1, 3, 8, 5, 4]4 > 親 3 なのでとどまる。最小値 1 は根に残る。

ヒープを使うべき場面

使うべき場面避けるべき場面
変化する集合から最小または最大の要素を繰り返し必要とするとき。極値だけでなく任意の値を検索する必要があるとき - BSTやハッシュ集合を使う。
Dijkstra、A*、スケジューラ向けに優先度付きキューを実装するとき。データを常に完全にソートされた状態で保つ必要があるとき。
コンパクトな配列レイアウトで O(log n) の挿入と最小値削除が欲しいとき。特定の(根でない)要素の高速な検索や削除が必要なとき。
要素のストリームをマージし、優先度順に取り出す必要があるとき。データ集合がごく小さく、線形走査の方が単純で十分速いとき。

Heap (Priority Queue)のコード

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

PythonでのHeap (Priority Queue)のコード

Python
1class MinHeap:2    def __init__(self):3        self.data = []4
5    def push(self, value):6        # Append at the end, then bubble up to restore order7        self.data.append(value)8        i = len(self.data) - 19        while i > 0:10            parent = (i - 1) // 211            if self.data[parent] <= self.data[i]:12                break13            self.data[i], self.data[parent] = self.data[parent], self.data[i]14            i = parent15
16    def pop(self):17        # Move the last leaf to the root, then sift it down18        top = self.data[0]19        last = self.data.pop()20        if self.data:21            self.data[0] = last22            self._sift_down(0)23        return top24
25    def _sift_down(self, i):26        n = len(self.data)27        while True:28            smallest = i29            left, right = 2 * i + 1, 2 * i + 230            if left < n and self.data[left] < self.data[smallest]:31                smallest = left32            if right < n and self.data[right] < self.data[smallest]:33                smallest = right34            if smallest == i:35                return36            self.data[i], self.data[smallest] = self.data[smallest], self.data[i]37            i = smallest38
39
40heap = MinHeap()41for value in [5, 3, 8, 1, 9, 2]:42    heap.push(value)43
44print("Heap array:     ", heap.data)45print("Popped in order:", [heap.pop() for _ in range(6)])
このコードをPythonプレイグラウンドで実行

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

ヒープは何に使われますか?
ヒープは優先度付きキューを実装し、Dijkstraの最短経路アルゴリズム、タスクスケジューラ、イベントシミュレーションを支えます。ヒープソートの原動力でもあります。変化する集合から最小または最大の要素を繰り返し必要とするときはいつでも、ヒープが適した道具です。
ヒープと二分探索木の違いは何ですか?
どちらも二分木ですが、BSTは左から右への完全な整列順序を保ち(順序付き探索を可能にし)、一方ヒープは親子関係だけ(根に最小または最大)を保証します。ヒープは極値へのO(1)アクセスを与え、BSTは任意の値へのO(log n)探索を与えます。
なぜヒープは配列に格納されるのですか?
ヒープは常に完全二分木なので、そのノードは配列のインデックスに完全に対応します。インデックス i の子は 2i+12i+2 にあり、親は (i-1)/2 にあります。これにより子へのポインタを格納せずに済み、優れたキャッシュ性能が得られます。
ヒープはソート済み配列と同じですか?
いいえ。ヒープは各親が子より小さい(最小ヒープ)または大きい(最大ヒープ)ことだけを保証するので、兄弟やいとこには特定の順序がありません。ソート済み配列は完全に整列していますが挿入に O(n) かかり、一方ヒープは O(log n) で挿入でき、それでも極値への即時アクセスを与えます。
配列を単にソートする代わりに、いつヒープを使うべきですか?
データが変化し続け、現在の最小値または最大値だけが必要なときはヒープを使いましょう。挿入と取り出しはそれぞれ O(log n) で、配列全体を再ソートするより有利です。静的なデータ集合で全要素を順に欲しいなら、1回の O(n log n) ソートの方が単純で多くの場合速いです。
n個の要素からヒープを構築するのに O(n log n) かかりますか?
一度に構築すればかかりません。n 個の要素を1つずつ挿入すると O(n log n) ですが、最後の親から根へ向かってふるい下げるボトムアップの heapify は全体で O(n) で動きます。ほとんどのノードは底に近く、短い距離しかふるい下げないからです。
Coddy programming languages illustration

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

始める