web-dev-qa-db-ja.com

Java要素編集時の優先キューの並べ替え

プライオリティキューを使用して最短パスを見つけるためのダイクストラのアルゴリズムを実装しようとしています。アルゴリズムの各ステップで、優先度キューから距離が最も短い頂点を削除し、優先度キュー内の各近傍の距離を更新します。 Javaの優先度キューは、その要素(順序を決定する要素)を編集しても再順序付けされない)であることを読んだので、挿入と削除によって強制的に再順序付けしようとしましたダミーの頂点ですが、これは機能していないようで、私はそれを理解しようとして立ち往生しています。

これは頂点オブジェクトとコンパレータのコードです

class vertex {
    int v, d;
    public vertex(int num, int dis) {
        v=num;
        d=dis;
    }
}

class VertexComparator implements Comparator {
    public int compare (Object a, Object b) {
        vertex v1 = (vertex)a;
        vertex v2 = (vertex)b;
        return v1.d-v2.d;
    }
 }

次に、ここでアルゴリズムを実行します。

    int[] distances=new int[p];
    Comparator<vertex> comparator = new VertexComparator();
    PriorityQueue<vertex> queue = new PriorityQueue<vertex>(p, comparator);
    for(int i=0; i<p; i++) {
        if(i!=v) {
            distances[i]=MAX;
        }
        else {
            distances[i]=0;
        }
        queue.add(new vertex(i, distances[i]));
    }
    // run dijkstra
    for(int i=0; i<p; i++) {
        vertex cur=queue.poll();
        Iterator itr = queue.iterator();
        while(itr.hasNext()) {
            vertex test = (vertex)(itr.next());
            if(graph[cur.v][test.v]!=-1) {
                test.d=Math.min(test.d, cur.d+graph[cur.v][test.v]);
                distances[test.v]=test.d;
            }
        }
        // force the PQ to resort by adding and then removing a dummy vertex
        vertex resort = new vertex(-1, -1);
        queue.add(resort);
        queue.remove(resort);
    }

私はいくつかのテキストケースを実行しましたが、優先度キューが頂点の距離を更新するたびに正しく並べ替えられないことはわかっていますが、その理由はわかりません。どこかでエラーが発生しましたか?

25
novice

発見したとおり、要素が追加または削除されても、優先度キューはすべての要素を再ソートしません。これを行うとコストがかかりすぎます(比較ソートのn log nの下限を覚えておいてください)。一方、合理的な優先度キューの実装(PriorityQueueを含む)は、O(log n)でノードを追加/削除することを約束します。

実際、その要素はまったくソートされません(そのため、イテレータは要素をソートされた順序で反復することを約束できません)。

PriorityQueueは、変更されたノードについて通知するためのAPIを提供していません。これには、基礎となるアルゴリズムがサポートしていない効率的なノードルックアップを提供する必要があるためです。実行する優先キューの実装はかなり複雑です。 PriorityQueuesに関するWikipediaの記事 は、それについて読むための良い出発点になるかもしれません。ただし、そのような実装の方が速いかどうかはわかりません。

簡単なアイデアは、変更されたノードを削除してから追加することです。 remove()はO(n)を取るので、しないようにしてください。代わりに、同じノードの別のエントリをPriorityQueueに挿入し、キューをポーリングするときに重複を無視します。つまり、次のようにします。

PriorityQueue<Step> queue = new PriorityQueue();

void findShortestPath(Node start) {
    start.distance = 0;
    queue.addAll(start.steps());

    Step step;
    while ((step = queue.poll()) != null) {
        Node node = step.target;
        if (!node.reached) {
            node.reached = true;
            node.distance = step.distance;
            queue.addAll(node.steps());
        }
    }

}

編集:PQの要素の優先順位を変更することはお勧めできません。そのため、Stepsの代わりにNodesを挿入する必要があります。

22
meriton

JavaのPriorityQueueの欠点は、remove(Object)にO(n)時間を要するため、O(n)要素を削除して再度追加することで優先度を更新したい場合は、時間内に実行できます。ただし、時間内に実行できますO(log(n))。動作する実装を見つけることができなかったため、 Google、TreeSetを使用して自分で実装しました(ただし、KotlinではJavaよりもその言語が好きです)。これは機能するようで、O(log(n))追加/更新/削除(更新はaddを介して行われます):

// update priority by adding element again (old occurrence is removed automatically)
class DynamicPriorityQueue<T>(isMaxQueue: Boolean = false) {

    private class MyComparator<A>(val queue: DynamicPriorityQueue<A>, isMaxQueue: Boolean) : Comparator<A> {
        val sign = if (isMaxQueue) -1 else 1

        override fun compare(o1: A, o2: A): Int {
            if (o1 == o2)
                return 0
            if (queue.priorities[o2]!! - queue.priorities[o1]!! < 0)
                return sign
            return -sign
        }

    }

    private val priorities = HashMap<T, Double>()
    private val treeSet = TreeSet<T>(MyComparator(this, isMaxQueue))

    val size: Int
        get() = treeSet.size

    fun isEmpty() = (size == 0)

    fun add(newElement: T, priority: Double) {
        if (newElement in priorities)
            treeSet.remove(newElement)
        priorities[newElement] = priority
        treeSet.add(newElement)
    }

    fun remove(element: T) {
        treeSet.remove(element)
        priorities.remove(element)
    }

    fun getPriorityOf(element: T): Double {
        return priorities[element]!!
    }


    fun first(): T = treeSet.first()
    fun poll(): T {
        val res = treeSet.pollFirst()
        priorities.remove(res)
        return res
    }

    fun pollWithPriority(): Pair<T, Double> {
        val res = treeSet.pollFirst()
        val priority = priorities[res]!!
        priorities.remove(res)
        return Pair(res, priority)
    }

}
4
winterriver

編集された各要素を削除して再挿入する必要があります。 (実際の要素であり、ダミーの要素ではありません!)したがって、distancesを更新するたびに、変更されたメインツリーの影響を受けた要素を削除して追加する必要があります。

私の知る限り、これはJavaに固有のものではありませんが、すべての操作でO(logn))で実行されるすべての優先度キューは、このように動作する必要があります。

4
amit

問題は、distances配列を更新するが、queueの対応するエントリを更新しないことです。キュー内の適切なオブジェクトを更新するには、削除してから追加する必要があります。

0
Petar Ivanov

この問題を解決するには、プロセスをtimeSlotsに分割し(タイムスケジューラで十分です)、ネイティブのPriorityQueueを拡張します。したがって、このメソッドのキーが次のコードである通知メソッドを実装します。

// If queue has one or less elements, then it shouldn't need an ordering
// procedure
if (size() > 1)
{
    // holds the current size, as during this process the size will
    // be vary
    int tmpSize = size();
    for (int i = 1; i < tmpSize; i++)
    {
        add(poll());
    }
}

お役に立てば幸いです。

0
Evan P