第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 つ確定する
- 確定した点から隣を見て、暫定距離を更新(緩和)する
全部の点を確定したら終わり。
緩和(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=2、Q=5- 未確定で最小は
P→ 確定(2)→ 緩和:Q=min(5, 2+1)=3、G=2+7=9 - 未確定で最小は
Q→ 確定(3)→ 緩和:G=min(9, 3+2)=5 - 未確定で最小は
G→ 確定(5)。終了
S → G の最短は 5(S-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 回緩和。負の辺 OK・O(n·e)・負の閉路を検出
- 道具は重みの条件で選ぶ:重みなし → BFS / 非負 → ダイクストラ / 負あり → ベルマンフォード
次回予告¶
- 期末テストの案内
- 練習問題集
- 問題解決における生成 AI との付き合い方
次回:問題解決における生成 AI との付き合い方・総まとめ。