最短経路問題
ベルマンフォード法とワーシャルフロイド法について解説します。
ダイクストラ法と比べて構造が単純(プライオリティキューがいらない)なので、多少は理解しやすいと思います。
入力の形式について
このページで使用しているサンプルコードの標準入力は、全て下記の形式で与えられます。
n m
$$[f_1 \ t_1 \ c_1]$$
$$[f_2 \ t_2 \ c_2]$$
...
$$[f_m \ t_m \ c_m]$$
nは頂点数、mは辺の数を示し、続くm行の \([f\_i, t\_i, c\_i]\) はそれぞれi番目の辺の始点、終点、重みを示します。
ベルマンフォード法
ベルマンフォード法は、2点間の最短路を見つけるアルゴリズム。
ダイクストラ法はうまく頂点を選ぶことで効率を上げているが、ベルマンフォード法では全ての辺に対して距離が短くなる経路があるか判定する。
基本的には、ベルマンフォード法はダイクストラ法より遅い。計算量は、辺の数をE、頂点数をVとすると、O(EV)となる。
ただし、ベルマンフォード法は負の辺があっても正しく動作し、負の閉路を検出することができる。負の閉路があると、その閉路内をずっと回っていれば無限に距離を減らすことができてしまい、ダイクストラ法では無限ループor誤った答えを返す。
また、距離が確定した頂点を選ぶ操作が不要なので、コードも多少単純化される。
以下のコードはベルマンフォード法の実装です。グラフは隣接リスト形式で表現されています(グラフの表現方法については後述)。
以下は同アルゴリズムを隣接行列で実装したものです(講習会のときはこちらのコードのみ掲載していました)。計算量は \([O(V^3)]\) で、辺の数Eが小さなグラフに対しては隣接リストを使った場合よりも効率が悪いです。
入力例:
入力例に対応するグラフ:
出力例:
0から1へのコスト: 2
0から2へのコスト: 3
0から3へのコスト: 7
ワーシャルフロイド法
ワーシャルフロイド法は、グラフの全ての頂点の間の最短路を見つけるアルゴリズム。
「3つの頂点a, b, cを選んで、a→b→cという道がa→cという道より短ければa→cの距離を更新する」
という操作を全ての頂点の組み合わせで繰り返して最短距離を確定させていく。(a→b→cやa→cの道が存在しないときは、距離が無限大の道があると考え、a→aのような道は距離0としておく)
これは単純なforループで実現可能。計算量は、頂点数をVとすると\([O(V^3)]\)。
入力例:
入力例に対応するグラフ:
出力例:
0から1へのコスト: 10
0から3へのコスト: 100
1から0へのコスト: 1005
1から3へのコスト: 1000
2から0へのコスト: 1006
2から1へのコスト: 1
2から3へのコスト: 1001
3から0へのコスト: 5
3から1へのコスト: 15
コードが短いので計算時間に余裕があるなら2点間の最短経路に使ってもいいかも。
ちなみに、ワーシャルフロイド法でも負の辺に対応できる。負の閉路があると、d[i][i]
が負となる(d[i][i]
は負閉路がなければ0になっているはず)。
ワーシャルフロイド法はプログラムは単純だが、なぜこれでうまく最短経路が見つかるのかは理解しづらい。
プログラミングコンテストチャレンジブック(通称蟻本)の98ページに動的計画法を用いた解説があるので、気になる人はそれを参照してください。
演習
補足:グラフをプログラム上で管理する方法
上記にある2つのサンプルコードは、グラフが重み付きの(辺に距離などのデータが付いている)有向グラフで、隣接行列を使ってグラフを表現したときの処理になっている。
グラフを管理する方法は、主に隣接行列と隣接リストの2つの方法がある。
隣接行列は行列を使って辺を管理し、隣接リストは頂点毎に辺を格納して管理する方法のこと。
隣接行列と隣接リストは一長一短で、一般にダイクストラ法とベルマンフォード法は隣接リストを使ったほうが効率がよく、ワーシャルフロイド法は隣接行列を使ったほうが効率がよかったりする。
なので、状況によって使い分ける必要がある(とはいえ多くの場合どちらを使っても大差はなかったり…そんなことありませんでした両者は全然違うので使い分けましょう。巨大なグラフや多重辺を扱う場合は隣接リストでなければならない場合が多いです)。
以下は隣接行列と隣接リストの両方を使ったサンプルコード。入力の形式は上述の通り。
参考文献
プログラミングコンテストチャレンジブック: 秋葉 拓哉, 岩田 陽一, 北川 宜稔