Алгоритм Дейкстры в Java

Алгоритм кратчайшего пути Дейкстры в Java

1. обзор

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

Основная цель алгоритма - определить кратчайший путь между начальным узлом и остальной частью графика.

2. Задача кратчайшего пути с помощью Дейкстры

Учитывая положительно взвешенный граф и начальный узел (A), Дейкстра определяет кратчайший путь и расстояние от источника до всех пунктов назначения в графе:

image

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

Чтобы отслеживать процесс, нам нужно иметь два разных набора узлов, установленных и неустановленных.

Установленные узлы - это узлы с известным минимальным расстоянием от источника. Набор неустановленных узлов объединяет узлы, которых мы можем достичь из источника, но мы не знаем минимального расстояния от начального узла.

Вот список шагов, которые необходимо выполнить, чтобы решить SPP с помощью Дейкстры:

  • Установите расстояниеstartNode на ноль.

  • Установите все другие расстояния на бесконечное значение.

  • Мы добавляемstartNode к набору неустановленных узлов.

  • Пока набор неустановленных узлов не пуст, мы:

    • Выберите узел оценки из набора неустановленных узлов, узел оценки должен быть тем, который находится на самом низком расстоянии от источника.

    • Рассчитайте новые расстояния до прямых соседей, сохраняя минимальное расстояние при каждой оценке.

    • Добавьте соседей, которые еще не установлены в набор неустановленных узлов.

Эти этапы можно объединить в два этапа: инициализация и оценка. Давайте посмотрим, как это применимо к нашему образцу графика:

2.1. инициализация

Прежде чем мы начнем исследовать все пути в графе, нам сначала нужно инициализировать все узлы с бесконечным расстоянием и неизвестным предшественником, кроме источника.

Как часть процесса инициализации, нам нужно присвоить значение 0 узлу A (мы знаем, что расстояние от узла A до узла A, очевидно, равно 0)

Итак, каждый узел в остальной части графа будет отличаться предшественником и расстоянием:

image

Чтобы завершить процесс инициализации, нам нужно добавить узел A к неустановленным узлам, чтобы он был выбран первым на этапе оценки. Имейте в виду, что установленный узел по-прежнему пуст.

2.2. оценка

Теперь, когда у нас инициализирован наш граф, мы выбираем узел с наименьшим расстоянием от неустановленного набора, затем мы оцениваем все смежные узлы, которые не находятся в установленных узлах:

image

Идея состоит в том, чтобы добавить вес края к расстоянию до оценочного узла, а затем сравнить его с расстоянием до места назначения. e.g. для узла B 0 + 10 меньше, чем INFINITY, поэтому новое расстояние для узла B равно 10, а для нового предшественника - A, то же самое относится и к узлу C.

Узел A затем перемещается из установленных узлов в установленные узлы.

Узлы B и C добавляются к неустановленным узлам, потому что они могут быть достигнуты, но их необходимо оценить.

Теперь, когда у нас есть два узла в неустановленном множестве, мы выбираем тот, у которого наименьшее расстояние (узел B), и повторяем, пока не уладим все узлы в графе:

image

Вот таблица, в которой перечислены итерации, которые были выполнены на этапах оценки:

итерация

Неурегулированный

Поселился

EvaluationNode

A

B

C

D

E

F

1

A

A

0

A-10

A-15

X-∞

X-∞

X-∞

2

B, C

A

B

0

A-10

A-15

B-22

X-∞

B-25

3

C, F, D

A, B

C

0

A-10

A-15

B-22

C-25

B-25

4

D, E, F

A, B, C

D

0

A-10

A-15

B-22

D-24

D-23

5

E, F

A, B, C, D

F

0

A-10

A-15

B-22

D-24

D-23

6

E

A, B, C, D, F

E

0

A-10

A-15

B-22

D-24

D-23

окончательный

ALL

NONE

0

A-10

A-15

B-22

D-24

D-23

 

Например, обозначение B-22 означает, что узел B является непосредственным предшественником с общим расстоянием 22 от узла A.

Наконец, мы можем вычислить кратчайшие пути от узла A следующим образом:

  • Узел B: A -> B (общее расстояние = 10)

  • Узел C: A -> C (общее расстояние = 15)

  • Узел D: A -> B -> D (общее расстояние = 22)

  • Узел E: A -> B -> D -> E (общее расстояние = 24)

  • Узел F: A -> B -> D -> F (общее расстояние = 23)

3. Реализация Java

В этой простой реализации мы представим граф в виде набора узлов:

public class Graph {

