플로이드-워셜 알고리즘 (Floyd-Warshall Algorithm)
정의
플로이드-워셜 알고리즘 (Floyd-Warshall Algorithm) 은 모든 정점 쌍 (i, j) 사이의 최단 거리를 구하는 DP 알고리즘. Robert W. Floyd (1962) 와 Stephen Warshall (1962) 이 독립적으로 발표. 음수 가중치 허용, 음수 사이클 검출 가능. 시간 복잡도 O(V^3).
문제 상황과 동기
그래프에서 모든 쌍 (i, j) 최단 거리를 구하고 싶다.
- Dijkstra V번: V개 시작점에 대해 각각 O((V + E) log V). 총 O(V(V + E) log V) ≈ O(V^2 log V + VE log V). 간선 많으면 O(V^3 log V).
- Bellman-Ford V번: V개 시작점 × O(VE) = O(V^2 E). 밀집 그래프 O(V^4).
- Floyd-Warshall: 항상 O(V^3). 코드 10줄. 음수 간선 OK, 구현 간단.
핵심 통찰: 중간 정점 k 를 거쳐 가는 경로 vs 안 거치는 경로 중 짧은 것 선택. k = 1..V 순회하면 모든 경로 고려.
시각화
핵심 아이디어
DP 상태: dist[i][j][k] = i 에서 j 로 가는데 중간 정점이 {1, 2, …, k} 부분집합인 최단 거리.
점화식:
dist[i][j][k] = min(
dist[i][j][k-1], // k 안 거침
dist[i][k][k-1] + dist[k][j][k-1] // k 거침
)
공간 최적화: k 차원 생략, 2D 배열 in-place 갱신 가능. dist[i][j] 를 직접 갱신.
for k = 1 to V:
for i = 1 to V:
for j = 1 to V:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
알고리즘
FloydWarshall(G):
// 초기화
for i = 1 to V:
for j = 1 to V:
if i == j: dist[i][j] = 0
else if edge(i, j) exists: dist[i][j] = weight(i, j)
else: dist[i][j] = ∞
// DP
for k = 1 to V: // 중간 정점 k
for i = 1 to V: // 시작점 i
for j = 1 to V: // 도착점 j
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
// 음수 사이클 검출
for i = 1 to V:
if dist[i][i] < 0:
return "negative cycle"
return dist
구현
// O(V^3) Floyd-Warshall
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main() {
int n, m;
cin >> n >> m;
vector<vector<long long>> dist(n + 1, vector<long long>(n + 1, INF));
for (int i = 1; i <= n; i++) dist[i][i] = 0;
for (int i = 0; i < m; i++) {
int u, v; long long w;
cin >> u >> v >> w;
dist[u][v] = min(dist[u][v], w); // 중복 간선 처리
}
// Floyd-Warshall
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (dist[i][k] != INF && dist[k][j] != INF)
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
// 음수 사이클 검출
bool negCycle = false;
for (int i = 1; i <= n; i++) {
if (dist[i][i] < 0) {
negCycle = true;
break;
}
}
if (negCycle) {
cout << "NEGATIVE CYCLE\n";
} else {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
cout << (dist[i][j] == INF ? -1 : dist[i][j]) << " ";
cout << "\n";
}
}
}4 7
1 2 3
1 4 5
2 3 2
3 4 1
4 1 2
2 4 4
3 1 60 3 5 5
6 0 2 3
4 7 0 1
2 5 7 0복잡도
| 항목 | 값 |
|---|---|
| 시간 | O(V^3) |
| 공간 | O(V^2) |
| 음수 간선 | ✓ |
| 음수 사이클 검출 | ✓ (dist[i][i] < 0) |
| 경로 복원 | next[i][j] 배열 추가 |
V ≤ 300~500 정도면 실용. V > 1000 이면 Dijkstra V번이 더 빠를 수 있음 (희소 그래프).
경로 복원
next[i][j] = i 에서 j 로 가는 최단 경로의 다음 정점.
// 초기화
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i != j && dist[i][j] != INF) next[i][j] = j;
// Floyd-Warshall 중 갱신
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
next[i][j] = next[i][k]; // i -> k 의 다음 정점
}
// 경로 출력
void printPath(int u, int v) {
if (next[u][v] == -1) { cout << "No path\n"; return; }
vector<int> path = {u};
while (u != v) {
u = next[u][v];
path.push_back(u);
}
for (int p : path) cout << p << " ";
}
변형 / 활용
| 응용 | 설명 |
|---|---|
| 경로 존재성 | dist 대신 bool reachable[i][j], OR 연산 |
| 최대 용량 경로 | min 대신 max, dist 초기값 0 |
| Transitive closure | Warshall’s algorithm. reachable[i][j] |= reachable[i][k] && reachable[k][j] |
| 중간 정점 개수 최소 | DP[i][j][k] 에 경로 길이도 저장 |
| minimax, maximin | 경로 상 최대 간선 최소화 / 최소 간선 최대화 |
함정
1. 중복 간선
u -> v 간선 여러 개 있으면 가장 작은 가중치만 사용. dist[u][v] = min(dist[u][v], w) 초기화.
2. INF + INF overflow
dist[i][k] + dist[k][j] 가 overflow. if (dist[i][k] != INF && dist[k][j] != INF) 체크.
3. self-loop 음수
초기화 시 dist[i][i] = 0. 음수 self-loop 있으면 음수 사이클.
4. k-i-j 순서
k 루프가 가장 바깥쪽. i, j 순서는 무관. k 순서 틀리면 DP 점화식 깨짐.
5. 무방향 그래프
양방향 간선 dist[u][v] = dist[v][u] = w 둘 다 초기화.
6. 음수 사이클 영향 범위
dist[i][i] < 0 인 i 를 거치는 모든 (u, v) 는 dist 가 -∞. 추가 전파 필요.
최적화
| 방법 | 설명 |
|---|---|
| Bitset | dist 대신 bitset<V> 로 reachable 표현. O(V^3 / 64) |
| Block Floyd-Warshall | k 루프를 B 크기 블록으로 나눠 캐시 효율. 상수 개선 |
| Parallel | k 고정 시 i, j 독립. GPU / OpenMP 병렬화 가능 |
| Johnson’s algorithm | 음수 간선 있으면 가중치 변환 + Dijkstra V번. O(V^2 log V + VE) |
BOJ 연습 문제
| 번호 | 제목 | 정답률 | 링크 |
|---|---|---|---|
| BOJ 11404 | 플로이드 | 43.5% | kokoa-lab |
| BOJ 2660 | 회장뽑기 | 57.8% | kokoa-lab |
| BOJ 1389 | 케빈 베이컨의 6단계 법칙 | 49.1% | kokoa-lab |
| BOJ 1956 | 운동 | 37.5% | kokoa-lab |
| BOJ 2458 | 키 순서 | 45.9% | kokoa-lab |
참고
이 글의 용어 (4개)
- 다익스트라 알고리즘 (Dijkstra's Algorithm)algorithm
- 정의 다익스트라 알고리즘 (Dijkstra's Algorithm) 은 음이 아닌 가중치 그래프에서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 그리디 알고리즘. Ed…
- 벨만-포드 알고리즘 (Bellman-Ford Algorithm)algorithm
- 정의 벨만-포드 알고리즘 (Bellman-Ford Algorithm) 은 음수 가중치 간선을 허용하면서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 DP 기반 알고리…
- Johnson's Algorithm: sparse APSPalgorithm
- 정의 Johnson's algorithm 은 sparse graph 의 all-pairs shortest paths 를 O(V·E log V + V²) 에 계산. Bellman-…
- Transitive Closure: 도달 가능성 폐포algorithm
- 정의 Transitive Closure 는 그래프의 모든 (u, v) 쌍에 대해 u 에서 v 로 도달 가능한지 담은 부울 행렬입니다. Floyd-Warshall 변형 O(V³).…
💬 댓글