다익스트라 알고리즘 Dijkstra Algorithm
본 글은 제 개인적인 공부를 위해 작성한 글입니다. 틀린 내용이 있다면 언제든지 피드백을 주시면 감사하겠습니다. 참고로만 활용해주시길 바랍니다.
개요
하나의 정점에서 출발했을 때 다른 모든 정점으로의 최단 경로를 구하는 알고리즘
- 어떤 경로도 음수 가중치를 갖지 않는 그래프에서 사용 가능
- 유향/무향 상관이 없다.
다익스트라 알고리즘은 욕심쟁이 기법과 다이나믹 프로그래밍 속성을 모두 가지고 있다.
욕심쟁이 기법
매 순간 현재 위치에서 갈 수 있는 가장 가까운 노드를 선택하는 방식이다. 지금 당장 거리가 가장 짧은 노드를 방문하면, 그 노드까지의 최단 거리는 더 이상 바뀔 일이 없다. 한 번 최단 거리로 확정된 노드는 다시 계산하지 않고 그대로 넘어간다.
다이나믹 프로그래밍
알고리즘이 동작하는 구조를 보면 DP의 핵심 원리인 최적 부분 구조(Optimal Substructure)를 활용한다. 시작점부터 노드 C까지 가는 최단 거리는 ‘시작점 -> 노드 B까지의 최단 거리’ + 노드 B -> C 거리’의 합으로 이루어진다. 즉, 큰 문제의 해를 구하기 위해 부분 문제의 해를 활용한다. 또한, 각 노드까지의 최단 거리를 테이블에 기록해 두고, 새로운 경로를 발견할 때마다 기존 값과 비교하여 갱신하는 메모이제이션을 사용한다.
과정
- 모든 노드의 거리를 큰 수로 초기화하고 시작 노드의 거리를 0으로 초기화
- 아직 방문하지 않은 노드 중 가장 가중치가 작은 노드 방문
- 해당 노드를 거쳐 갈 수 있는 노드의 거리가 이전보다 작으면 해당 거리를 갱신
- 더 이상 방문할 노드가 없을 때까지 2~3 반복
모든 노드의 거리를 큰 수로 초기화하고, 시작 노드(1)의 거리를 0으로 초기화
시작 노드에서 갈 수 있는 노드들의 거리를 갱신 (현재 선택된 노드와 에지는 파란색으로 표시)
아직 방문하지 않은 노드 중 가장 가중치가 작은 노드(2) 방문 및 해당 노드를 거쳐 갈 수 있는 거리 중 최단 거리가 있다면 갱신 (이미 방문한 노드는 빨간색으로 표시)
더 이상 방문할 노드가 없고, 노드 1에서 모든 노드로 가는 최단 거리를 구하였음
시간 복잡도
$V$: 노드의 숫자, $E$: 간선의 숫자
초기 알고리즘의 시간 복잡도는 $O(V^2)$
- $V$ 크기의 최단거리 1차원 배열을 매번 반복문을 이용하여 검사
- 총 $O(V)$번에 걸쳐서 모든 인접노드를 검사하기에 $O(V^2)$
최소 힙(우선순위 큐)으로 구현 시 $O((V+E)log V)$
- $VlogV+ElogV=(V+E)logV$
- 각 노드마다 미방문 노드 중 출발점으로부터 현재까지 계산된 최단 거리를 가지는 노드를 찾는데 $O(VlogV)$
- 최대 $V$개의 노드가 들어가 있는 우선순위 큐에서 삭제 연산 $(logV)$
- 각 노드마다 이웃한 노드의 최단거리를 갱신하는데 $O(ElogV)$
- 각 노드에 연결된 최대 $E$개의 에지에서 우선순위 큐에 삽입 연산 $(logV)$
백준 11779번 : 최소 비용 구하기 2
https://www.acmicpc.net/problem/11779
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
struct node
{
int end;
int cost;
node(const int inEnd, const int inCost) : end(inEnd), cost(inCost) {}
bool operator<(const node& other) const
{
return cost > other.cost;
}
};
int N, M;
void Dijkstra(std::vector<std::vector<node>>& graph, int start, int end)
{
std::vector dist(N + 1, INT_MAX);
dist[start] = 0;
std::priority_queue<node> pq;
pq.emplace(start, 0);
std::vector backtracking(N + 1 , -1);
while (!pq.empty())
{
auto [node, cost] = pq.top();
pq.pop();
if (dist[node] < cost) continue;
for (auto& [adj_node, adj_cost] : graph[node])
{
if (dist[adj_node] > dist[node] + adj_cost)
{
dist[adj_node] = dist[node] + adj_cost;
pq.emplace(adj_node, dist[adj_node]);
backtracking[adj_node] = node;
}
}
}
std::vector<int> path;
for (int i = end; i != -1; i = backtracking[i])
{
path.emplace_back(i);
}
std::reverse(path.begin(), path.end());
std::cout << dist[end] << '\n' << path.size() << '\n';
for (const auto& elem : path)
{
std::cout << elem << ' ';
}
}
int main()
{
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
std::cin >> N >> M;
std::vector graph(N + 1, std::vector<node>());
int s, e, c;
for (int i = 0; i < M; i++)
{
std::cin >> s >> e >> c;
graph[s].emplace_back(e, c);
}
int start, end;
std::cin >> start >> end;
Dijkstra(graph, start, end);
return 0;
}
참고