    private Set nodes = new HashSet<>();

    public void addNode(Node nodeA) {
        nodes.add(nodeA);
    }

    // getters and setters
}

Узел может быть описан с помощьюname, aLinkedList со ссылкой наshortestPath, adistance из источника и списком смежности с именемadjacentNodes:

public class Node {

    private String name;

    private List shortestPath = new LinkedList<>();

    private Integer distance = Integer.MAX_VALUE;

    Map adjacentNodes = new HashMap<>();

    public void addDestination(Node destination, int distance) {
        adjacentNodes.put(destination, distance);
    }

    public Node(String name) {
        this.name = name;
    }

    // getters and setters
}

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

Что касается атрибутаshortestPath, это список узлов, который описывает кратчайший путь, вычисленный от начального узла.

По умолчанию все расстояния между узлами инициализируются с помощьюInteger.MAX_VALUE для имитации бесконечного расстояния, как описано на этапе инициализации.

Теперь давайте реализуем алгоритм Дейкстры:

public static Graph calculateShortestPathFromSource(Graph graph, Node source) {
    source.setDistance(0);

    Set settledNodes = new HashSet<>();
    Set unsettledNodes = new HashSet<>();

    unsettledNodes.add(source);

    while (unsettledNodes.size() != 0) {
        Node currentNode = getLowestDistanceNode(unsettledNodes);
        unsettledNodes.remove(currentNode);
        for (Entry < Node, Integer> adjacencyPair:
          currentNode.getAdjacentNodes().entrySet()) {
            Node adjacentNode = adjacencyPair.getKey();
            Integer edgeWeight = adjacencyPair.getValue();
            if (!settledNodes.contains(adjacentNode)) {
                calculateMinimumDistance(adjacentNode, edgeWeight, currentNode);
                unsettledNodes.add(adjacentNode);
            }
        }
        settledNodes.add(currentNode);
    }
    return graph;
}

МетодgetLowestDistanceNode() возвращает узел с наименьшим расстоянием от набора невыполненных узлов, в то время как методcalculateMinimumDistance() сравнивает фактическое расстояние с вновь вычисленным при следовании по вновь исследованному пути:

private static Node getLowestDistanceNode(Set < Node > unsettledNodes) {
    Node lowestDistanceNode = null;
    int lowestDistance = Integer.MAX_VALUE;
    for (Node node: unsettledNodes) {
        int nodeDistance = node.getDistance();
        if (nodeDistance < lowestDistance) {
            lowestDistance = nodeDistance;
            lowestDistanceNode = node;
        }
    }
    return lowestDistanceNode;
}
private static void CalculateMinimumDistance(Node evaluationNode,
  Integer edgeWeigh, Node sourceNode) {
    Integer sourceDistance = sourceNode.getDistance();
    if (sourceDistance + edgeWeigh < evaluationNode.getDistance()) {
        evaluationNode.setDistance(sourceDistance + edgeWeigh);
        LinkedList shortestPath = new LinkedList<>(sourceNode.getShortestPath());
        shortestPath.add(sourceNode);
        evaluationNode.setShortestPath(shortestPath);
    }
}

Теперь, когда все необходимые элементы на месте, давайте применим алгоритм Дейкстры к образцу графа, являющемуся предметом статьи:

Node nodeA = new Node("A");
Node nodeB = new Node("B");
Node nodeC = new Node("C");
Node nodeD = new Node("D");
Node nodeE = new Node("E");
Node nodeF = new Node("F");

nodeA.addDestination(nodeB, 10);
nodeA.addDestination(nodeC, 15);

nodeB.addDestination(nodeD, 12);
nodeB.addDestination(nodeF, 15);

nodeC.addDestination(nodeE, 10);

nodeD.addDestination(nodeE, 2);
nodeD.addDestination(nodeF, 1);

nodeF.addDestination(nodeE, 5);

Graph graph = new Graph();

graph.addNode(nodeA);
graph.addNode(nodeB);
graph.addNode(nodeC);
graph.addNode(nodeD);
graph.addNode(nodeE);
graph.addNode(nodeF);

graph = Dijkstra.calculateShortestPathFromSource(graph, nodeA);

После расчета атрибутыshortestPath иdistance устанавливаются для каждого узла в графе, мы можем перебирать их, чтобы убедиться, что результаты точно соответствуют тому, что было найдено в предыдущем разделе.

4. Заключение

В этой статье мы увидели, как алгоритм Дейкстры решает SPP и как реализовать его на Java.

Реализацию этого простого проекта можно найти в следующихGitHub project link.