Algorithme Dijkstra en Java

L'algorithme de chemin le plus court de Dijkstra en Java

1. Vue d'ensemble

Cet article met l'accent sur le problème du plus court chemin (SPP), l'un des problèmes théoriques fondamentaux connus en théorie des graphes, et sur la manière dont l'algorithme de Dijkstra peut être utilisé pour le résoudre.

L'objectif de base de l'algorithme est de déterminer le chemin le plus court entre un nœud de départ et le reste du graphique.

2. Problème de chemin le plus court avec Dijkstra

À partir d’un graphe à pondération positive et d’un nœud de départ (A), Dijkstra détermine le chemin et la distance les plus courts entre la source et toutes les destinations du graphe:

image

L'idée centrale de l'algorithme de Dijkstra est d'éliminer en permanence les chemins les plus longs entre le nœud de départ et toutes les destinations possibles.

Pour suivre le processus, nous avons besoin de deux ensembles distincts de nœuds, installés et non installés.

Les nœuds installés sont ceux avec une distance minimale connue de la source. L'ensemble de nœuds non définis regroupe les nœuds que nous pouvons atteindre depuis la source, mais nous ne connaissons pas la distance minimale par rapport au nœud de départ.

Voici une liste des étapes à suivre pour résoudre le SPP avec Dijkstra:

  • Réglez la distance àstartNode sur zéro.

  • Définissez toutes les autres distances sur une valeur infinie.

  • Nous ajoutons lesstartNode à l'ensemble de nœuds non réglés.

  • Bien que le jeu de nœuds en attente ne soit pas vide, nous:

    • Choisissez un nœud d’évaluation dans l’ensemble des nœuds non définis. Le nœud d’évaluation doit être celui dont la distance est la plus faible de la source.

    • Calculez les nouvelles distances pour diriger vos voisins en gardant la distance la plus basse à chaque évaluation.

    • Ajoutez des voisins qui ne sont pas encore installés au groupe de nœuds non définis.

Ces étapes peuvent être regroupées en deux étapes: initialisation et évaluation. Voyons comment cela s'applique à notre exemple de graphique:

2.1. Initialisation

Avant de commencer à explorer tous les chemins du graphe, nous devons d’abord initialiser tous les nœuds avec une distance infinie et un prédécesseur inconnu, à l’exception de la source.

Dans le cadre du processus d’initialisation, nous devons affecter la valeur 0 au nœud A (nous savons que la distance entre le nœud A et le nœud A est évidemment égale à 0).

Ainsi, chaque nœud du reste du graphique sera distingué par un prédécesseur et une distance:

image

Pour terminer le processus d'initialisation, nous devons ajouter le noeud A aux noeuds non réglés. Définissez-le pour qu'il soit sélectionné en premier dans l'étape d'évaluation. N'oubliez pas que le jeu de nœuds installés est toujours vide.

2.2. Évaluation

Maintenant que notre graphe est initialisé, nous sélectionnons le nœud avec la distance la plus basse de l'ensemble non réglé, puis nous évaluons tous les nœuds adjacents qui ne sont pas dans des nœuds réglés:

image

L’idée est d’ajouter l’épaisseur du bord à la distance du nœud d’évaluation, puis de la comparer à la distance de la destination. e.g. pour le noeud B, 0 + 10 étant inférieur à INFINITY, la nouvelle distance pour le noeud B est donc 10, et le nouveau prédécesseur est A, il en va de même pour le noeud C.

Le nœud A est ensuite déplacé des nœuds non définis vers les nœuds réglés.

Les nœuds B et C sont ajoutés aux nœuds non définis car ils peuvent être atteints, mais ils doivent être évalués.

Maintenant que nous avons deux nœuds dans l'ensemble non réglé, nous choisissons celui dont la distance est la plus faible (nœud B), puis nous le répétons jusqu'à ce que nous réglions tous les nœuds du graphique:

image

Voici un tableau qui résume les itérations qui ont été effectuées lors des étapes d'évaluation:

Itération

Instable

Colonisé

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

Final

ALL

NONE

0

A-10

A-15

B-22

D-24

D-23

 

La notation B-22, par exemple, signifie que le nœud B est le prédécesseur immédiat, avec une distance totale de 22 par rapport au nœud A.

Enfin, nous pouvons calculer les chemins les plus courts à partir du noeud A sont les suivants:

  • Nœud B: A -> B (distance totale = 10)

  • Nœud C: A -> C (distance totale = 15)

  • Nœud D: A -> B -> D (distance totale = 22)

  • Nœud E: A -> B -> D -> E (distance totale = 24)

  • Nœud F: A -> B -> D -> F (distance totale = 23)

3. Implémentation Java

Dans cette implémentation simple, nous allons représenter un graphique sous forme d'un ensemble de nœuds:

public class Graph {

    private Set nodes = new HashSet<>();

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

    // getters and setters
}

Un nœud peut être décrit avec unname, unLinkedList en référence auxshortestPath, undistance de la source et une liste de contiguïté nomméeadjacentNodes:

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
}

L'attributadjacentNodes est utilisé pour associer des voisins immédiats à la longueur d'arête. Il s'agit d'une implémentation simplifiée d'une liste d'adjacence mieux adaptée à l'algorithme de Dijkstra que la matrice d'adjacence.

Quant à l'attributshortestPath, il s'agit d'une liste de nœuds qui décrit le chemin le plus court calculé à partir du nœud de départ.

Par défaut, toutes les distances des nœuds sont initialisées avecInteger.MAX_VALUE pour simuler une distance infinie comme décrit dans l'étape d'initialisation.

Maintenant, implémentons l'algorithme Dijkstra:

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

La méthodegetLowestDistanceNode() retourne le nœud avec la distance la plus basse par rapport à l'ensemble de nœuds non définis, tandis que la méthodecalculateMinimumDistance() compare la distance réelle avec la distance nouvellement calculée en suivant le chemin nouvellement exploré:

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

Maintenant que toutes les pièces nécessaires sont en place, appliquons l'algorithme de Dijkstra sur l'exemple de graphique faisant l'objet de l'article:

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

Après le calcul, les attributsshortestPath etdistance sont définis pour chaque nœud du graphique, nous pouvons les parcourir pour vérifier que les résultats correspondent exactement à ce qui a été trouvé dans la section précédente.

4. Conclusion

Dans cet article, nous avons vu comment l'algorithme Dijkstra résout le SPP et comment l'implémenter en Java.

La mise en œuvre de ce projet simple se trouve dans lesGitHub project link suivants.