Dijkstra-Algorithmus in Java

Dijkstra Shortest Path Algorithmus in Java

1. Überblick

Der Schwerpunkt dieses Artikels liegt auf dem Problem des kürzesten Pfades (SPP), einem der grundlegenden theoretischen Probleme, die in der Graphentheorie bekannt sind, und wie der Dijkstra-Algorithmus zu seiner Lösung verwendet werden kann.

Das grundlegende Ziel des Algorithmus besteht darin, den kürzesten Weg zwischen einem Startknoten und dem Rest des Graphen zu bestimmen.

2. Problem mit dem kürzesten Weg mit Dijkstra

Bei einem positiv gewichteten Graphen und einem Startknoten (A) bestimmt Dijkstra den kürzesten Weg und die kürzeste Entfernung von der Quelle zu allen Zielen im Graphen:

image

Die Kernidee des Dijkstra-Algorithmus besteht darin, längere Pfade zwischen dem Startknoten und allen möglichen Zielen kontinuierlich zu eliminieren.

Um den Prozess verfolgen zu können, müssen zwei unterschiedliche Knotensätze festgelegt und nicht festgelegt werden.

Abgerechnete Knoten haben einen bekannten Mindestabstand zur Quelle. Das nicht festgelegte Knotenset sammelt Knoten, die wir von der Quelle aus erreichen können, aber wir kennen den Mindestabstand vom Startknoten nicht.

Hier ist eine Liste der Schritte, die Sie ausführen müssen, um das SPP mit Dijkstra zu lösen:

  • Setzen Sie den Abstand aufstartNode auf Null.

  • Stellen Sie alle anderen Abstände auf einen unendlichen Wert ein.

  • Wir addieren diestartNode zu den nicht veränderten Knoten.

  • Während die Menge der nicht erledigten Knoten nicht leer ist, führen wir Folgendes aus:

    • Wählen Sie einen Auswertungsknoten aus dem Satz nicht festgelegter Knoten. Der Auswertungsknoten sollte der Knoten mit dem geringsten Abstand zur Quelle sein.

    • Berechnen Sie neue Abstände zu direkten Nachbarn, indem Sie bei jeder Auswertung den niedrigsten Abstand einhalten.

    • Fügen Sie Nachbarn hinzu, die sich noch nicht in der Gruppe der nicht erledigten Knoten befinden.

Diese Schritte können in zwei Phasen zusammengefasst werden: Initialisierung und Auswertung. Mal sehen, wie sich das auf unser Beispieldiagramm auswirkt:

2.1. Initialisierung

Bevor wir beginnen, alle Pfade im Diagramm zu untersuchen, müssen wir zunächst alle Knoten mit einer unendlichen Entfernung und einem unbekannten Vorgänger mit Ausnahme der Quelle initialisieren.

Als Teil des Initialisierungsprozesses müssen wir Knoten A den Wert 0 zuweisen (wir wissen, dass der Abstand von Knoten A zu Knoten A offensichtlich 0 ist).

So wird jeder Knoten im Rest des Diagramms mit einem Vorgänger und einem Abstand unterschieden:

image

Um den Initialisierungsprozess abzuschließen, müssen wir Knoten A zu den nicht festgelegten Knoten hinzufügen, damit er im Bewertungsschritt zuerst ausgewählt wird. Denken Sie daran, dass der festgelegte Knotensatz noch leer ist.

2.2. Auswertung

Nachdem wir unser Diagramm initialisiert haben, wählen wir den Knoten mit dem geringsten Abstand von der nicht festgelegten Menge aus und werten dann alle benachbarten Knoten aus, die sich nicht in festgelegten Knoten befinden:

image

Die Idee ist, das Kantengewicht zur Entfernung des Bewertungsknotens hinzuzufügen und es dann mit der Entfernung des Ziels zu vergleichen. e.g. für Knoten B ist 0 + 10 niedriger als INFINITY, daher ist der neue Abstand für Knoten B 10 und der neue Vorgänger ist A, dasselbe gilt für Knoten C.

Knoten A wird dann von den nicht abgewickelten Knoten zu den abgewickelten Knoten verschoben.

Die Knoten B und C werden zu den nicht erledigten Knoten hinzugefügt, da sie erreichbar sind, aber ausgewertet werden müssen.

Nachdem wir nun zwei Knoten in der ungeklärten Menge haben, wählen wir den Knoten mit dem geringsten Abstand (Knoten B) und wiederholen dies, bis wir alle Knoten in der Grafik festgelegt haben:

image

In der folgenden Tabelle sind die Iterationen zusammengefasst, die während der Bewertungsschritte durchgeführt wurden:

Wiederholung

Unruhig

Erledigt

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

Finale

ALL

NONE

0

A-10

A-15

B-22

D-24

D-23

 

Die Notation B-22 bedeutet zum Beispiel, dass Knoten B der unmittelbare Vorgänger ist, mit einer Gesamtentfernung von 22 von Knoten A.

Schließlich können wir die kürzesten Pfade von Knoten A wie folgt berechnen:

  • Knoten B: A -> B (Gesamtentfernung = 10)

  • Knoten C: A -> C (Gesamtentfernung = 15)

  • Knoten D: A -> B -> D (Gesamtentfernung = 22)

  • Knoten E: A -> B -> D -> E (Gesamtentfernung = 24)

  • Knoten F: A -> B -> D -> F (Gesamtentfernung = 23)

3. Java-Implementierung

In dieser einfachen Implementierung stellen wir einen Graphen als Satz von Knoten dar:

public class Graph {

    private Set nodes = new HashSet<>();

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

    // getters and setters
}

Ein Knoten kann mit einemname, einemLinkedList in Bezug aufshortestPath, einemdistance von der Quelle und einer Adjazenzliste mit dem NamenadjacentNodes beschrieben werden:

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
}

Das AttributadjacentNodes wird verwendet, um unmittelbare Nachbarn mit der Kantenlänge zu verknüpfen. Dies ist eine vereinfachte Implementierung einer Adjazenzliste, die für den Dijkstra-Algorithmus geeigneter ist als die Adjazenzmatrix.

Das AttributshortestPath ist eine Liste von Knoten, die den kürzesten Pfad beschreibt, der vom Startknoten berechnet wird.

Standardmäßig werden alle Knotenabstände mitInteger.MAX_VALUE initialisiert, um einen unendlichen Abstand zu simulieren, wie im Initialisierungsschritt beschrieben.

Implementieren wir nun den Dijkstra-Algorithmus:

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;
}

Die MethodegetLowestDistanceNode() gibt den Knoten mit dem geringsten Abstand zu den nicht festgelegten Knoten zurück, während die MethodecalculateMinimumDistance() die tatsächliche Entfernung mit der neu berechneten Entfernung vergleicht, während sie dem neu erkundeten Pfad folgt:

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);
    }
}

Nachdem alle erforderlichen Elemente vorhanden sind, wenden wir den Dijkstra-Algorithmus auf das Beispieldiagramm an, das Gegenstand des Artikels ist:

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);

Nach der Berechnung werden die AttributeshortestPath unddistance für jeden Knoten im Diagramm festgelegt. Wir können sie durchlaufen, um zu überprüfen, ob die Ergebnisse genau mit denen übereinstimmen, die im vorherigen Abschnitt gefunden wurden.

4. Fazit

In diesem Artikel haben wir gesehen, wie der Dijkstra-Algorithmus das SPP löst und wie es in Java implementiert wird.

Die Implementierung dieses einfachen Projekts finden Sie in den folgendenGitHub project link.