← 알고리즘 강의 목록으로
🛣️
고급 그래프
DP 관점·모든 쌍 최단 경로

단원 25 — 플로이드-워셜 (Floyd-Warshall)

정점 쌍 모두에 대한 최단 경로를 O(V³)으로 구하는 알고리즘. DP 점화식 관점이 핵심입니다.

플로이드-워셜모든 쌍 최단
소요 시간
약 45분
난이도
📊 고급
선수 조건
🎯 DP·그래프
결과물
정점 쌍 모두에 대한 최단 경로를 O(V³)으로 구하는 알고리즘. DP 점화식 관점이 핵심입니다.

이 강의에서 배우는 것

  • 1플로이드-워셜의 DP 점화식 `dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])` 를 이해하고 구현한다.
  • 2경로 복원 행렬(`next[][]`)을 사용하여 실제 경로를 추출할 수 있다.
  • 3음수 사이클 탐지, 도달 가능성(전이 폐쇄) 계산에 플로이드-워셜을 응용한다.

개요

💡

**Part 07 고급 그래프** | 이전: [24_다익스트라](../24_다익스트라/) | 다음: [26_최소_신장_트리](../26_최소_신장_트리/)

소개

플로이드-워셜 알고리즘은 **모든 쌍 최단 경로(All-Pairs Shortest Path)**를 구하는 동적 계획법(DP) 기반 알고리즘입니다. 음수 가중치 간선도 처리 가능하며, 음수 사이클 탐지에도 활용됩니다.

  • **적용 그래프**: 방향/무방향, 음수 간선 허용 (음수 사이클 없을 때)
  • **시간 복잡도**: O(V³)
  • **공간 복잡도**: O(V²)

정점 수 V가 수백~수천 이하일 때 사용합니다. V > 500 이면 TLE 주의.

핵심 개념

1. DP 점화식

text
dist[i][j][k] = 노드 1~k 만 경유 가능할 때, i→j 최단 거리

점화식:
dist[i][j][k] = min(
    dist[i][j][k-1],           ← k 를 경유하지 않음
    dist[i][k][k-1] + dist[k][j][k-1]  ← k 를 경유함
)

k 차원을 제거(공간 최적화):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

2. 알고리즘 구조

python
# 초기화
for i in range(V):
    for j in range(V):
        dist[i][j] = INF
    dist[i][i] = 0          # 자기 자신 거리 = 0
for u, v, w in edges:
    dist[u][v] = min(dist[u][v], w)

# 메인 루프 (k = 경유 노드)
for k in range(V):          # 순서 중요!
    for i in range(V):
        for j in range(V):
            dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

**주의**: 세 중첩 루프에서 **k(경유 노드)가 가장 바깥** 루프여야 합니다.

3. 텍스트 아트: 예제 그래프와 단계별 실행

text
그래프 (방향, 4개 노드 0~3):

  0 ──3── 1
  │ \     │
  7   2   1
  │     \ │
  3 ──1── 2

간선: 0→1:3, 0→2:2, 0→3:7, 1→2:1, 2→3:1

초기 dist 행렬 (INF 생략):
     0    1    2    3
0  [ 0    3    2    7  ]
1  [INF   0    1   INF ]
2  [INF  INF   0    1  ]
3  [INF  INF  INF   0  ]

k=0 경유 후 (0을 거치는 경로 추가):
     0    1    2    3
0  [ 0    3    2    7  ]
1  [INF   0    1   INF ]
2  [INF  INF   0    1  ]
3  [INF  INF  INF   0  ]
(변화 없음 — 0→?→1 경로가 더 길기 때문)

k=1 경유 후:
     0    1    2    3
0  [ 0    3    2    7  ]  (0→1→2 = 4 > 기존 2, 변화 없음)
1  [INF   0    1   INF ]
2  [INF  INF   0    1  ]
3  [INF  INF  INF   0  ]

k=2 경유 후:
     0    1    2    3
0  [ 0    3    2    3  ]  (0→2→3 = 3 < 7 ← 갱신!)
1  [INF   0    1    2  ]  (1→2→3 = 2 ← 갱신!)
2  [INF  INF   0    1  ]
3  [INF  INF  INF   0  ]

k=3 경유 후:
(3을 경유해도 단축되는 경로 없음)
최종 dist:
0→0=0, 0→1=3, 0→2=2, 0→3=3
1→2=1, 1→3=2
2→3=1

4. 다익스트라 vs 플로이드-워셜 비교

항목다익스트라 (heapq)플로이드-워셜
출발점단일모든 쌍
시간 복잡도O((V+E) log V)O(V³)
공간 복잡도O(V+E)O(V²)
음수 간선불가가능
음수 사이클탐지 불가탐지 가능
적합 상황V 크고 특정 출발점V 작고 모든 쌍 필요
V=500 기준500회 다익스트라O(1.25억) 연산

예제로 보기

파일내용핵심 개념
`src/01_floyd_warshall.py`기본 플로이드-워셜 구현INF 초기화, 3중 루프
`src/02_floyd_path.py`경로 복원 (next 행렬)역추적
`src/03_negative_cycle.py`음수 사이클 탐지`dist[i][i] < 0`
`src/04_transitive_closure.py`전이 폐쇄 (도달 가능성)bool 행렬
bash
python src/01_floyd_warshall.py
python src/02_floyd_path.py
python src/03_negative_cycle.py
python src/04_transitive_closure.py

자주 하는 실수

  1. **k 루프가 가장 안쪽**: k(경유 노드)를 가장 바깥 루프로 두지 않으면 오답. `for k → for i → for j` 순서를 지켜야 합니다.
  2. **INF + INF 오버플로**: `dist[i][k] + dist[k][j]`에서 두 값이 모두 INF이면 합산 오류. `if dist[i][k] == INF or dist[k][j] == INF: continue` 가드 추가 또는 `float('inf')` 사용.
  3. **자기 자신 거리 초기화 누락**: `dist[i][i] = 0` 을 잊으면 오답.
  4. **경로 복원 행렬 초기화 오류**: `next[i][j] = j` (직접 연결) 또는 `next[i][j] = -1` (미연결) 로 초기화한 뒤, 간선 추가 시 `next[i][j] = j` 로 설정해야 합니다.
  5. **V 크기 제한 무시**: V = 10,000 이면 O(10¹²) 연산 → 절대 사용 불가. V ≤ 500 ~ 1,000 이하에서만 사용하세요.

정리

text
플로이드-워셜 요약
━━━━━━━━━━━━━━━━━━━━━
문제 유형   : 모든 쌍 최단 경로
시간 복잡도 : O(V³)
공간 복잡도 : O(V²)
음수 간선   : 허용 (사이클 없을 때)
음수 사이클 : dist[i][i] < 0 으로 탐지
경로 복원   : next[V][V] 행렬
도달 가능성 : bool 행렬로 응용

직접 해 보기

  1. 위 텍스트 아트 예제를 손으로 k=0~3 단계 추적하여 최종 dist를 구해 보세요.
  2. `src/02_floyd_path.py`를 수정해 경유 노드 수가 가장 적은 경로를 구해 보세요.
  3. 백준 11404 (플로이드) 를 직접 제출하여 AC를 받아 보세요.

과제

→ [homework/README.md](homework/README.md)

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