web-dev-qa-db-ja.com

Pythonのheapqモジュールとは何ですか?

"heapq" を試してみたところ、画面に表示されるものと期待が異なるという結論に達しました。それがどのように機能し、どこで役立つかを説明してくれる人が必要です。

本から 今週のPythonモジュール 段落の下2.2並べ替え

値を追加および削除するときにソートされたリストを維持する必要がある場合は、heapqを確認してください。 heapqの関数を使用してリストに項目を追加または削除することにより、オーバーヘッドを抑えてリストのソート順を維持できます。

これが私がやることと得ることです。

import heapq
heap = []

for i in range(10):
    heap.append(i)

heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

heapq.heapify(heap)    
heapq.heappush(heap, 10)    
heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

heapq.heappop(heap)
0    
heap
[1, 3, 2, 7, 4, 5, 6, 10, 8, 9] <<< Why the list does not remain sorted?

heapq.heappushpop(heap, 11)
1
heap
[2, 3, 5, 7, 4, 11, 6, 10, 8, 9] <<< Why is 11 put between 4 and 6?

そのため、「ヒープ」リストはまったくソートされていませんが、実際には、項目を追加および削除するほど、より多くの項目が整理されます。プッシュされた値は説明できない位置を取ります。何が起こっている?

53
minerals

heapqモジュールはヒープ不変式を維持しますが、これは実際のリストオブジェクトをソート順に維持することとは異なります。

heapq documentation からの引用:

ヒープは、すべての親ノードがその子の値以下の値を持つバイナリツリーです。この実装では、すべてのkに対して_heap[k] <= heap[2*k+1]_および_heap[k] <= heap[2*k+2]_の配列を使用し、ゼロから要素をカウントします。比較のために、存在しない要素は無限であると見なされます。ヒープの興味深い特性は、その最小要素が常にルート_heap[0]_であることです。

これは、最小の要素を見つけるのは非常に効率的であることを意味します(_heap[0]_を取るだけです)。これは優先キューに最適です。その後、次の2つの値は1番目の値より大きく(または等しく)、その後の次の4つの値は「親」ノードより大きくなり、次の8つの値は大きくなります。

ドキュメントの理論セクション で、データ構造の背後にある理論の詳細を読むことができます。 MIT OpenCourseWareアルゴリズム入門コース)からのこの講義 もご覧いただけます。この講義では、一般的な用語でアルゴリズムを説明しています。

ヒープは、非常に効率的にソート済みリストに戻すことができます。

_def heapsort(heap):
    return [heapq.heappop(heap) for _ in range(len(heap))]
_

ヒープから次の要素をポップするだけです。ただし、Pythonの並べ替えで使用されるTimSortアルゴリズムは、ヒープに既に存在する部分的な順序を利用するため、sorted(heap)を使用する方が高速です。

最小値または最初のn最小値のみに関心がある場合、特にこれらの値に継続的に関心がある場合は、ヒープを使用します。値を追加するたびにリストを再利用するよりも、新しいアイテムを追加して最小のものを削除するのは非常に効率的です。

83
Martijn Pieters

あなたの本は間違っています!あなたが示すように、ヒープはソートされたリストではありません(ソートされたリストはヒープですが)。ヒープとは何ですか? Skienaのアルゴリズム設計マニュアルを引用するには

ヒープは、優先度の高いキュー操作insertおよびextract-minを効率的にサポートするためのシンプルでエレガントなデータ構造です。これらは、ソートされた順序よりも弱い(したがって、維持するのが効率的である)が、ランダムな順序よりも強い(したがって、最小要素を迅速に識別できる)要素セットの部分順序を維持することによって機能します。

ソートされたリストと比較して、ヒープはより弱い条件ヒープ不変式に従います。定義する前に、まず、条件を緩和することが役立つ理由を考えてください。答えは、弱い条件は維持しやすいであるということです。ヒープを使用するとより少ない処理を実行できますが、より高速に実行できます

ヒープには3つの操作があります。

  1. Find-Minimum is O(1)
  2. O(log n)を挿入
  3. Remove-Min O(log n)

重要なのは、ソートされたリストの場合、O(n))に勝るO(log n)です。

ヒープ不変量とは何ですか? 「親が子供を支配するバイナリツリー」。あれは、 "p ≤ c pのすべての子c "。Skienaは、写真で説明し、不変量を維持しながら要素を挿入するアルゴリズムを示します。しばらく考えれば、自分でそれらを発明できます。バブルダウン)

幸いなことに、バッテリーに含まれるPythonは、すべてのモジュールを heapq モジュールで実装します。ヒープタイプは定義していません(より簡単だと思います)使用します)が、リスト上のヘルパー関数として提供します。

道徳:ソートされたリストを使用してアルゴリズムを記述し、一方の端だけを検査して削除する場合、ヒープを使用してアルゴリズムをより効率的にすることができます。

ヒープデータ構造が役立つ問題については、 https://projecteuler.net/problem=5 を参照してください。

29
Colonel Panic

ヒープデータ構造の実装には誤解があります。 heapqモジュールは、実際には バイナリヒープ 実装のバリアントです。ここで説明するように、ヒープ要素はリストに格納されます。 https://en.wikipedia。 org/wiki/Binary_heap#Heap_implementation

ウィキペディアの引用:

ヒープは一般的に配列で実装されます。任意のバイナリツリーを配列に格納できますが、バイナリヒープは常に完全なバイナリツリーであるため、コンパクトに格納できます。ポインターにスペースは必要ありません。代わりに、各ノードの親と子は、配列インデックスの算術演算によって見つけることができます。

以下のこの画像は、ツリーの表現とヒープのリスト表現の違いを感じるのに役立つはずです(、これは通常の最小ヒープの逆である最大ヒープです!)):

enter image description here

一般に、ヒープデータ構造は、特定の要素が他の要素よりも大きいか小さいかに関する情報を犠牲にするという点で、ソートされたリストとは異なります。ヒープは、この特定の要素が親よりも小さく、子よりも大きいことしかわかりません。データ構造が保存する情報が少ないほど、それを変更するのにかかる時間/メモリが少なくなります。ヒープとソートされた配列の間のいくつかの操作の複雑さを比較します。

        Heap                  Sorted array
        Average  Worst case   Average   Worst case

Space   O(n)     O(n)         O(n)      O(n)

Search  O(n)     O(n)         O(log n)  O(log n)

Insert  O(1)     O(log n)     O(n)      O(n)

Delete  O(log n) O(log n)     O(n)      O(n)
23