Какой тип данных использовать в очереди в алгоритме Дейкстры?

Я пытаюсь реализовать алгоритм Дейкстры в Java (самообучение). Я использую псевдокод, предоставленный Wikipedia (ссылка). Теперь, ближе к концу алгоритма, я должен decrease-key v in Q;. Думаю, я должен был внедрить Q с BinaryHeap или что-то в этом роде? Каким будет правильный (встроенный) тип данных для использования здесь?

private void dijkstra(int source) {
        int[] dist = new int[this.adjacencyMatrix.length];
        int[] previous = new int[this.adjacencyMatrix.length];
        Queue<Integer> q = new LinkedList<Integer>();

        for (int i = 0; i < this.adjacencyMatrix.length; i++) {
            dist[i] = this.INFINITY;
            previous[i] = this.UNDEFINED;
            q.add(i);
        }

        dist[source] = 0;

        while(!q.isEmpty()) {
            // get node with smallest dist;
            int u = 0;
            for(int i = 0; i < this.adjacencyMatrix.length; i++) {
                if(dist[i] < dist[u])
                    u = i;
            }

            // break if dist == INFINITY
            if(dist[u] == this.INFINITY) break;

            // remove u from q
            q.remove(u);

            for(int i = 0; i < this.adjacencyMatrix.length; i++) {
                if(this.adjacencyMatrix[u][i] == 1) {
                    // in a unweighted graph, this.adjacencyMatrix[u][i] always == 1;
                    int alt = dist[u] + this.adjacencyMatrix[u][i]; 
                    if(alt < dist[i]) {
                        dist[i] = alt;
                        previous[i] = u;

                        // here where I should "decrease the key"
                    }
                }
            }
        }
    }

Ответ 1

Самый простой способ - использовать приоритетную очередь и не заботиться о ранее добавленном ключе в очереди приоритетов. Это означает, что вы будете иметь каждый node несколько раз в очереди, но это вообще не повредит алгоритму. Если вы посмотрите на него, все версии node, которые были заменены, будут собраны позже, и к тому времени самое близкое расстояние уже будет определено.

Проверка if alt < dist[v]: из википедии - вот что делает эту работу. Время выполнения немного ухудшится от этого, но если вам нужна очень быстрая версия, вам нужно еще больше оптимизировать.

Ответ 2

Вы можете использовать TreeSet, (в С++ вы можете использовать std::set), чтобы реализовать приоритетную очередь для Dijkstra. A TreeSet представляет набор, но мы также разрешили описывать порядок элементов в наборе. Вам нужно сохранить узлы в наборе и использовать расстояния узлов для упорядочения узлов. node с наименьшим расстоянием будет в начале набора.

class Node {
    public int id;   // store unique id to distinguish elements in the set
    public int dist; // store distance estimates in the Node itself
    public int compareTo(Node other) {
        // TreeSet implements the Comparable interface.
        // We need tell Java how to compare two Nodes in the TreeSet.
        // For Dijstra, we want the node with the _smallest_ distance
        // to be at the front of the set.
        // If two nodes have same distance, choose node with smaller id
        if (this.dist == other.dist) {
            return Integer.valueOf(this.id).compareTo(other.id);
        } else {
            return Integer.valueOf(this.dist).compareTo(other.dist);
        }
    }
}
// ...
TreeSet<Node> nodes = new TreeSet<Node>();

Операция extract-min реализуется через следующее и занимает наихудшее время O (lgn):

Node u = nodes.pollFirst();

С помощью операции уменьшения мы удалим node со старым ключом (старая оценка расстояния) и добавим новый node с меньшим ключом (новое, лучшее расстояние оценить). Обе операции берут O (lgn) наихудшее время.

nodes.remove(v);
v.dist = /* some smaller key */
nodes.add(v);

Некоторые дополнительные примечания:

  • Вышеизложенное очень просто реализовать и, поскольку обе эти операции логарифмичны в n, в общем случае время выполнения будет O ((n + e) ​​lgn). Это считается эффективным для базовой реализации Дейкстры. См. CLRS book (ISBN: 978-0-262-03384-8). Глава 19 для доказательства этой сложности.
  • Хотя большинство учебников будут использовать очередь приоритетов для Dijkstra, Prim, A * и т.д., к сожалению, ни Java, ни С++ на самом деле не имеют реализацию очереди приоритетов с той же операцией уменьшения O (lgn)!

  • PriorityQueue существует в Java, но метод remove(Object o) не является логарифмическим, и поэтому операция с уменьшенным ключом будет O (n) вместо O (lgn) и (асимптотически), которую вы получите медленнее Дикйстра!

  • Чтобы создать TreeSet из ничего (используя цикл for), требуется время O (nlgn), которое немного медленнее по сравнению с наихудшим временем O (n) для инициализации очереди кучи/приоритета из n элементов. Однако основной цикл Dijkstra занимает время O (nlgn + elgn), которое доминирует над этим временем инициализации. Поэтому для Dijkstra инициализация TreeSet не приведет к существенному замедлению.

  • Мы не можем использовать HashSet, потому что мы заботимся о порядке ключей - мы хотим сначала вытащить наименьшее количество. Это дает нам node с наилучшей оценкой расстояния!

  • TreeSet в Java реализуется с использованием дерева Red-Black - самобалансирующегося двоичного дерева поиска. То, почему эти операции имеют логарифмическое худшее время.

  • Вы используете int для представления графических узлов, которые хороши, но когда вы вводите класс Node, вам понадобится способ связать эти два объекта. Я рекомендую построить HashMap<Integer, Node> при построении графика - это поможет отслеживать, что int соответствует тому, что Node. `

Ответ 3

Предлагаемый PriorityQueue не обеспечивает операцию уменьшения ключа. Однако его можно эмулировать, удалив элемент, а затем снова вставив его в новый ключ. Это не должно увеличивать асимптотическое время выполнения алгоритма, хотя его можно сделать немного быстрее со встроенной поддержкой.

EDIT. Это увеличивает асимптотическое время запуска, так как ожидается, что клавиша уменьшения будет O(log n) для кучи, но remove(Object) равна O(n). Похоже, что нет встроенной очереди приоритетов в Java с поддержкой эффективного уменьшения ключа.

Ответ 4

Приоритетная очередь согласно статье wiki. Это говорит о том, что классическая реализация теперь заключается в использовании "очереди с минимальным приоритетом, реализованной кучей Фибоначчи".