어떤 문제를 푸는가
모든 정점 쌍 \((i, j)\) 사이의 최단 거리를 한꺼번에 구합니다. 정점이 적고
(\(V \le 400\) 정도) 쌍 전부의 거리가 필요할 때, 한 줄짜리 삼중 루프로 끝나는
이 방법이 압도적으로 편합니다. 음수 간선도 허용합니다(음수 사이클만 없으면).
핵심 아이디어 — 경유 정점을 하나씩 허용
dist[i][j]를 "정점 \(i\)에서 \(j\)로 가는 최단 거리"로 두고, 경유해도 되는
중간 정점의 집합을 \(\{1\}, \{1,2\}, \dots\) 처럼 점점 넓혀 갑니다.
\(k\)번째 정점까지 경유를 허용했을 때의 거리를 \(dist_k[i][j]\)라 하면,
$$ dist_k[i][j] = \min\bigl(dist_{k-1}[i][j],\ dist_{k-1}[i][k] + dist_{k-1}[k][j]\bigr) $$
즉 "\(k\)를 거치지 않는 기존 경로" vs "\(i \to k \to j\)로 \(k\)를 거치는 경로"
중 작은 쪽입니다.
왜 옳은가
\(i\)에서 \(j\)로 가는 임의의 최단 경로를 봅니다. 그 경로가 사용하는 중간 정점들
중 번호가 가장 큰 것 을 \(k\)라 하면, 경로는 "\(i \to k\) (중간 정점 번호 모두
\(
\(dist_{k-1}\)가 다루는 경우이므로, \(k\)를 마지막으로 허용하는 단계에서 이 경로가
포착됩니다. 모든 \(k\)를 차례로 허용하면 모든 최단 경로가 고려됩니다.
여기서 \(k\) 루프가 가장 바깥 이어야 한다는 점이 절대적으로 중요합니다.
1차원으로 in-place 갱신해도 되는 이유
\(k\) 단계에서 dist[i][k]와 dist[k][j]는 이번 단계에 갱신되더라도 값이
변하지 않습니다 (dist[k][k] = 0이므로). 그래서 배열 한 장으로 덮어써도
정확합니다.
복잡도
| 항목 | 값 |
|---|---|
| 시간 | \(O(V^3)\) |
| 공간 | \(O(V^2)\) |
\(V = 400\)이면 \(6.4 \times 10^7\)로 충분히 빠릅니다. \(V\)가 수천을 넘으면
못 씁니다 — 그때는 각 정점마다 다익스트라(\(O(V \cdot E \log V)\))를 고려합니다.
음수 사이클 탐지
플로이드-워셜 후 어떤 정점 \(i\)에 대해 dist[i][i] < 0이면, \(i\)가 음수
사이클 위에 있다는 뜻입니다. 자기 자신으로 돌아오는 비용이 음수일 수 없기
때문입니다. 다음 강의에서 구현과 응용을 봅니다.