다익스트라 알고리즘 1 — 각 정점까지의 최단 거리만 구하기
“지금까지 거리를 확정한 정점들 중에서, 가장 가깝게 닿을 수 있는 다음 정점을 하나씩 추가한다.” 다익스트라는 그리디 알고리즘이고, 매 단계 정점을 고르는 방식이 Prim과 많이 닮았다. 차이가 있다면 Prim은 “트리에 가장 가볍게 붙는 간선”를 보고, Dijkstra는 “시작점에서 거리가 가장 짧은 정점”을 본다는 점이다. Prim과의 본격적인 비교는 2편에서 다룬다.
- Dijkstra의 세 가지 변형 중 1편, 각 정점까지의 최단 거리(길이)만 구하기 (경로 복원과 시간복잡도는 2편에서)
- 알고리즘의 아이디어: 답을 확정한 정점 집합 을 키워가는 그리디 전략
- 단계별 진행을 작은 그래프에서 시각화
- 정확성 증명 2가지: “가장 작은 이 곧 최단거리”와 “새로 추가된 의 간선만 갱신해도 충분”
알고리즘의 아이디어
입력은 가중 그래프 와 시작점 이고, 정점(vertex) 수는 이다. 이 글은 그래프의 점을 정점, 이음선을 간선(edge) 으로 부른다. 여기서 도착점은 따로 정하지 않는다. 임의의 정점 까지의 최단 경로를 구하면 그 경로 위에 있는 모든 중간 정점까지의 최단 거리도 자동으로 함께 얻어지기 때문이다. 만약 중간의 어떤 부분 경로가 최단이 아니었다면, 그 부분을 더 짧은 경로로 바꿔서 전체 길이를 줄일 수 있을 텐데, 이는 모순이다. 그래서 처음부터 모든 정점까지의 거리를 한꺼번에 구한다.
각 정점 에 대해 현재까지 추정한 최단 거리 를 관리한다. 초깃값은 다음과 같다.
- 간선 가 존재하면:
- 간선이 없으면:
그리고 이미 답이 확정된 정점들의 집합 을 도입한다. 처음에는 이다. 시작점까지의 거리는 당연히 이다.
이제 이 모든 정점을 포함할 때까지 다음 세 단계를 반복한다.
- (아직 확정되지 않은 정점들) 중 이 가장 작은 정점 를 고른다.
- 를 에 추가한다. 이 시점에 가 의 진짜 최단거리로 확정되는데, 왜 그런지는 아래 정확성 증명에서 다룬다.
- 에서 나가는 간선을 따라 에 있는 인접 정점 들의 거리를 로 갱신한다.
Pseudo-code
// G = (V, E, w), 시작 정점 v0
R = { v0 }
d[v0] = 0
d[v] = ∞ for all v ≠ v0
d[v] = w(v0, v) if (v0, v) ∈ E
while (R != V) {
u = argmin { d[u] : u ∉ R };
R = R ∪ { u };
for each (u, v) ∈ E with v ∉ R {
d[v] = min(d[v], d[u] + w(u, v));
}
}
return d;
한 번의 반복은 정점 하나를 에 추가한다. 시작할 때 이고 끝났을 때 이므로, 반복은 정확히 번 일어난다.
이 글은 두 가지 가정 위에서 이야기를 풀어간다. 첫째로 모든 간선 가중치가 이상이고, 둘째로 시작점 에서 모든 정점에 도달할 수 있다고 본다. 음의 가중치나 도달 불가능한 정점을 처리하는 방법은 후속 글에서 다룬다.
단계별 진행 시각화
작은 그래프 한 개로 알고리즘을 따라가 보자.
- 정점: (시작점 )
- 간선:
각 단계에서 선택된 정점은 빨간색으로 채워지고, 그 정점이 갱신한 간선은 굵은 빨간색이 된다. 단계별 변화를 표로 정리하면 다음과 같다.
| 반복 | 선택된 | (추가 후) | 갱신된 값들 |
|---|---|---|---|
| 1 | , | ||
| 2 | , | ||
| 3 | |||
| 4 | — |
이 그림에서 볼 수 있듯이, Dijkstra는 항상 가까운 정점부터 추가한다. 값이 작은 순서대로 에 들어간다는 뜻이다.1
정확성 증명
Dijkstra가 매 단계 올바른 정점을 에 추가한다는 것, 그리고 그때 갱신하는 간선만으로 모든 거리가 정확히 유지된다는 것을 두 명제로 나누어 증명한다.
1. 가장 작은 가 정말 의 최단거리인가
알고리즘은 중 이 가장 작은 정점 를 골라 에 추가하면서 “가 의 최단거리”라고 선언한다. 이게 왜 맞을까?
가정. 가 의 최단거리가 아니라고 해 보자. 그러면 이보다 더 짧은 어떤 경로 가 존재할 것이다. 그런데 현재 는 “의 정점들만 거쳐서 에 도달하는 최단 길이”이므로, 는 반드시 어딘가에서 바깥으로 빠져나간다. 그렇게 처음으로 에 들어가는 정점을 이라고 하자.
관찰. 역시 의 원소다. 그러니 를 고른 조건(가장 작은 )에 의해
이 성립한다. 또한 경로 가 에 도달하는 시점의 누적 길이는 이미 이상이다. 이 “만 거쳐 에 도달하는 최단 길이”로 정의되기 때문이다.
결론. 따라서 의 전체 길이는
가 된다. 이는 가 보다 짧다는 가정에 모순이다. 그러므로 는 곧 의 최단거리다.
| 주장 | |
|---|---|
| 가정 | 가 의 최단거리가 아니다 → 더 짧은 경로 존재 |
| 관찰 | 는 어느 순간 로 빠짐 → 첫 진입점 존재 |
| 관찰 | 는 중 가장 작은 을 가지므로 |
| 결론 | → 모순 |
2. 왜 새로 추가된 의 간선만 갱신해도 충분한가
알고리즘은 를 에 추가한 직후, 에서 직접 나가는 간선만 가지고 의 거리를 갱신한다. 안에 있는 다른 정점들은 다시 들여다보지 않는다. 이렇게만 해도 왜 충분할까?
의문. 를 거쳐 다시 안의 어떤 정점 를 들렀다가, 거기서 의 정점 로 빠지는 경로는 왜 따로 고려하지 않아도 될까?
관찰. 는 보다 먼저 에 들어온 정점이다. 이미 에 있다는 건 그 시점에 의 거리가 확정됐다는 뜻이다. 그때 정해진 는 를 거치지 않고 얻은 길이다. 따라서
가 성립한다. 만약 이게 성립하지 않는다면 의 거리가 더 줄어들 수 있었다는 말이고, 이는 가 진짜 최단거리라는 사실과 모순이다.
결론. 그러므로 경로의 길이는
가 된다. 그런데 는 가 에 들어왔을 때 이미 를 갱신하면서 반영된 값이다. 즉 새로운 정보가 아니다. 그래서 를 거쳐 로 우회하는 경로는 굳이 볼 필요가 없고, 에서 로 직접 가는 간선만 새로 갱신하면 충분하다.
- Dijkstra는 답이 확정된 정점 집합 을 시작점만 넣고 시작해, 매 단계 중 이 가장 작은 정점을 골라 에 추가한다.
- 한 번의 반복이 정점 하나를 확정하므로 정확히 번 반복한 뒤 종료한다.
- 정확성 1: 가장 작은 는 곧 의 최단거리다. 를 한 번이라도 거치는 우회 경로는 이상이고, 이므로 더 짧을 수 없기 때문이다.
- 정확성 2: 새로 추가한 에서 직접 나가는 간선만 갱신해도 된다. 안의 다른 정점 를 우회하는 경로는, 가 이미 가진 가 최단인 이상 새로운 정보가 아니다.
- 결과적으로 한 시작점에서 모든 정점까지의 최단 거리가 단 한 번의 실행으로 얻어진다.
Prim은 cycle 위의 대체 간선 과 비교하는 방식으로, 그리디 선택이 항상 어떤 MST에 포함된다는 것을 보였다. Dijkstra의 증명은 조금 다른 방식이다. “이미 확정된 거리”라는 사실 자체에 기댄다. 새로 고른 의 를 흔들 수 있는 경로는 반드시 를 거쳐야 하는데, 의 정점은 정의상 모두 이상이기 때문이다. 둘 다 같은 그리디 알고리즘이지만 정확성을 보이는 방법은 이렇게 다르다.
다익스트라 알고리즘 2 — 최단 경로 복원, 시간 복잡도, 그리고 Prim과의 비교 에서는 거리만이 아니라 최단 경로 자체를 복원하는 방법, 우선순위 큐로 시간을 달성하는 구현, BFS와 Dijkstra의 관계, 그리고 Prim vs Dijkstra의 본격 비교까지 다룬다.
Footnotes
-
이 사실의 정식 증명은 후속 글에서 다룬다. 알고리즘이 추가하는 순서대로 이 단조증가한다는 것을 보이면 된다. ↩