コンテンツにスキップ

第12回:最短経路(ダイクストラ法・ベルマンフォード法)

前回は DFS・BFS でグラフを歩いた。特に BFS のレベル = 最短の辺数(区間数) が出ることを見た。ただしそれは「1 辺=1」の世界、つまり重みなしのときだけ。今日は、辺に重み(所要時間など)がついたときの本当の最短を求める。

前回の補足:DFS の訪問順の約束

DFS はスタックでも再帰でも書けるが、この 2 つで点を訪れる順番が変わることがある。この授業で「DFS の訪問順を書け」と言うときは、スタック実装(隣は名前順に積む)を基準にする(可視化ツール⑫ グラフ探索 もこの実装)。なお、到達できるか・連結成分・閉路といった「分かる性質」は実装によらず同じ。変わるのは順番だけ。

今日の問い

前回の最後に見た路線(中央-緑 だけ 30 分、ほかは各 5 分)で、中央 → 終点 を考える。

  • 区間数最小中央-緑-終点(2 区間)= 35 分
  • 区間数中央-桜-港-浜-終点(4 区間)= 20 分

本数が少ない」と「時間が短い」は別物だ。重み(時間)が違うとき、本当の最短経路はどう求める?

演習12-1

下の重みつきグラフ(無向、数字はその区間の所要時間)で、S から T へ、合計がいちばん短い行き方を見つけ、その合計を書け。

graph LR
    S ---|1| A
    S ---|3| B
    A ---|1| C
    A ---|5| D
    B ---|2| C
    B ---|1| D
    C ---|8| T
    D ---|1| T

考えるヒント

  • 「いちばん安い区間から進む」作戦で、本当に最短になる? S-A(1)と S-B(3)、最初の 1 区間の安さは当てになるか試そう。
  • 各点に「いまわかっている最短」を書き込み、もっと短い道が見つかるたびに書き換えるとよい。

ダイクストラ法:確定して広げる

手で探すと、点が増えたとたんに破綻する。そこで機械的な手順にする。これがダイクストラ法

各点に 暫定距離(いまわかっている、始点からの最短)を持たせる。

  • 始点 = 0、ほかは (まだ不明)

そして次の 2 つを繰り返す。

  1. 未確定の中で、暫定距離が最小の点を 1 つ確定する
  2. 確定した点から隣を見て、暫定距離を更新(緩和)する

全部の点を確定したら終わり。

緩和(relaxation):もっと短い道で上書き

確定した点 u の隣 v を見て、

  • もし (u の距離 + 辺の重み) < v の暫定距離 なら、v をその値に上書き

「より短い行き方が見つかったら書き換える」だけ。そうでなければそのまま。暫定距離は、確定するまで何度でも短く更新されうる。

手を動かして追う

別の小さな例で 1 周してみる(S が始点、G がゴール)。

graph LR
    S ---|2| P
    S ---|5| Q
    P ---|1| Q
    P ---|7| G
    Q ---|2| G
  • 始点 S=0、ほかは
  • S を確定(0)→ 緩和:P=2Q=5
  • 未確定で最小は P → 確定(2)→ 緩和:Q=min(5, 2+1)=3G=2+7=9
  • 未確定で最小は Q → 確定(3)→ 緩和:G=min(9, 3+2)=5
  • 未確定で最小は G → 確定(5)。終了

S → G の最短は 5S-P-Q-G)。直通の S-Q(5)や P-G(7)に引っぱられず、遠回りでも合計が小さい道が残る。

やってみよう

同じ手順を、上の演習12-1 のグラフでも回してみよう。手で「安い区間から」選んだときと、答えが同じになるか確かめると、ダイクストラのありがたみが分かる。

なぜ「最小の未確定点」は確定してよいのか

確定するのは、いつも暫定距離が最小の未確定点。その点へ、いまより短い行き方はあるだろうか?

  • あるとすれば、まだ未確定の、もっと遠い点を経由するしかない
  • 重みは非負と仮定しているので、遠い点を通れば必ずそれ以上になる

だから「最小の未確定点」の距離はもう縮まない = 確定してよい。

大前提:辺の重みにマイナスがない

この理屈は重みが非負だから成り立つ。負の辺があると崩れる(後半で扱う)。

