Приоритет очереди приоритетов Java при редактировании элементов

Я пытаюсь реализовать алгоритм Dijkstra для поиска кратчайших путей с использованием очереди приоритетов. На каждом шаге алгоритма я удаляю вершину с кратчайшим расстоянием от очереди приоритета, а затем обновляю расстояния для каждого из своих соседей в очереди приоритетов. Теперь я прочитал, что очередь приоритетов в 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);
    }

Я запустил несколько текстовых случаев, и я знаю, что очередь приоритетов не изменяется правильно каждый раз, когда я просматриваю и обновляю расстояния для вершин, но я не знаю почему. Я сделал ошибку где-нибудь?

Ответы

Ответ 1

Как вы обнаружили, очередь приоритетов не прибегает ко всем элементам всякий раз, когда элемент добавляется или удаляется. Это будет слишком дорого (помните нижнюю границу n log n для сортировки сортировки), в то время как любая разумная реализация очереди приоритетов (включая PriorityQueue) promises для добавления/удаления узлов в O (log n).

Фактически, он не сортирует свои элементы вообще (почему его итератор не может обещать итерации элементов в отсортированном порядке).

PriorityQueue не предлагает api, чтобы сообщить об измененном node, поскольку это потребует от него эффективного поиска node, который его базовый алгоритм не поддерживает. Реализация приоритетной очереди, которая делает это, довольно востребована. Статья Википедии о PriorityQueues может послужить хорошей отправной точкой для чтения об этом. Я не уверен, что такая реализация будет быстрее.

Простая идея состоит в том, чтобы удалить и затем добавить измененный node. Не делайте этого, поскольку remove() принимает O (n). Вместо этого вставьте другую запись для того же node в 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, поэтому необходимо вставить Step вместо Node s.

Ответ 2

вам нужно будет удалить и повторно вставить каждый элемент, который будет удален. (фактический элемент, а не фиктивный!). поэтому каждый раз, когда вы обновляете distances, вам нужно удалить и добавить элементы, на которые повлияла измененная вставка.

насколько я знаю, это не уникально для Java, но каждая очередь приоритетов, которая работает в O (logn) для всех ops, должна работать таким образом.

Ответ 3

Недостатком Java PriorityQueue является то, что для remove(Object) требуется время O (n), что приводит к O (n) времени, если вы хотите обновить приоритеты, удалив и добавив элементы снова. Это можно сделать за время O (log (n)). Поскольку я не смог найти рабочую реализацию через Google, я попытался реализовать ее сам (в Kotlin, хотя, насколько я предпочитаю этот язык для Java) с помощью TreeSet. Это, похоже, работает и должно иметь 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

Вы можете избежать обновления элементов в очереди, просто пометив каждую node, как указано = false по умолчанию, и добавив новые элементы в очередь по мере продвижения.

Затем поместите a node из очереди и обработайте его только в том случае, если он ранее не был посещен.

Алгоритм Dijkstra гарантирует, что каждый node посещается только один раз, поэтому, даже если у вас могут быть устаревшие узлы в очереди, вы никогда их не обрабатываете.

Также это, вероятно, проще, если вы отделите внутренности алгоритма от структуры данных графа.

public void dijkstra(Node source) throws Exception{
    PriorityQueue q = new PriorityQueue();
    source.work.distance = 0;
    q.add(new DijkstraHeapItem(source));

    while(!q.isEmpty()){
        Node n = ((DijkstraHeapItem)q.remove()).node;
        Work w = n.work;

        if(!w.visited){
            w.visited = true;

            Iterator<Edge> adiacents = n.getEdgesIterator();
            while(adiacents.hasNext()){
                Edge e = adiacents.next();
                if(e.weight<0) throw new Exception("Negative weight!!");
                Integer relaxed = e.weight + w.distance;

                Node t = e.to;
                if (t.work.previous == null || t.work.distance > relaxed){
                    t.work.distance = relaxed;
                    t.work.previous = n;
                    q.add(new DijkstraHeapItem(t));
                }
            }
        }
    }
}

Ответ 5

Проблема в том, что вы обновляете массив distances, но не соответствующую запись в queue. Чтобы обновить соответствующие объекты в очереди, вам нужно удалить и затем добавить.

Ответ 6

Я решаю эту проблему, деля мой процесс на 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());
    }
}

Надеюсь, это помогло.