web-dev-qa-db-ja.com

Java PriorityQueueの要素が優先度を変更する場合のPriorityQueue

PriorityQueueを使用してオブジェクトを並べ替えるには、Comparatorを使用しようとしています。

これは簡単に実現できますが、オブジェクトクラス変数(コンパレータが優先度を計算する)は、最初の挿入後に変更される場合があります。ほとんどの人は、オブジェクトを削除し、値を更新してから再挿入するという簡単な解決策を提案しています。これは、優先キューのコンパレーターが実行されるときです。

これを行うために、PriorityQueueの周りにラッパークラスを作成する以外のより良い方法はありますか?

51
Marcus Whybrow

新しい要素が挿入されたときに適切な位置に新しい要素を配置することでキューが機能するため、削除して再挿入する必要があります。これは、キューから取り出すたびに最も優先度の高い要素を見つけるよりもはるかに高速です。欠点は、要素が挿入された後に優先順位を変更できないことです。 TreeMapにも同じ制限があります(HashMapと同様に、挿入後に要素のハッシュコードが変更されると壊れます)。

ラッパーを作成する場合は、比較コードをエンキューからデキューに移動できます。エンキュー時にソートする必要がなくなります(変更を許可すると、作成される順序は信頼できないためです)。

ただし、これはパフォーマンスが低下するため、優先順位を変更した場合はキューで同期する必要があります。優先度を更新するときに同期コードを追加する必要があるため、デキューおよびエンキューすることもできます(どちらの場合もキューへの参照が必要です)。

33
Thilo

Java実装があるかどうかはわかりませんが、キー値を変更する場合は、フィボナッチヒープを使用できます。これには、O(1)償却コストがdecrease通常のヒープのようなO(log(n))ではなく、ヒープ内のエントリのキー値。

10
Jon McCaffrey

whenが直接変化するかどうかに大きく依存します。

値がいつ変更されるかがわかっている場合は、削除して再挿入することができます(実際には、削除にはヒープ全体の線形スキャンが必要になるため、かなりコストがかかります!)。さらに、この状況ではUpdatableHeap構造(在庫なしJavaしかし))を使用できます。基本的に、それはハッシュマップ内の要素の位置を追跡するヒープです。要素が変更された場合、ヒープを修復できます3番目に、同じことを行うフィボナッチヒープを検索できます.

更新レートによっては、毎回リニアスキャン/クイックソート/ QuickSelectも機能する場合があります。特に、pullsよりも多くの更新がある場合、これが方法です。更新のバッチがあり、プル操作のバッチがある場合は、おそらくQuickSelectが最適です。

5
Anony-Mousse

再ヒープ化をトリガーするには、これを試してください:

if(!priorityQueue.isEmpty()) {
    priorityQueue.add(priorityQueue.remove());
}
3
Techflash

実装できる簡単なソリューションの1つは、その要素を再度優先度キューに追加するだけです。要素を抽出する方法は変わりませんが、より多くのスペースを消費しますが、実行時間に影響を与えるほど多くはなりません。

これを証明するために、以下のdijkstraアルゴリズムを考えてみましょう。

_public int[] dijkstra() {
int distance[] = new int[this.vertices];
int previous[] = new int[this.vertices];
for (int i = 0; i < this.vertices; i++) {
    distance[i] = Integer.MAX_VALUE;
    previous[i] = -1;
}
distance[0] = 0;
previous[0] = 0;
PriorityQueue<Node> pQueue = new PriorityQueue<>(this.vertices, new NodeComparison());
addValues(pQueue, distance);
while (!pQueue.isEmpty()) {
    Node n = pQueue.remove();
    List<Edge> neighbours = adjacencyList.get(n.position);
    for (Edge neighbour : neighbours) {
        if (distance[neighbour.destination] > distance[n.position] + neighbour.weight) {
            distance[neighbour.destination] = distance[n.position] + neighbour.weight;
            previous[neighbour.destination] = n.position;
            pQueue.add(new Node(neighbour.destination, distance[neighbour.destination]));
        }
    }
}
return previous;
_

}

ここでは、pQueue.add(new Node(neighbour.destination, distance[neighbour.destination]));行に関心があります。特定のノードを削除して再度追加することで優先度を変更するのではなく、同じ値で優先度の異なる新しいノードを追加するだけです。ここで抽出時には、ここに最小ヒープを実装しているため、常にこのノードを最初に取得します。これよりも大きい値(優先順位が低い)のノードは常に抽出され、この方法で、以前のノードよりも前にすべての隣接ノードがすでにリラックスしています要素が抽出されます。

1
Harshit Agrawal

私が試したことがあり、これまでのところうまくいきます;それ以外の場合は、ヘッドがポーリングされると、とにかくヒープがヒープ化されるため、ポーリングなしで変更できます。

ダウンサイド:これにより、同じ優先度を持つオブジェクトの優先度が変更されます。

1
Phoenix King