JavaでのDijkstraアルゴリズム

Javaのダイクストラ最短経路アルゴリズム

1. 概要

この記事では、グラフ理論で知られている基本的な理論上の問題の1つである最短経路問題(SPP)と、ダイクストラアルゴリズムを使用してそれを解決する方法に重点を置いています。

アルゴリズムの基本的な目標は、開始ノードとグラフの残りの部分との間の最短経路を決定することです。

2. ダイクストラの最短経路問題

正に重み付けされたグラフと開始ノード(A)が与えられると、ダイクストラはグラフ内のソースからすべての宛先までの最短経路と距離を決定します。

image

ダイクストラアルゴリズムのコアアイデアは、開始ノードとすべての可能な宛先の間の長いパスを継続的に排除することです。

プロセスを追跡するために、2つの異なるノードセット、つまり、確定済みノードと未確定ノードが必要です。

安定したノードは、ソースから既知の最小距離を持つノードです。 未決済のノードセットは、ソースから到達できるノードを収集しますが、開始ノードからの最小距離はわかりません。

ダイクストラでSPPを解決するために従うべき手順のリストは次のとおりです。

  • 距離をstartNodeに設定してゼロにします。

  • 他のすべての距離を無限値に設定します。

  • 未決済のノードセットにstartNodeを追加します。

  • 未設定のノードセットは空ではありませんが、次のことを行います。

    • 未解決のノードセットから評価ノードを選択します。評価ノードは、ソースからの距離が最も低いノードでなければなりません。

    • 各評価で最短距離を維持することにより、直接の隣人までの新しい距離を計算します。

    • まだ解決されていない近隣ノードを未解決のノードセットに追加します。

これらのステップは、初期化と評価の2つの段階に集約できます。 それがサンプルグラフにどのように適用されるかを見てみましょう。

2.1. 初期化

グラフ内のすべてのパスの探索を開始する前に、最初に、ソースを除くすべてのノードを無限の距離と未知の先行ノードで初期化する必要があります。

初期化プロセスの一部として、ノードAに値0を割り当てる必要があります(ノードAからノードAまでの距離は明らかに0であることがわかっています)

したがって、グラフの残りの各ノードは、先行ノードと距離で区別されます。

image

初期化プロセスを完了するには、ノードAを未解決のノードに追加して、評価ステップで最初に選択されるように設定する必要があります。 整定されたノードセットはまだ空であることに注意してください。

2.2. 評価

グラフが初期化されたので、未解決のセットから最も距離の短いノードを選択し、その後、解決済みノードにないすべての隣接ノードを評価します。

image

アイデアは、エッジの重みを評価ノードの距離に追加し、それを宛先の距離と比較することです。 e.g. ノードBの場合、0 10はINFINITYよりも低いため、ノードBの新しい距離は10、新しい先行ノードはAです。ノードCにも同じことが当てはまります。

次に、ノードAが未決済ノードセットから決済済みノードに移動します。

ノードBとCは到達可能であるため、未処理のノードに追加されますが、評価する必要があります。

未設定のセットに2つのノードがあるので、距離が最も短いノード(ノード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

あいうえお

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が直前の先行ノードであり、ノードAからの合計距離が22であることを意味します。

最後に、ノード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
}

ノードは、nameshortestPathを参照するLinkedList、ソースからのdistance、および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にあります。