アルゴリズム
ワーシャルフロイド法

ワーシャルフロイド法

キーワード

概要

重み付きグラフの全ての頂点対間の最短経路を求めるアルゴリズム。 ダイクストラ法と異なり、負の辺があっても動作する。ただし、負の閉路がある場合は正しく動作しない。

最終的に AijA_{ij} が頂点 ii から頂点 jj への最短経路の重みを表すような n×nn \times n 行列を得る。

まず次のように AA を初期化する。

aij={0(i=j)cij(ij(i,j)E)(ij(i,j)E)a_{ij} = \begin{cases} 0 & (i = j) \\ c_{ij} & (i \neq j \land (i, j) \in E) \\ \infty & (i \neq j \land (i, j) \notin E) \end{cases}

次に、以下のように AA を更新する。

for k=1 to nfor i=1 to nfor j=1 to naij=min(aij,aik+akj)\text{for } k = 1 \text{ to } n \\ \quad \text{for } i = 1 \text{ to } n \\ \quad \quad \text{for } j = 1 \text{ to } n \\ \quad \quad \quad \quad \quad \quad \quad \quad a_{ij} = \min(a_{ij}, a_{ik} + a_{kj})

正当性

kk 回目の反復後の AijA_{ij}dij(k)d_{ij}^{(k)} とする。頂点 1,2,,k1,2,\dots,k を中間点として使って良いとした時の ii から jj への最短経路の重みをfij(k)f_{ij}^{(k)} とする。 このとき、fij(k)=dij(k)f_{ij}^{(k)} = d_{ij}^{(k)}kk について帰納的に示す。

k=0k=0 のときは初期値なので明らかに成り立つ。

k1k \geq 1 に対して、k1k-1 以下では成り立つと仮定する。

アルゴリズムの構成法から、fij(k)dij(k)f_{ij}^{(k)} \leq d_{ij}^{(k)} が成立。(dij(k)d_{ij}^{(k)} は頂点 1,2,,k1,2,\dots,k を中間点として使って良いとした時の ii から jj へのある路の長さ)

従って、 fij(k)dij(k)f_{ij}^{(k)} \geq d_{ij}^{(k)} が成り立つことを示せば良い。

頂点 1,2,,k1,2,\dots,k を中間点として使って良いとした時の ii から jj への最短路を PP と置く。( d(P)=fij(k)d(P) = f_{ij}^{(k)} )

PPkk を通らないとすると、 fij(k)=fij(k1)f_{ij}^{(k)} = f_{ij}^{(k-1)} である。帰納法の仮定より、 fij(k1)=dij(k1)f_{ij}^{(k-1)} = d_{ij}^{(k-1)} であり、アルゴリズムの構成法より、 dij(k1)dij(k)d_{ij}^{(k-1)} \geq d_{ij}^{(k)} である。従って、 fij(k)dij(k)f_{ij}^{(k)} \geq d_{ij}^{(k)} である。

次に、 PPkk を通る場合を考える。

このとき、 P=(i,,k,,j)P = (i, \dots, k, \dots, j) と書かれる。 i,k,ji,k,j 以外の点は 1,2,,k11,2,\dots,k-1 のいずれかである。(負閉路を含まないため、同じ点が複数現れることはない)

よって、 fik(k1)=dik(k1)=d(Pik),fkj(k1)=dkj(k1)=d(Pjk)f_{ik}^{(k-1)} = d_{ik}^{(k-1)} = d(P_{i \to k}), \quad f_{kj}^{(k-1)} = d_{kj}^{(k-1)} = d(P_{j \to k}) が成り立つ。ここで、 PikP_{i \to k}PPii から kk の部分路、 PkjP_{k \to j}PPkk から jj の部分路である。

従って、 dij(k)dik(k1)+dkj(k1)=d(P)=fij(k)d_{ij}^{(k)} \leq d_{ik}^{(k-1)} + d_{kj}^{(k-1)} = d(P) = f_{ij}^{(k)} が言える。

以上より、 fij(k)=dij(k)f_{ij}^{(k)} = d_{ij}^{(k)} が成り立つ。

計算量

グラフの頂点数を NN とすると、 O(N3)\mathrm{O}(N^3)

応用例

実装

func main() {
	var n, m int
	scanIntVariables(&n, &m)
	A := make([][]int, n+1)
	for i := 1; i <= n; i++ {
		A[i] = make([]int, n+1)
	}
	for i := 1; i <= n; i++ {
		for j := 1; j <= n; j++ {
			if i == j {
				continue
			}
			A[i][j] = 1 << 60
		}
	}
	for i := 0; i < m; i++ {
		var a, b, c int
		scanIntVariables(&a, &b, &c)
		A[a][b] = c
	}
	for k := 1; k <= n; k++ {
		for i := 1; i <= n; i++ {
			for j := 1; j <= n; j++ {
				A[i][j] = min(A[i][j], A[i][k]+A[k][j])
			}
		}
	}
}