다익스트라 알고리즘은 하나의 정점에서 출발했을 때 다른 모든 정점으로의 최단 경로 구하는 알고리즘
플로이드 와샬 알고리즘은 모든 정점에서 다른 모든 정점으로의 최단 경로를 구하는 알고리즘
둘 다 최단 경로 비교라는 공통점이 존재하지마니
용도, 구현 방식, 시간 복잡도 등에 차이가 난다
하나의 시작 정점에서 다른 모든 정점까지의 최단 경로…
생각해보면 알겠지만 GPS다
양의 가중치만을 허용하며, 가장 가까운 노드부터 순서대로 거리 정보를 갱신한다
시간 복잡도 : $O((V + E) log V)$ (우선 순위 큐 사용시)
그리디 방식이라 음수 넣으면 최단 경로 보장 못함;;
# 간선 정보 (A~D)
[A]
1 / | \ # A-B: 1
/ | 2\ # A-C: 2
/ 4| \ # A-D: 4
[B]--3--[C] # B-C: 3
1\ | / # B-D: 1
\ | /5 # C-D: 5
[D]
distance = {
A: 0, # 시작점은 0
B: ∞,
C: ∞,
D: ∞
}
visited = {
A: False,
B: False,
C: False,
D: False
}
B: min(∞, 0+1) = 1
C: min(∞, 0+2) = 2
D: min(∞, 0+4) = 4
# 현상태
distance = { A: 0, B: 1, C: 2, D: 4 }
visited = { A: ✅, B: ❌, C: ❌, D: ❌ }
C: min(2, 1+3) = 2 (변화 없음)
D: min(4, 1+1) = 2 (더 짧아짐)
# 현상태
distance = { A: 0, B: 1, C: 2, D: 2 }
visited = { A: ✅, B: ✅, C: ❌, D: ❌ }
D: min(2, 2+5) = 2 (변화 없음)
# 현상태
distance = { A: 0, B: 1, C: 2, D: 2 }
visited = { A: ✅, B: ✅, C: ❌, D: ❌ }
A → A = 0
A → B = 1
A → C = 2
A → D = 2
혹은 플로이드 워샬
모든 정점 쌍 사이의 최단 거리를 구한다
중간에 거쳐갈 수 있는 모든 정점을 하나씩 고려하며 갱신하는는 느낌’s~
(Dynamic Programming)
핵심 아이디어는
정접 i
에서 j
로가는 최단 경로 중
k
를 거쳐가는 것이 더 빠르면 i → k → j
로 갱신
예시)
distance[i][j] = min(distance[i][j], distance[i][k] + distance[k][j])
얘는 음수 가중치도 가능하다
단, 음수 사이클은 없어야 한다
음수 사이클 판별도 가능
시간 복잡도는 $O(V^3)$이다 (3중 for문)
[0]
↙ ↘
4 5
↓ ↓
[1] → 2 → [2]
↑ ↘ ↓
1 1 3
↖ ↓ ↙
[3]
path | cost |
---|---|
0 → 1 | 4 |
0 → 2 | 5 |
1 → 2 | 2 |
1 → 3 | 1 |
3 → 1 | 1 |
2 → 3 | 3 |
우선 거리행렬 (2차원 배열)로 초기화 한다
dist[i][j] = i에서 j로 가는 최소 거리
(처음엔 직통 거리 또는 INF)
초기상태
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 4 | 5 | INF |
1 | INF | 0 | 2 | 1 |
2 | INF | INF | 0 | 3 |
3 | INF | 1 | INF | 0 |
INF
로 표현
___모든 정점 k를 거쳐가며
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
수행
3중 for문 구조:
# py
for k in range(V):
for i in range(V):
for j in range(V):
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]
가능한 갱신 경로:
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 4 | 5 | INF |
1 | INF | 0 | 2 | 1 |
2 | INF | INF | 0 | 3 |
3 | INF | 1 | INF | 0 |
갱신된 경로:
0 → 3
: 0→1→3 = 4 + 1 = 5
(기존 INF
)
3 → 2
: 3→1→2 = 1 + 2 = 3
(기존 INF
)
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 4 | 5 | 5 |
1 | INF | 0 | 2 | 1 |
2 | INF | INF | 0 | 3 |
3 | INF | 1 | 3 | 0 |
갱신된 경로:
0 → 3
: 0→2→3 = 5 + 3 = 8
기존 5 가 더 짧음 → 변화 없음
1 → 3
: 1→2→3 = 2 + 3 = 5
기존 1 이 더 짧음 → 변화 없음
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 4 | 5 | 5 |
1 | INF | 0 | 2 | 1 |
2 | INF | INF | 0 | 3 |
3 | INF | 1 | 3 | 0 |
갱신된 경로:
2 → 1
: 2→3→1 = 3 + 1 = 4
(기존 INF
)
나머지 경로는 변화 없음
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 4 | 5 | 5 |
1 | INF | 0 | 2 | 1 |
2 | INF | 4 | 0 | 3 |
3 | INF | 1 | 3 | 0 |
위 표가 최종 행렬이다
최종 행렬은 “모든 정점 쌍 간 최단 거리” 를 나타내며,
특정 노드에서 다른 노드까지의 최소 이동 비용을 한눈에 파악할 수 있습니다.
사이클이 음수인 경우를 파악해야 한다
무시하고 냅둘 경우 무한히 순회하며 비용이 무한히 줄어들어 최단 거리의 의미가 없어진다
의외로 탐지 방법은 쉽다
알고리즘 종료 후 dist[i][i] < 0
인 정접이 존재시
=> 음수 사이클 존재
자기 자신으로 돌아오는 사이클 비용이 음수라는 뜻임
for i in range(V):
if dist[i][i] < 0:
print("음수 사이클 존재")
거리 말고도 어떤 경로로 이동했는지 알고 싶을때
방법 :
① path 배열 생성
next = [[None] * V for _ in range(V)]
② 초기화
for u, v, w in edges: dist[u][v] = w next[u][v] = v # 직접 갈 수 있는 경우는 다음 노드를 바로 기록
③ 갱신할 때 중간 경유지 저장
if dist[i][j] > dist[i][k] + dist[k][j]: dist[i][j] = dist[i][k] + dist[k][j] next[i][j] = next[i][k] # i→j로 가려면, 일단 i→k로 간 뒤 계속 따라감
def get_path(u, v):
if next[u][v] is None:
return [] # 경로가 없으면 빈 리스트 반환
path = [u] # 출발 정점을 먼저 경로에 넣고
while u != v: # 도착 정점에 도달할 때까지
u = next[u][v] # 다음 정점으로 이동
path.append(u) # 이동한 정점을 경로에 추가
return path
get_path(2,1)
(2 → 3 → 1 이라는 가정)
[2, 3, 1]