본문으로 건너뛰기
김신건의 로그

플로이드-워셜 알고리즘 (Floyd-Warshall Algorithm)

· 수정 · 📖 약 3분 · 931자/단어 #algorithm #graph #shortest-path #floyd-warshall #dp
floyd-warshall, 플로이드워셜, 플로이드-워셜, 모든 쌍 최단 경로

정의

플로이드-워셜 알고리즘 (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";
      }
  }
}
stdin
4 7
1 2 3
1 4 5
2 3 2
3 4 1
4 1 2
2 4 4
3 1 6
결과
0 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 closureWarshall’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 가 -∞. 추가 전파 필요.

최적화

방법설명
Bitsetdist 대신 bitset<V> 로 reachable 표현. O(V^3 / 64)
Block Floyd-Warshallk 루프를 B 크기 블록으로 나눠 캐시 효율. 상수 개선
Parallelk 고정 시 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³).…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기