「次の 1 個」を最速で取り出す = ヒープ

毎回やるのは「未確定の中で暫定距離が最小の点を取り出す」こと。これは第10回のヒープ(優先度付きキュー)そのものの仕事。

  • 取り出し(pop)も、更新による入れ直し(push)も O(log n)

「次に処理すべき 1 個を最速で出す」という、ヒープの存在理由がここで効く。

アニメーションで見てみよう

可視化ツール⑬ 最短経路(重み付き) で「ダイクストラ法(貪欲)」を選び、確定が広がって暫定距離が更新される様子を追ってみよう。

演習12-2

下のグラフ(無向・重みつき)を、S からダイクストラで途中まで進めた。

いまの状態:確定済み S=0, A=2 / 暫定 B=3, C=9, T=∞

graph LR
    S ---|5| B
    S ---|2| A
    A ---|1| B
    A ---|7| C
    B ---|3| C
    B ---|8| T
    C ---|2| T
  • (a) 次に確定されるのはどの点か
  • (b) その点の距離がもう改善されないと言い切れる理由を一言
  • (c) その点を確定したとき、暫定距離が変わる隣と、その新しい値は?

考えるヒント

(a) は未確定(B, C, T)の中で暫定距離が最小の点。(c) は本文「緩和」のとおり、(確定した点の距離 + 辺の重み) と、いまの暫定距離を比べる。

計算量:O((n + e) log n)

  • 確定は各点 1 回 = ヒープから n 回取り出す
  • 緩和は各辺ごと = ヒープへ最大 e 回入れ直す
  • ヒープの取り出し・入れ直しは O(log n)

合わせて O((n + e) log n)(疎なグラフで実用的)。隣接リストで「隣だけ」を見るから、辺は e 回で済む。


ここで前提が崩れる:負の辺

ダイクストラの「最小の未確定点は確定してよい」は、重みが非負だから成り立っていた。もし負の辺があると?

  • 一度確定した点でも、あとから負の辺を通ればもっと短くできる場合がある
  • でもダイクストラは確定済みを直さない間違える

重みが「距離・時間」とは限らない。損得(コスト)なら、得する区間はマイナスになりうる。負の辺があるグラフでも正しく解きたい → 別の方法が要る。

ベルマンフォード法:確定せず、全部の辺を繰り返し緩和

貪欲に「確定」しない。代わりに、全部の辺を 1 回ずつ緩和するのをまとめて行い、これを n−1 回繰り返す。

確定という前提を使わないので、負の辺があっても正しい

なぜ n−1 回?

  • 1 巡(全部の辺を 1 回ずつ緩和)で確実に進むのは 1 辺ぶんだけ(緩和の順が悪いと、根本がまだ の辺は効かず、先頭から 1 本ずつしか伸びない)
  • 最短経路は高々 n−1 辺(負の閉路がなければ同じ点を二度通らない)
  • だからどんな緩和順でも n−1 巡あれば必ず出そろう(最悪を保証)

例:S→A→B→C→T、全部重み 1。運悪く「T に入る辺」から逆順に緩和すると、1 巡で 1 本ずつしか伸びない。

S A B C T
初期 0
1 巡目 0 1
2 巡目 0 1 2
3 巡目 0 1 2 3
4 巡目 0 1 2 3 4

点が 5 個なので n−1 = 4 巡で全部出そろう。

負の閉路の検出

n−1 回まわした後、まだ緩和できる辺が残っていたら? → 回るほど短くなる負の閉路がある証拠。

負の閉路があると、ぐるぐる回るほど合計が下がる = 最短が決まらない(下限なし)。ベルマンフォードは「最短が存在しない」ことも判定できる。これはダイクストラにはない強み。

アニメーションで見てみよう

可視化ツール⑬ 最短経路(重み付き) で「ベルマンフォード法(全辺くり返し)」を選び、「鎖:くり返しが効く」や「負の辺あり」「負閉路あり」のグラフで動かしてみよう。

演習12-3

下の有向グラフ(矢印の向きにしか進めない)で、S から各点への最短を考える。

graph LR
    S -->|1| A
    S -->|2| B
    B -->|-2| A
  • (a) ダイクストラだと A は距離 1 で確定される。でも本当の最短は?
  • (b) なぜダイクストラはそれを直せないのか、一言で
  • (c) なぜベルマンフォードなら直せるのか、一言で

