🛣️
고급 그래프
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=14. 다익스트라 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자주 하는 실수
- **k 루프가 가장 안쪽**: k(경유 노드)를 가장 바깥 루프로 두지 않으면 오답. `for k → for i → for j` 순서를 지켜야 합니다.
- **INF + INF 오버플로**: `dist[i][k] + dist[k][j]`에서 두 값이 모두 INF이면 합산 오류. `if dist[i][k] == INF or dist[k][j] == INF: continue` 가드 추가 또는 `float('inf')` 사용.
- **자기 자신 거리 초기화 누락**: `dist[i][i] = 0` 을 잊으면 오답.
- **경로 복원 행렬 초기화 오류**: `next[i][j] = j` (직접 연결) 또는 `next[i][j] = -1` (미연결) 로 초기화한 뒤, 간선 추가 시 `next[i][j] = j` 로 설정해야 합니다.
- **V 크기 제한 무시**: V = 10,000 이면 O(10¹²) 연산 → 절대 사용 불가. V ≤ 500 ~ 1,000 이하에서만 사용하세요.
정리
text
플로이드-워셜 요약
━━━━━━━━━━━━━━━━━━━━━
문제 유형 : 모든 쌍 최단 경로
시간 복잡도 : O(V³)
공간 복잡도 : O(V²)
음수 간선 : 허용 (사이클 없을 때)
음수 사이클 : dist[i][i] < 0 으로 탐지
경로 복원 : next[V][V] 행렬
도달 가능성 : bool 행렬로 응용직접 해 보기
- 위 텍스트 아트 예제를 손으로 k=0~3 단계 추적하여 최종 dist를 구해 보세요.
- `src/02_floyd_path.py`를 수정해 경유 노드 수가 가장 적은 경로를 구해 보세요.
- 백준 11404 (플로이드) 를 직접 제출하여 AC를 받아 보세요.
과제
→ [homework/README.md](homework/README.md)