web-dev-qa-db-ja.com

ヒープ内の要素を更新する方法は? (優先キュー)

最小/最大ヒープアルゴリズムを使用する場合、優先順位が変更される場合があります。これを処理する1つの方法は、要素を削除して挿入し、キューの順序を更新することです。

配列を使用して実装された優先度キューの場合、これはパフォーマンスのボトルネックになる可能性があり、特に優先度への変更が小さい場合には回避可能と思われます。

これが優先キューの標準操作ではない場合でも 、これは私のニーズに合わせて変更できるカスタム実装です。

Min/max-heapの要素を更新するためのベストプラクティスの方法はよく知られていますか?


背景情報:バイナリツリーの専門家ではありません。優先度キューに要素を再挿入するパフォーマンスボトルネックのあるコードを継承しました。私は新しい要素を並べ替える最小ヒープの再挿入関数を作成しました-これは(削除と挿入)よりもかなり改善されていますが、これは他の人がよりエレガントに解決したかもしれない種類の問題のようですway。

もし助ければコードにリンクできますが、実装の詳細にはあまり焦点を合わせたくありません-このQ&Aはおそらく一般的なものになる可能性があるためです

9
ideasman42

典型的な解決策

通常の解決策は、要素を無効としてマークし、新しい要素を挿入し、無効なエントリがポップオフされるのでそれらを削除することです。

代替ソリューション

このアプローチでは不十分な場合、変更される値の場所がわかっている限り、O(log n)ステップで最小ヒープ不変量を復元することが可能です

最小ヒープは、「siftup」と「siftdown」という2つのプリミティブを使用して構築および維持されることを思い出してください(ただし、さまざまなソースが、どちらがアップかダウンかについて異なる意見を持っています)。これらの1つは値をツリーにプッシュし、もう1つは値を上にフロートします。

ケース1:値が増加する

新しい値x1が古い値x0よりも大きい場合、 parent(x) <= x0 < x1であるため、xの下のツリーのみを修正する必要があります。ただPushxツリーを下にスワッピングしてx 2つの子のうち小さい方xはその子の1つより大きい

ケース2:値が減少する

新しい値x1が古い値xよりも小さい場合、下のツリーxは、x1 < x0 <= either_child(x)であるため、調整の必要はありません。代わりに、上に移動し、親とxを交換しながら、xはその親よりも小さい。兄弟ノードは、より低い値で置き換えられる可能性のある親以上であるため、考慮する必要はありません。

ケース3:値は変更されません

作業は必要ありません。既存の不変式は変更されていません。

Pythonでの作業コード

1,000,000回の試行のテスト:ランダムヒープを作成します。ランダムに選択された値を変更します。ヒープ状態を復元します。結果が最小ヒープであることを確認します。

from heapq import _siftup, _siftdown, heapify
from random import random, randrange, choice

def is_minheap(arr):
    return all(arr[i] >= arr[(i-1)//2] for i in range(1, len(arr)))

n = 40
trials = 1_000_000
for _ in range(trials):

    # Create a random heap
    data = [random() for i in range(n)]
    heapify(data)

    # Randomly alter a heap element
    i = randrange(n)
    x0 = data[i]
    x1 = data[i] = choice(data)

    # Restore the heap
    if x1 > x0:                 # value is increased
        _siftup(data, i)
    Elif x1 < x0:               # value is decreased
        _siftdown(data, 0, i)

    # Verify the results
    assert is_minheap(data), direction
19

作業コードへのリンクが含まれているため、自身の質問への回答を投稿します。


これは実際、非常に簡単です。

通常、min-heapの実装には、順序付けのための関数 例:BubbleUp/Down があります。

これらの関数は、現在の値に対する変更に応じて、変更された要素で実行できます。例えば:

if new_value < old_value {
    heap_bubble_up(heap, node);
} else if new_value > old_value {
    heap_bubble_down(heap, node);
}

操作の数は値の分布に依存しますが、これは、ソートされたリストを維持する場合と同等または少ないステップになります。

一般に、小さな変更は、remove + insertよりもはるかに効率的です

codetest を参照してください。初期ルックアップなしで挿入/削除/再優先の最小ヒープを実装します(呼び出し元は不透明な参照を保存します)。


必要な要素のみを並べ替えるだけでも、大きなヒープに対して多くの操作が必要になる場合があります。

これが非効率的である場合、最小ヒープは適切ではない可能性があります。

バイナリツリーの方が優れている場合があり(たとえば、赤黒ツリー)、削除および挿入のスケーリングが向上します。

しかし、min-heapができるように、rb-treesの in-place in-place の機能についてはわかりません。

3
ideasman42