Pythonには、ヒープデータ構造を実装するheapq
モジュールがあり、いくつかの基本的な操作(プッシュ、ポップ)をサポートしています。
O(log n)のヒープからi番目の要素を削除する方法heapq
でも可能ですか、それとも別のモジュールを使用する必要がありますか?
ドキュメントの下部に例があります: http://docs.python.org/library/heapq.html これは可能なアプローチを示唆しています-これは私が望むものではありません。単に削除済みとしてマークするのではなく、要素を削除してほしい。
ヒープから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()
呼び出しは失敗しますが、その場合、ヒープの終わりから要素をポップしても、ヒープの不変条件は壊れません。
(a)遅延削除を望まない理由を検討してください。多くの場合、これは適切なソリューションです。
(b)ヒープはリストです。他のリストと同様に、要素をインデックスで削除できますが、ヒープの不変条件を満たさなくなるため、要素を再度ヒープ化する必要があります。