web-dev-qa-db-ja.com

STLpriority_queueで効率的な優先度更新を行う方法は?

いくつかのオブジェクトのpriority_queueがあります:

typedef priority_queue<Object> Queue;
Queue queue;

オブジェクトの1つの優先度が変更される場合があります。キュー内のそのオブジェクトの優先度を、効率的な方法で更新できる必要があります。現在、私はこの方法を使用していますが、これは機能しますが非効率的です。

Queue newQueue;
while (!queue.empty())
{
  Object obj=queue.top();
  queue.pop();

  if (priorityHasChanged(obj))
    newQueue.Push_back(Object(new_priority));
  else
    newQueue.Push_back(obj);
}

newQueue.swap(queue); // this only works because I actually subclassed the priority_queue
                 // class and exposed a swap method that swaps in the container

当時は急いでいたので、この方法で実装しました。これは、問題なく動作することを確信できる最も迅速な方法でした。しかし、これよりも良い方法が必要です。本当に私が欲しいのは、次のいずれかの方法です。

  • 優先度が変更されたインスタンスを抽出し、新しい優先度の値で新しいインスタンスを挿入します
  • 変更された優先度でインスタンスを更新してから、正しくソートされるようにキューを更新します

これを行うための最良の方法は何ですか?

32

基になるdeque/vector/listなどを取得できないため、標準の優先度キューでは運が悪いと思います。独自に実装する必要があります-それほど難しくはありません。

7
anon

どちらも実際の更新を実行しませんが、問題を解決するために2つの選択肢を提案できます。

  1. 使用 priority_queueおよび要素を更新するたびに要素をプッシュします。キューに役に立たないエントリがあるという事実を受け入れます。最上位の値をポップするときは、最新の値が含まれているかどうかを確認してください。そうでない場合は、それを無視して次をポップします。

    このようにして、更新された要素の削除を最上位になるまで遅らせます。このアプローチが、ダイクストラアルゴリズムを実現するトッププログラマーによって使用されていることに気づきました。

  2. setを使用します。また、対数時間で最大の要素を抽出できるようにソートされています。また、古い要素を削除してから再度挿入することもできます。したがって、更新操作はまだ不可能ですが、削除と再挿入は可能です。

両方のアプローチの複雑さは同じようです。

45
Jarekczek

適切なデータ構造は「フィボナッチヒープ」と呼ばれます。ただし、自分で実装する必要があります。挿入/更新はO(1) ExtractMinはO(logn)です

5
Nima

STL(私が知っている)でこれを行う最も簡単な方法は、エントリを削除し、その優先度を更新してから、再挿入することです。 std :: priority_queueを使用すると、これを行うのは非常に遅くなりますが、std :: setを使用して同じことを行うことができます。残念ながら、オブジェクトがセット内にある場合は、オブジェクトの優先度を変更しないように注意する必要があります。

Std :: multimap(優先度から値へのマッピング用)とstd :: map(値から優先度へのマッピング用)を結合するmutable_priority_queueクラスベースを実装しました。これにより、アイテムの挿入/削除、および対数での既存の値の更新が可能になります。時間。あなたはコードとそれを使用する方法の例を得ることができます ここ

5
James Gregosn

高性能の更新可能な優先度キューを実装し、利用できるようにしました github上

これはあなたが通常それを使用する方法です:

better_priority_queue::updatable_priority_queue<int,int> pQ;
pQ.Push(0, 30);   // or pQ.set(0, 30)
pQ.Push(1, 20);
pQ.Push(2, 10);

pQ.update(2, 25); // or pQ.set(2, 25)

while(!pQ.empty())
    std::cout << pQ.pop_value().key << ' ';

// Outputs: 0 2 1

Jarekczekの答えを補足するために、実際にsetと「役に立たないエントリを持つ純粋なヒープ」アプローチの両方が同じ複雑さを持っている場合、stl::setバージョンは通常stl::priority_queueバージョンよりもパフォーマンスがはるかに遅くなりますstl::priority_queueは可能な限り純粋で高速なバイナリヒープであるのに対し、深さが2 * log_2(n_elements)未満であり、定期的な更新が必要な赤黒木で実装されているという事実。これが、ダイクストラを実装するときに通常使用される理由です。

ただし、少数のベースノードで多くの更新を行う場合は、セットアプローチの方が高速になる可能性があります。これは、私のライブラリを使用することで最も改善される場所でもあります。

1
Ten

残念ながら、priority_queueの値を更新することはできません。 priority_queueはそのようなインターフェースを提供しません。

Jarekczekが言ったようにsetを使用するか、 このソリューション (make_heapを使用)を使用する方がよいと思います。

0
Yeongjin Choi

例を使用してreplace_ifを確認することをお勧めします ここ

0
dubnde