Floyd-Warshall 알고리즘 정리
AlgorithmGraph
Floyd-Warshall이란
모든 노드 쌍 (i, j) 사이의 최단 경로를 한 번에 구하는 알고리즘이다. 단일 출발점 최단 경로(Dijkstra, BFS)를 N번 반복하는 대신, 3중 루프 하나로 해결한다.
핵심 아이디어
“i에서 j로 갈 때, 중간에 k를 거치는 게 더 짧은가?”를 모든 k에 대해 확인한다.
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
k를 1부터 N까지 순서대로 확장하면, k번째 루프가 끝난 시점에서 dist[i][j]는 {1, 2, …, k}만을 중간 노드로 사용했을 때의 최단 거리다.
코드: 최단 거리
const int INF = 1e9;
vector<vector<int>> dist(n + 1, vector<int>(n + 1, INF));
// 자기 자신은 0
for (int i = 1; i <= n; i++) dist[i][i] = 0;
// 간선 입력
for (auto& e : edges) {
dist[e[0]][e[1]] = e[2]; // 방향 그래프
// dist[e[1]][e[0]] = e[2]; // 무방향이면 추가
}
// 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];
}
}
}
변형: 전이적 폐쇄 (Transitive Closure)
최단 거리 대신 “도달 가능한가?”만 구하는 변형이다. min(+) 대신 &&를 쓴다.
vector<vector<bool>> reach(n + 1, vector<bool>(n + 1, false));
for (auto& e : edges) {
reach[e[0]][e[1]] = true;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (reach[i][k] && reach[k][j])
reach[i][j] = true;
}
}
}
메모
- k가 가장 바깥 루프
- k를 안쪽에 놓으면 아직 확장되지 않은 중간 노드를 참조하게 되어 틀린 결과가 나온다
- k → i → j 순서는 바꿀 수 없다
- 음수 간선
- Floyd-Warshall은 음수 가중치 간선을 허용한다 (Dijkstra는 불가)
- 단, 음수 사이클이 있으면 최단 경로가 정의되지 않는다.
dist[i][i] < 0인 노드가 있으면 음수 사이클 존재
- N ≤ 500 정도까지 실용적
- O(N³)이므로 N = 500이면 1.25억, N = 1000이면 10억으로 시간 초과 위험
- N이 크면 각 노드에서 Dijkstra를 돌리는 게 더 빠를 수 있다
- 적용 사례
- 프로그래머스 49191 순위: 승패 전이 관계 → 전이적 폐쇄로 순위 확정 선수 수 계산
복잡도
| 시간 | 공간 | |
|---|---|---|
| Floyd-Warshall | O(N³) | O(N²) |