Изменение порядка очереди приоритетов 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);
    }

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


person novice    schedule 05.08.2011    source источник
comment
возможный дубликат обновления Java PriorityQueue при изменении приоритета его элементов   -  person Raedwald    schedule 20.03.2015


Ответы (6)


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

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

PriorityQueue не предлагает API для информирования об измененном узле, так как это потребовало бы от него обеспечения эффективного поиска узлов, который его базовый алгоритм не поддерживает. Реализация приоритетной очереди, которая выполняет, довольно сложна. Хорошей отправной точкой для чтения об этом может быть статья в Википедии о PriorityQueues. Однако я не уверен, что такая реализация будет быстрее.

Простая идея - удалить, а затем добавить измененный узел. Не этого делать, поскольку 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.

person meriton    schedule 05.08.2011
comment
Отлично, у меня была такая же идея, но разве это не изменило бы время работы алгоритма с O (m log n) на O (m log m). Поскольку куча теперь будет содержать повторяющиеся элементы, почти равные количеству ребер в графе O (m). Думаю, это не лучшее решение. - person Samer Makary; 10.07.2014
comment
Графы поиска пути обычно плоские и поэтому имеют постоянное среднее количество соседей. То есть размер PQ будет увеличиваться на постоянный коэффициент, а его высота - на постоянное смещение. Это не должно сильно повлиять на время выполнения. С другой стороны, для эффективного изменения узлов у вызывающей стороны должен быть дескриптор каждого узла. В Java это можно сделать только с помощью ссылки на объект, которая требует, чтобы PriorityQueue сохранял объект узла для каждой записи. Это, в свою очередь, требует на постоянный коэффициент больше памяти, чем очередь с приоритетом, которая кодирует свою структуру с использованием индексов массива. - person meriton; 10.07.2014
comment
Поэтому действительно довольно непонятно, какая реализация будет быстрее. - person meriton; 10.07.2014
comment
Это не лучшее решение в случаях, когда необходимо поддерживать временную сложность, но в других случаях оно достаточно хорошо, чем реализация вашего собственного или использования каких-либо других ds. - person vibhor vaish; 25.06.2019

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

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

person amit    schedule 05.08.2011
comment
remove(Object o) не обязательно должно быть O (n). Просто библиотека Java использует наивный метод O (n), чтобы найти элемент, который нужно удалить. Если вместо этого вы используете структуру индексации, например карту, для хранения позиции элемента в куче, вы можете сделать remove в O (log (n)). - person lcn; 08.11.2013
comment
remove (Object) взять O (n), но удалить () и добавить take O (log (N)) docs.oracle.com/javase/7/docs/api/java/util/ - person Shrey; 22.11.2014

Недостатком 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)
    }

}
person winterriver    schedule 23.03.2016
comment
Вы можете использовать древовидную карту вместо древовидной структуры. - person Ashwin; 12.09.2018
comment
Не думаю, что это работает. Ключевой особенностью очереди приоритетов является то, что она позволяет дублировать, а наборы - нет. Насколько я могу судить, вы пытались разрешить дублирование, рассматривая записи как не равные, если их указатели не совпадают в вашем компараторе. Я пробовал аналогичную реализацию на чистой Java, и эта стратегия, похоже, работала, однако иногда не удавалось удалить элемент в наборе, если существовал отдельный элемент с таким же приоритетом. - person A. Davidson; 19.09.2019
comment
Я почти уверен, что поиск удаляемого элемента в дереве иногда будет идти не по той ветви, когда его сравнивают с элементом с таким же приоритетом. Поправьте меня, если я ошибаюсь, но я не думаю, что TreeSets можно модифицировать, чтобы разрешить дублирование. - person A. Davidson; 19.09.2019

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

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

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

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

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));
                }
            }
        }
    }
}
person Savino Sguera    schedule 05.08.2011
comment
я не понимаю - если вы .remove () узел из очереди, вы вызовете переупорядочение очереди, таким образом побеждая цель установки .visited = true? - person jasonk; 11.05.2012

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

person Petar Ivanov    schedule 05.08.2011

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

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

person Evan P    schedule 30.01.2012