알고리즘 입문
개념 배우기직접 풀어보기
놀면 뭐 하니,
그냥 하는거야.

가장 가까운 곳부터 확정하자냥! 다익스트라

코딩냥

안녕, 나 코딩냥이다냥! 10장에서 BFS 로 최단 거리를 구했었지냥? 그런데 그건 모든 길의 비용이 같을 때만 통했다냥. 도로마다 거리가 다르면 어떡하냥?

그때 쓰는 게 다익스트라라냥. 가중치가 있는 그래프의 최단 거리라냥.

오늘의 학습 목표

  • 가중치가 있으면 BFS 가 안 되는 이유를 안다.
  • 다익스트라를 우선순위 큐로 짠다.
  • 음수 간선에는 못 쓴다는 걸 안다.
  • 경유지와 다중 출발로 응용한다.
  • 역방향 그래프로 돌아오는 길을 구한다.

1단계 · BFS 로는 안 된다냥 (10분)

코딩냥

10장의 BFS 는 한 칸씩 퍼졌다냥. 모든 간선이 "1칸"이라 가까운 순서대로 나왔다냥. 그런데 간선마다 비용이 다르면 그 순서가 깨진다냥.

비유로 이해하기 — 지하철 vs 걷기

A 에서 B 로 한 정거장 바로 가는 길이 있고, C 를 거쳐 두 정거장 돌아가는 길도 있다냥. 정거장 수만 세는 BFS 는 "한 정거장" 이 짧다고 한다냥.

그런데 그 한 정거장이 아주 먼 거리고, 돌아가는 두 정거장이 가까우면? BFS 는 틀린다냥. 거리(가중치) 를 봐야 하는데 정거장 수 만 봤기 때문이라냥.

BFS 는 간선을 전부 1로 본다냥.

그래서 가중치가 다른 그래프에서 BFS 로 거리를 세면 틀린다냥. 오늘 1번 문제를 BFS 로 풀면 바로 걸린다냥. 가중치가 있으면 다익스트라라냥.

2단계 · 다익스트라의 아이디어 (13분)

코딩냥

핵심은 하나라냥. 아직 확정 안 된 것 중 가장 가까운 것부터 확정한다냥. 한 번 확정하면 다시는 안 바뀐다냥.

뼈대가장 가까운 것부터 꺼내 이웃을 갱신한다냥
  1. 시작점의 거리를 0, 나머지는 무한대로 둔다
  2. 가장 가까운 미확정 노드를 꺼낸다
  3. 그 노드를 거쳐 이웃에 가는 게 더 짧으면 거리를 줄인다(완화)
  4. 큐가 빌 때까지 반복한다

비유로 이해하기 — 물결이 번지는 속도가 다르다냥

BFS 는 물결이 일정한 속도로 동그랗게 퍼졌다냥. 다익스트라는 길마다 속도가 다른 물결이라냥. 그래서 "지금 가장 먼저 도착할 곳"을 매번 골라야 한다냥. 그걸 우선순위 큐가 해준다냥.

3단계 · 우선순위 큐로 짠다냥 (13분)

코딩냥

"가장 가까운 것"을 매번 빨리 찾으려면 우선순위 큐(최소 힙)를 쓴다냥. 파이썬은 heapq 가 있다냥.

import heapq

INF = float("inf")
dist = [INF] * (n + 1)
dist[1] = 0
pq = [(0, 1)]                 # (거리, 노드) 를 넣는다냥
while pq:
    d, u = heapq.heappop(pq)  # 가장 가까운 것부터 꺼낸다냥
    if d > dist[u]:
        continue              # 이미 확정됐으면 건너뛴다냥
    for v, w in adj[u]:
        if d + w < dist[v]:   # 완화: 더 짧으면 줄인다냥
            dist[v] = d + w
            heapq.heappush(pq, (dist[v], v))

if d > dist[u]: continue 를 꼭 넣자냥.

큐에는 같은 노드가 여러 번 들어갈 수 있다냥. 예전의 더 긴 거리가 남아 있을 수 있다냥. 꺼냈을 때 이미 더 짧은 거리로 확정됐으면 버려야 한다냥.

