Menu
Coddy logo textTech

ダイクストラ法

最終更新

ダイクストラ法は、非負の辺の重みを持つグラフにおいて、始点ノードから他のすべてのノードへの最短経路を求めます。各ノードに暫定距離を保持し、暫定距離が最小の未確定ノードを繰り返し確定させ、その辺を緩和します。現在のノードを経由するより短い経路が見つかるたびに隣接ノードの距離を更新します。上部の再生ボタンを押すと、各ノードが確定するにつれて距離が下がる様子を確認できます。

重要な洞察は貪欲であることです。最も近い未確定ノードが選ばれると、その距離は確定します。なぜなら、そのノードへの他のすべての経路は、すでにより遠いノードを通らなければならないからです。二分ヒープの優先度付きキューを用いると、ダイクストラ法は O((V + E) log V) 時間で実行されます。非負の重みが必要です。辺が負になり得る場合はベルマン・フォード法を使ってください。

時間計算量と空間計算量

実装計算量備考
二分ヒープO((V + E) log V)一般的で実用的な選択
配列走査O(V²)より単純。密なグラフに適する
空間O(V)距離 + 優先度付きキュー
必要条件非負の重み負の辺は貪欲な選択を壊す

ステップごとの流れ

ステップ何が起こるか
1始点の距離を 0 に、それ以外をすべて無限大に設定する。
2暫定距離が最小の未確定ノードを選ぶ。
3それを確定済みとする。最短距離がここで確定する。
4各隣接ノードについて、現在経由の距離 + 辺の重みを計算する。
5それが隣接ノードの現在の距離より小さければ緩和する。
6到達可能なすべてのノードが確定するまで繰り返す。

具体例で追う

A-B=4A-C=1C-B=2C-D=5B-D=1 を持つグラフで始点 A からの最短経路:

ステップ確定距離動作
0-A=0, B=∞, C=∞, D=∞初期化: 始点 A=0、それ以外はすべて無限大。
1A (0)B=4, C=1, D=∞A からの辺を緩和: B=4C=1 に設定。
2C (1)B=3, D=6C 経由: B=1+2=3 が 4 を更新。D=1+5=6
3B (3)D=4B 経由: D=3+1=4 が 6 を更新。
4D (4)A=0, C=1, B=3, D=4D を確定。緩和すべきものはもうない。完了。

ダイクストラ法を使うべき場面

使うべき場面避けるべき場面
すべての辺の重みが非負のときいずれかの辺が負になり得るとき: ベルマン・フォード法を使う
1 つの始点から全ノードへの最短経路が必要なとき全ペア間の最短経路が必要なとき: ワーシャル・フロイド法の方が単純
グラフが重み付きで正確な距離が欲しいときグラフが重みなしのとき: 単純な BFS の方が速く単純
優れたヒープや優先度付きキューが使えるときヒューリスティックで単一の目的地に速く到達したいとき: A* を使う

Dijkstra's Algorithmのコード

Python, JavaScript, Java, C++, Cによるクリーンで実行可能なDijkstra's Algorithmの実装です。言語を選んでコードをコピーするか、Coddyプレイグラウンドに読み込んだ状態で開けます。

PythonでのDijkstra's Algorithmのコード

Python
1import heapq2
3
4def dijkstra(graph, start):5    dist = {node: float("inf") for node in graph}6    dist[start] = 07    pq = [(0, start)]8    while pq:9        d, node = heapq.heappop(pq)10        if d > dist[node]:11            continue  # stale entry, a shorter path was already found12        for neighbor, weight in graph[node]:13            new_dist = d + weight14            if new_dist < dist[neighbor]:15                dist[neighbor] = new_dist16                heapq.heappush(pq, (new_dist, neighbor))17    return dist18
19
20graph = {21    "A": [("B", 4), ("C", 1)],22    "B": [("D", 1)],23    "C": [("B", 2), ("D", 5)],24    "D": [("E", 3)],25    "E": [],26}27
28for node, d in dijkstra(graph, "A").items():29    print(f"A -> {node}: {d}")
このコードをPythonプレイグラウンドで実行

ダイクストラ法に関するよくある質問

ダイクストラ法の時間計算量はどのくらいですか?
二分ヒープの優先度付きキューを用いると O((V + E) log V) で実行されます。各ステップで配列を走査して最小値を探す単純な版は O(V²) で、密なグラフではむしろこちらの方が速いことがあります。どちらも空間は O(V) です。
なぜダイクストラ法は負の辺の重みで機能しないのですか?
ダイクストラ法は、最も近い未確定ノードを確定した時点でその距離が確定すると仮定します。負の辺は、あとから確定済みのノードへのより短い経路を作り出し、この仮定を壊す可能性があります。負の重みを持つグラフにはベルマン・フォード法を使ってください。
ダイクストラ法と BFS の違いは何ですか?
BFS は辺を数えて(各辺の重みは実質 1)、単純なキューを使って最短経路を求めます。ダイクストラ法はこれを重み付きグラフへ一般化し、優先度付きキューを使って常に合計距離が最小のノードを展開します。重みなしグラフでは両者は同じ経路を生成します。
ダイクストラ法と A* 探索の違いは何ですか?
A* は、目的地までの残り距離を見積もるヒューリスティックを加えたダイクストラ法で、全方向へ一様に広がる代わりに探索をその目標へ導きます。ヒューリスティックがゼロのとき、A* はちょうどダイクストラ法に帰着します。単一の目的地と良い許容的ヒューリスティックがあるときは A* を、全ノードへの距離が必要なときはダイクストラ法を使ってください。
ベルマン・フォード法ではなくダイクストラ法を使うべきなのはいつですか?
すべての辺の重みが非負であれば常にダイクストラ法を使ってください。ベルマン・フォード法の O(V·E) に対して O((V + E) log V) と高速です。ベルマン・フォード法は、辺が負になり得るか、負閉路を検出する必要がある場合にのみ選びます。非負のグラフではダイクストラ法がほぼ常により良い選択です。
ダイクストラ法は確定したノードを再訪することがありますか?
いいえ。ノードがいったん確定すると、その距離は確定し、二度と緩和されません。ヒープ実装でよくある落とし穴は、ノードの距離が改善した後に優先度付きキューへ古いエントリを残してしまうことです。取り出したノードがすでに確定済みなら(取り出した距離が記録された距離を上回るなら)スキップする必要があります。このチェックを忘れても答えは正しいままですが、無駄な処理が発生します。
Coddy programming languages illustration

Coddy でアルゴリズムをマスターしよう

始める