ソート対象が動的に変わる場合PriorityQueueが期待通りに動作しない


Javaアルゴリズムの練習用プログラムを作成したところ、PriorityQueueが期待どおりに動作せず、盛大にハマってしまったので、自分用のメモを残しておきます。


状況


以下のようにキューからオブジェクトを取り出すごとに、キュー内オブジェクトの
ソートに使う値が動的に変わるプログラムを作成していました。

package my.algorithm.prim;

import my.algorithm.Vertex;
import my.algorithm.VertexConnection;

import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

/**
 * {@link PriorityQueue}って、実はpoll対象の要素はソート対象から除外されてるのね・・・。
 * つまり、動的にソートに使用するキーが変わるようなオブジェクトの管理には向いていないっぽい。
 */
public class InvalidPrim {

    private static final Comparator<Vertex> VERTEX_COMPARATOR = new Comparator<Vertex>() {
        @Override
        public int compare(Vertex o1, Vertex o2) {
            System.out.println("compare: " + o1 + ", " + o2);
            return o1.getDistance() - o2.getDistance();
        }
    };

    void calculate(List<Vertex> vertices) {
        System.out.println("creation start");
        PriorityQueue<Vertex> unvisitVertices = new PriorityQueue<>(vertices.size(), VERTEX_COMPARATOR);
        for ( Vertex vertex : vertices ) {
            unvisitVertices.add(vertex);
        }
        System.out.println(unvisitVertices);

        System.out.println("iteration start");
        while ( ! unvisitVertices.isEmpty() ) {
            System.out.println("iteration...");
            System.out.println(unvisitVertices);

            Vertex target = unvisitVertices.poll();
            for (VertexConnection connection : target.getConnections() ) {
                Vertex toVertex = connection.getToVertex();
                boolean isAlreadyVisited = ! unvisitVertices.contains(toVertex);
                if ( isAlreadyVisited ) {
                    continue;
                }

                boolean smallerWeightFound = connection.getWeight() < toVertex.getDistance();
                if ( smallerWeightFound ) {
                    toVertex.setDistance(connection.getWeight());
                    toVertex.setPrev(target.getNumber());
                }
            }
        }
    }
}


しかし、実際に上記のプログラムを動かした時に表示されるprintlnの結果は以下のとおりでした。


creation start
compare: {number=1, distance=2147483647, prev=-1}, {number=0, distance=0, prev=-1}
compare: {number=2, distance=2147483647, prev=-1}, {number=0, distance=0, prev=-1}
compare: {number=3, distance=2147483647, prev=-1}, {number=1, distance=2147483647, prev=-1}
compare: {number=4, distance=2147483647, prev=-1}, {number=1, distance=2147483647, prev=-1}
[{number=0, distance=0, prev=-1}, {number=1, distance=2147483647, prev=-1}, {number=2, distance=2147483647, prev=-1}, {number=3, distance=2147483647, prev=-1}, {number=4, distance=2147483647, prev=-1}]
iteration start
iteration...
[{number=0, distance=0, prev=-1}, {number=1, distance=2147483647, prev=-1}, {number=2, distance=2147483647, prev=-1}, {number=3, distance=2147483647, prev=-1}, {number=4, distance=2147483647, prev=-1}]
compare: {number=1, distance=2147483647, prev=-1}, {number=2, distance=2147483647, prev=-1}
compare: {number=4, distance=2147483647, prev=-1}, {number=1, distance=2147483647, prev=-1}
iteration...
[{number=4, distance=4, prev=0}, {number=1, distance=2, prev=0}, {number=2, distance=2147483647, prev=-1}, {number=3, distance=8, prev=0}]
compare: {number=1, distance=2, prev=0}, {number=2, distance=2147483647, prev=-1}
compare: {number=3, distance=8, prev=0}, {number=1, distance=2, prev=0}
iteration...
[{number=1, distance=2, prev=0}, {number=3, distance=7, prev=4}, {number=2, distance=1, prev=4}]
compare: {number=2, distance=1, prev=4}, {number=3, distance=7, prev=4}
iteration...
[{number=2, distance=1, prev=4}, {number=3, distance=7, prev=4}]
iteration...
[{number=3, distance=5, prev=2}]

解説


上記の例では、「iteration...」の行が、pollの直前のキューの状態であり、その後の「compare: ...」の行が、pollした時にComparatorが出力するログになります。ログの内容を見ればわかりますが、pollされる先頭のオブジェクトはソート対象になっておらず、pollされた後のキュー内部のオブジェクトしかソートしないようです。ソートに使う項目が静的という過程ならば、上記の動作でソート回数を減らせますが、ソートに使う項目が動的に変わるとなると、先頭のオブジェクトをソートに使わない上記の動きには困ってしまいますね・・・。

参考情報


github上に上記のソースがおいてあります。

zyake/algorithm github