考えるヒント

S→A(直行)と S→B→A(負の辺を通る)を、合計で比べてみる。(b)(c) は「確定したら直さない」のか「確定せず全辺を繰り返す」のか、という両者の仕組みの違いに注目。

計算量:O(n · e)

全部の辺(e 本)を緩和、を n−1 ≒ n 回。だから O(n · e)。ダイクストラの O((n+e) log n) より遅い。その遅さと引き換えに、負の辺 OK・負の閉路の検出が手に入る。

ダイクストラ vs ベルマンフォード

ダイクストラ ベルマンフォード
重みの条件 非負のみ 負の辺 OK
仕組み 最小の点を確定 → 隣を緩和 全辺を繰り返し緩和(n−1 回)
道具 優先度付きキュー(ヒープ) 特になし(全辺をなめる)
速さ O((n+e) log n):速い O(n · e):遅い
負の閉路 前提が崩れて不可 検出できる

共通点:どちらも「緩和(短い道で上書き)」が中心。違いは「貪欲に確定するか/全部を繰り返すか」。

演習12-4

次のそれぞれで、BFS/ダイクストラ/ベルマンフォードのどれを使うか。理由も一言。

  • (a) 全区間同じ所要時間の路線で、乗り換え回数を最小にしたい
  • (b) 区間ごとに所要時間が違う(すべて正)路線で、最短時間を知りたい
  • (c) 区間ごとに損得があり(得する区間はマイナス)、合計の損を最小にしたい

考えるヒント

道具は「重みの条件」で決まる。重みなし(1 辺=1)/すべて非負/負ありの 3 択で考える。下の「道具の選び方」が地図になる。

道具の選び方(今日の地図)

graph TD
    Q{辺に重みは?} -->|重みなし<br/>1辺=1| BFS[BFS<br/>最短の辺数]
    Q -->|重みあり| N{負の辺は?}
    N -->|全部 非負| DJK[ダイクストラ]
    N -->|負あり| BF[ベルマンフォード<br/>負の閉路も判定]

「どれが正解」ではなく、重みの条件がどれを使えるかを決める。

演習12-5

第11回と同じ路線図に、区間ごとの所要時間を入れた。ある人が BFS で乗り換え案内を作った:区間数が最小の経路を「最短」として表示する。

graph LR
    中央 ---|30| 緑
    中央 ---|5| 桜
    緑 ---|8| 港
    緑 ---|5| 終点
    桜 ---|5| 港
    桜 ---|11| 浜
    港 ---|5| 浜
    港 ---|6| 終点
    浜 ---|5| 終点
  • (a) このアプリは 中央 → 終点どの経路・何区間と表示するか。その経路の実際の所要時間は何分か
  • (b) 中央 → 終点本当の最短時間は何分か。経路も書け
  • (c) 中央 と直通(中央-緑=30 分)。 への最短はその直通か? 違うなら最短時間と経路を書け
  • (d) ダイクストラを 中央 から始めると、終点 はどちらが先に確定するか。理由を一言

考えるヒント

  • (a) BFS は区間数で選ぶ。その経路の時間の合計は別途足し算する。
  • (b)(c) 「直通だから速い」「区間数が少ないから速い」は重みつきでは成り立たない。遠回りでも合計が小さければそれが最短。いくつかの経路の合計を出して比べよう。
  • (d) ダイクストラは暫定距離が小さい順に確定する。終点 の最短時間を出して、小さいほうが先。

まとめ

  • 重みつきの最短は、緩和(短い道で上書き)が中心
  • ダイクストラ:最小の未確定点を確定 → 隣を緩和。非負前提O((n+e) log n)。「次の 1 個」を出すのがヒープ
  • ベルマンフォード:確定せず全辺を n−1 回緩和。負の辺 OKO(n·e)負の閉路を検出
  • 道具は重みの条件で選ぶ:重みなし → BFS / 非負 → ダイクストラ / 負あり → ベルマンフォード

次回予告

  • 期末テストの案内
  • 練習問題集
  • 問題解決における生成 AI との付き合い方

次回:問題解決における生成 AI との付き合い方・総まとめ