web-dev-qa-db-ja.com

Python:ヒープから要素を削除

Pythonには、ヒープデータ構造を実装するheapqモジュールがあり、いくつかの基本的な操作(プッシュ、ポップ)をサポートしています。

O(log n)のヒープからi番目の要素を削除する方法heapqでも可能ですか、それとも別のモジュールを使用する必要がありますか?

ドキュメントの下部に例があります: http://docs.python.org/library/heapq.html これは可能なアプローチを示唆しています-これは私が望むものではありません。単に削除済みとしてマークするのではなく、要素を削除してほしい。

39
Ecir Hana

ヒープからi番目の要素を非常に簡単に削除できます。

_h[i] = h[-1]
h.pop()
heapq.heapify(h)
_

削除する要素を最後の要素で置き換え、最後の要素を削除して、ヒープを再度ヒープ化するだけです。これはO(n)です。必要な場合は、O(log(n))でも同じことができますが、内部のheapify関数をいくつか呼び出す必要があります。 larsmanが指摘したように、heapq.pyから_siftup/_siftdownのソースを独自のコードにコピーするだけです。

_h[i] = h[-1]
h.pop()
if i < len(h):
    heapq._siftup(h, i)
    heapq._siftdown(h, 0, i)
_

いずれの場合も、iが最後の要素を参照すると失敗するため、h[i] = h.pop()を実行することはできません。特別なケースで最後の要素を削除する場合は、上書きとポップを組み合わせることができます。

ヒープの一般的なサイズによっては、heapifyを呼び出すだけで理論的に効率が悪くなる場合があるので、__siftup_/__siftdown_を再利用するよりも高速であることに注意してください。 heapifyはおそらくCで実装されていますが、内部関数のC実装は公開されていません。パフォーマンスが重要な場合は、典型的なデータに対してタイミングテストを実行して、どちらが最適かを検討することを検討してください。本当に大量のヒープがない限り、big-Oは最も重要な要素ではないかもしれません。

編集:誰かがこの回答を編集して、__siftdown_への呼び出しを削除し、次のコメントを追加しようとしました:

_siftdownは必要ありません。新しいh [i]は古いh [i]の子の中で最小であることが保証されており、古いh [i]の親(新しいh [i]の親)よりもまだ大きいです。 _siftdownは何もしません。コメントを追加するのに十分な担当者がいないため、編集する必要があります。

彼らがこのコメントで見逃したのは、_h[-1]_が_h[i]_の子ではない可能性があることです。 _h[i]_に挿入された新しい値は、ヒープの完全に異なるブランチからのものである可能性があるため、どちらの方向にもシフトする必要がある場合があります。

また、なぜsort()を使用してヒープを復元しないのかを尋ねるコメントへ:__siftup_と__siftdown_の呼び出しは両方ともO(log n)操作であり、heapifyの呼び出しはO(n)です。 sort()の呼び出しはO(n log n)操作です。 sortの呼び出しは十分速い可能性がありますが、大きなヒープの場合は不要なオーバーヘッドです。

編集済み @Seth Bruderが指摘した問題を回避します。 iが終了要素を参照する場合、_siftup()呼び出しは失敗しますが、その場合、ヒープの終わりから要素をポップしても、ヒープの不変条件は壊れません。

48
Duncan

(a)遅延削除を望まない理由を検討してください。多くの場合、これは適切なソリューションです。

(b)ヒープはリストです。他のリストと同様に、要素をインデックスで削除できますが、ヒープの不変条件を満たさなくなるため、要素を再度ヒープ化する必要があります。

11
Marcin