이걸 빼도 답은 맞지만, 쓸데없는 일을 반복해 느려진다냥. 습관으로 넣자냥.

거리는 아주 커질 수 있다냥.

간선이 4000개, 각 비용이 100만이면 거리가 40억까지 간다냥. 파이썬은 괜찮지만 다른 언어면 long/int64 를 쓰자냥. int 로 두면 넘친다냥.

4단계 · 음수 간선은 안 된다냥 (10분)

코딩냥

다익스트라에는 중요한 전제가 있다냥. 모든 간선의 비용이 0 이상이어야 한다냥. 음수가 있으면 틀린다냥.

왜 음수면 안 되냥?

다익스트라는 "가장 가까운 걸 확정하면 다시는 안 바뀐다" 를 믿는다냥. 이건 12장 그리디의 정신이라냥 — 눈앞에서 제일 가까운 걸 덥석 확정하는 거라냥.

그런데 음수 간선이 있으면, 나중에 음수 길을 타고 와서 이미 확정한 거리가 더 짧아질 수 있다냥. 확정을 못 믿게 되는 거라냥. 그러면 다익스트라가 틀린다냥.

음수 간선이 있는 최단 경로는 벨만-포드라는 다른 방법을 써야 한다냥. 이 수업 범위를 넘으니, 오늘은 "음수가 있으면 다익스트라는 안 된다" 만 기억하자냥. 오늘 문제는 전부 양수라 걱정 없다냥.

5단계 · 응용: 경유·다중 출발·역방향 (12분)

코딩냥

다익스트라 하나만 잘 짜두면, 조금씩 바꿔서 여러 문제를 푼다냥.

경유꼭 들러야 하는 곳이 있으면 나눈다냥

1 → v → n 처럼 v 를 꼭 거쳐야 하면, 두 조각으로 나눈다냥.

1 에서 다익스트라 → dist(1, v), v 에서 다익스트라 → dist(v, n). 둘을 더하면 답이라냥. 오늘 3번 문제라냥.

다중 출발시작점이 여러 개면 전부 넣는다냥

대피소가 여러 곳이고 "가장 가까운 대피소까지 거리" 를 구한다면, 모든 대피소를 거리 0 으로 큐에 넣고 시작한다냥. 10장의 다중 시작 BFS 와 똑같은 요령이라냥. 오늘 4번 문제라냥.

for src in sources:
    dist[src] = 0
    heapq.heappush(pq, (0, src))
역방향돌아오는 길은 화살표를 뒤집는다냥

방향 그래프에서 n 에서 1 로 돌아오는 최단 거리는, 모든 화살표를 뒤집은 역방향 그래프에서 1 에서 n 으로 가는 거리와 같다냥.

그래서 간선을 넣을 때 정방향과 역방향을 둘 다 만들어두면, 왕복을 다익스트라 두 번으로 푼다냥. 오늘 5번 문제라냥.

오늘 배운 내용 정리

코딩냥

먼저 스스로 답해보고, "정답 보기"를 눌러서 맞는지 확인해보자냥!

Q1.가중치가 있는 그래프에서 왜 BFS 가 안 되냥?
Q2.다익스트라의 핵심 아이디어가 뭐냥?
Q3.pq 에서 꺼낼 때 if d > dist[u]: continue 는 왜 넣냥?
Q4.다익스트라를 음수 간선에 쓰면 왜 안 되냥?
Q5.방향 그래프에서 돌아오는 최단 거리는 어떻게 구하냥?

이제 직접 풀어볼 차례다냥

코딩냥

오늘 문제는 다섯 개라냥. 1번 모든 곳까지, 2번 출발지→목적지, 3번 경유지, 4번 다중 출발(대피소), 5번 왕복(역방향)이라냥.

다익스트라 뼈대를 한 번 제대로 짜두면 나머지는 어디서 시작하고 무엇을 더하느냐만 바뀐다냥. 가보자냥~