이전 글에서는 Dijkstra 알고리즘을 나에게 가작 익숙한 언어인 Python으로 구현해보았다.
C++은 20살 이후로 처음 써보는데, 그 당시에는 A학점을 받았지만 지금 다시 써보려니 어색하고 낯설다.
또한 맥북에서 C++을 사용하려니 맥용 비주얼 스튜디오는 지원이 중단되어서 VSCode 익스텐션을 사용해야 하는데 이 설정 과정이 좀 번거로웠다.
아무튼, 이번에는 Dijkstra 알고리즘을 C++로 구현해보겠다.
C++ 표준 라이브러리의 priority_queue
를 활용하여 Dijkstra 알고리즘을 구현할 것이다.
(시작 노드로부터 다른 모든 노드까지의 최단 거리 계산)
priority_queue
의 기본 개념priority_queue
는 C++ 표준 라이브러리(STL)에서 제공하는 우선순위 큐 자료구조이다. 이 큐는 가장 높은 우선순위를 가진 요소가 먼저 나오도록 정렬된다.priority_queue
는 기본적으로 최대 힙(max heap) 구조로 구현되어, 가장 큰 값이 우선순위를 가지는 형태이다.priority_queue
Dijkstra 알고리즘에서는 최단 거리를 기준으로 가장 우선순위가 높은 (가장 거리가 짧은) 노드를 먼저 처리해야 한다. 따라서 최소 힙(min heap) 형태의 priority_queue
를 사용하여 최단 거리를 가진 노드를 선택하도록 한다.
C++의 priority_queue
는 기본적으로 최대 힙이므로, 최소 힙을 만들기 위해 greater<type>
를 사용하여 우선순위를 반전시켜야 한다.
priority_queue
의 주요 함수pq.push(item)
: 큐에 item
을 삽입한다.pq.top()
: 현재 가장 높은 우선순위를 가진 요소를 반환한다.pq.pop()
: 현재 가장 높은 우선순위를 가진 요소를 제거한다.(반환 X)priority_queue
의 시간 복잡도push
) : pop
) : top
) : #include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <limits>
using namespace std;
// node, weight 설정을 위한 pair 정의
typedef pair<int, int> pii;
// Dijkstra 알고리즘 함수
unordered_map<int, int> dijkstra(const unordered_map<int, vector<pii>>& graph, int start) {
// 각 노드까지의 최단 거리를 저장하는 unordered_map (초기값 : 무한대)
unordered_map<int, int> distances;
for (const auto& node : graph) {
distances[node.first] = numeric_limits<int>::max();
}
distances[start] = 0;
// 우선순위 큐 초기화 (first : 거리, second : node 인 pair)
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
while (!pq.empty()) {
int current_distance = pq.top().first;
int current_node = pq.top().second;
pq.pop();
// 이미 처리된 노드라면 건너뛴다
if (current_distance > distances[current_node]) {
continue;
}
// 인접한 노드와 가중치 확인
for (const auto& neighbor : graph.at(current_node)) {
int adjacent = neighbor.first;
int weight = neighbor.second;
int distance = current_distance + weight;
// 최단 거리를 갱신할 수 없는 경우
if (distance < distances[adjacent]) {
distances[adjacent] = distance;
pq.push({distance, adjacent});
}
}
}
return distances;
}
int main() {
// 그래프 정의 (인접 리스트 형태)
unordered_map<int, vector<pii>> graph;
graph[1] = {{2, 2}, {3, 4}};
graph[2] = {{1, 2}, {3, 1}, {4, 7}};
graph[3] = {{1, 4}, {2, 1}, {4, 3}};
graph[4] = {{2, 7}, {3, 3}};
int start_node = 1;
unordered_map<int, int> distances = dijkstra(graph, start_node);
cout << "노드 " << start_node << "에서 다른 노드까지의 최단 거리 : \n";
for (const auto& [node, distance] : distances) {
cout << "노드 " << start_node << "에서 노드 " << node << "까지의 거리 : ";
if (distance == numeric_limits<int>::max()) {
cout << "도달 불가\n";
} else {
cout << distance << "\n";
}
}
return 0;
}
C++의 최신 문법에도 적응하지 못해서 완전 애먹었다. 아무래도 C++을 제대로 사용하려면 많은 공부가 필요할 것 같다.
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <limits>
iostream
: Console의 입출력 기능vector
: 인접 노드를 저장하기 위해 사용queue
: Dijkstra 알고리즘에서 우선순위 큐를 구현하기 위해 priority_queue
를 사용하기 위함unordered_map
: 그래프의 인접 리스트와 최단 거리를 저장하기 위해 사용.limits
: 거리 초기화에 무한대 값을 사용하기 위해 numeric_limits
를 포함한다여기서 unordered_map
은 키-값 쌍을 저장할 때 사용하는 자료구조다. 해시 테이블 기반으로 구현되어 데이터가 정렬되지 않고 저장되고, 데이터를 저장할 때 각 키가 특정 해시값으로 매핑된다는 점에서 map
과 차이가 있다.
// node, weight 설정을 위한 pair 정의
typedef pair<int, int> pii;
typedef pair<int, int> pii
: pair<int, int>
를 pii
라는 타입 별칭으로 작성함. pair<int, int>
의 첫번째 int
는 node, 두번째 int
는 가중치이다.// Dijkstra 알고리즘 함수
unordered_map<int, int> dijkstra(const unordered_map<int, vector<pii>>& graph, int start) {
unordered_map
형태로 반환한다.graph
: 인접 리스트 형태로 표현된 그래프. 각 노드의 연결관계와 가중치를 저장한다.start
: 알고리즘이 시작되는 노드 번호 unordered_map<int, int> distances;
for (const auto& node : graph) {
distances[node.first] = numeric_limits<int>::max();
}
distances[start] = 0;
distance
는 각 노드까지의 최단 거리를 저장하는 unordered_map
이다. 각 노드의 거리를 초기화하여 시작 노드는 0, 나머지 노드는 무한대로 설정한다.numeric_limits<int>::max()
: 무한대를 표현하기 위한 값 while (!pq.empty()) {
int current_distance = pq.top().first;
int current_node = pq.top().second;
pq.pop();
pq.top()
을 통해 현재 최단 거리의 노드와 거리를 가져온다.current_distance
는 현재 노드까지의 거리, current_node
는 현재 노드 // 이미 처리된 노드라면 건너뛴다
if (current_distance > distances[current_node]) {
continue;
}
current_distance
가 distances[current_node]
보다 크다면, 이미 최단 거리가 확정된 노드이므로 탐색하지 않고 건너뛴다. (큐에 중복으로 들어가는 불필요한 경로들을 처리하는 것을 막아서, Dijkstra 알고리즘의 효율성을 높인다) // 인접한 노드와 가중치 확인
for (const auto& neighbor : graph.at(current_node)) {
int adjacent = neighbor.first;
int weight = neighbor.second;
int distance = current_distance + weight;
// 최단 거리를 갱신할 수 없는 경우
if (distance < distances[adjacent]) {
distances[adjacent] = distance;
pq.push({distance, adjacent});
}
}
neighbor.first
는 인접 노드의 번호, neighbor.second
는 해당 경로의 가중치distance = current_distance + weight
를 통해 현재 노드에서 인접 노드로 이동할 때의 새로운 거리를 계산한다distances[adjacent]
을 새로운 거리로 갱신한다. return distances;
distance
맵을 반환한다.int main() {
// 그래프 정의 (인접 리스트 형태)
unordered_map<int, vector<pii>> graph;
graph[1] = {{2, 2}, {3, 4}};
graph[2] = {{1, 2}, {3, 1}, {4, 7}};
graph[3] = {{1, 4}, {2, 1}, {4, 3}};
graph[4] = {{2, 7}, {3, 3}};
int start_node = 1;
unordered_map<int, int> distances = dijkstra(graph, start_node);
cout << "노드 " << start_node << "에서 다른 노드까지의 최단 거리 : \n";
for (const auto& [node, distance] : distances) {
cout << "노드 " << start_node << "에서 노드 " << node << "까지의 거리 : ";
if (distance == numeric_limits<int>::max()) {
cout << "도달 불가\n";
} else {
cout << distance << "\n";
}
}
return 0;
}
그래프 정의 : unordered_map
으로 그래프를 정의한다. 각 노드와 연결된 다른 노드와 그 간선의 가중치를 인접 리스트 형태로 저장한다. 대략 아래 그림과 같은 형태다.
출력
distances
맵에서 시작 노드로부터 각 노드까지의 최단 거리를 출력한다.numeric_limits<int>::max()
)라면, 해당 노드에 도달할 수 없음을 의미하여 "도달 불가" 로 출력한다.