이것도 알아야 하네?
[개념 정리] 최소 거리 구하기: 다익스트라 (개념 및 C++ 우선순위 큐를 이용한 구현) 본문
다익스트라 알고리즘 개념
다익스트라 알고리즘 또는 데이크스트라 알고리즘은 그래프에서 노드(Node) 간의 최단 경로를 찾는 알고리즘 중 하나로, 시작 노드(Node)가 주어졌을 때 해당 시작점으로부터 다른 모든 노드(Node)까지의 최단 경로 찾습니다. 해당 알고리즘은 도로 교통망 및 라우팅 프로토콜 등에서 많이 활용되고 있습니다. 예를 들어, 그래프의 꼭짓점들이 각각 도시를 나타내고, 변들이 도시 사이를 연결하는 도로의 길이를 나타낸다면, 다익스트라 알고리즘을 통하여 두 도시 사이의 최단 경로를 찾을 수 있습니다.
다익스트라 동작
소스(Source)는 시작 노드(Node)이며, dist[]를 시작 노드(Node)와 시작 노드(Node)를 포함한 모든 노드까지의 거리로 정의합니다. dist[]에서는 시작 노드(Node)를 포함하고 있기 때문에 시작 노드(Node) 간의 거리는 0으로 부여하고, 나머지 노드(Node)에 대한 거리는 무한대로 초기 값을 부여합니다. prev[]를 이전에 방문한 노드(Node)로 정의합니다.
슈도 코드를 보면 알 수 있듯이, Q에는 모든 노드(Node)를 저장하고 해당 Q안에 존재하는 노드(Node) 중 최소 거리를 갖는 노드(Node)를 다음 방문 노드(Node)로 설정합니다. 초기 상태에는 dist[]에서 시작 노드(Node)의 값만 0이고 나머지는 무한대를 가지므로 항상 시작 노드(Node)가 선택됩니다. 선택된 노드(Node)는 Q에서 제거되고, 해당 노드(Node)에 연결된 다른 노드(Node)들을 확인하며 현재 dist[]에 저장된 값보다 짧은 거리를 발견했을 때 dist[]와 prev[]의 값을 업데이트합니다.
다익스트라 알고리즘 구현
1. 리스트 기반 구현 (Vanilla Code)
#include <iostream>
#include <vector>
#include <limits.h>
using namespace std;
vector<bool> visited; // 방문 유무를 저장하는 벡터 선언
vector<int> d; // 시작 노드에서부터 각 노드까지의 최단 거리를 저장하는 벡터 선언
int getSmallestNode(int V) // 방문하지 않은 노드 중에서, 가장 최단 거리가 짧은 노드의 번호를 반환.
{
int min_value=INT_MAX;
int index = 0;
for(int i=0; i<V; i++)
{
if(d[i] < min_value && !visited[i])
{
min_value = d[i];
index = i;
}
}
return index;
}
void dijkstra(int V, vector<pair<int,int>> graph[], int start)
{
d[start]=0;
visited[start]= true;
for(int j=0; j<graph[start].size(); j++)
{
int adj = graph[start][j].first;
d[adj] = graph[start][j].second; // 최단거리 테이블에 초기값 세팅
}
for(int i=0; i<V-1; i++)
{
int now = getSmallestNode(V);
visited[now]=true;
for(int j=0; j<graph[now].size(); j++)
{
int cost = d[now] + graph[now][j].second;
if(cost<d[graph[now][j].first])
{
d[graph[now][j].first]=cost; // 최단 거리 업데이트
}
}
}
}
int main()
{
int V, E, src; // (V: # of nodes, E: # of edges, start: source)
cin >> V >> E; // 모든 간선 정보를 입력 받기
cin >> src; // 시작점 입력 받기
vector<pair<int,int>> graph[V]; // pair를 저장하는 벡터 V 개 생성
for (int i = 0; i < E; i++)
{
int from, to, cost;
cin >> from >> to >> cost;
graph[from-1].push_back({to-1, cost}); // graph[] 에 from에서 to까지 가는 가는 비용 저장
}
d.assign(V, INT_MAX); // 초기 값을 무한대로 저장
visited.assign(V, 0); // 방문한 적 없음을 선언
dijkstra(V, graph, src-1);
// 모든 노드로 가기 위한 최단 거리를 출력
for (int i = 0; i < V; i++)
{
if (d[i] == INT_MAX)
cout << "INFINITY ";
else
cout << d[i] << " ";
}
return 0;
}
노드(Node)의 수와 간선(Edge)의 수를 먼저 받고, 시작 노드(Node) 및 연결 관계정보를 받는다면, 시작 노드(Node)로 부터 다른 모든 노드(Node)까지 최소 거리를 출력할 수 있습니다. graph[]는 사이즈가 V인 벡터의 array로 graph[v-1]는 V개의 노드(Node) 중 v번 째에 연결되어 있는 다른 노드의 (번호 -1)와 거리를 저장합니다. -1을 하는 이유는 인덱스가 0부터 시작하기 때문입니다. 다익스트라 함수의 내부는 시작점이 주어졌기 때문에, 해당 노드(Node) 방문했음을 visited[]에 저장하고, 해당 노드(Node)에 연결된 정보를 graph에서 찾아 d[]의 값을 업데이트해준다. 그 후, 앞에서 설명했듯이, d[]에 저장된 값 중 가장 짧은 거리를 찾아 다음으로 갈 노드(Node)로 선정합니다. 이 때, 이미 다녀온 노드(Node)에는 방문할 필요가 없으니, 제외합니다. 거리 기준으로 다음으로 방문할 노드(Node)가 선정되면, 해당 노드(Node)에 연결된 모든 노드(Node)를 확인하여 현재까지 저장된 거리보다 짧은 거리를 발견했을 경우 d[]를 업데이트합니다. 모든 노드(Node)를 확인하면 다시 d[]에서 거리를 확인하여 다음으로 방문할 노드(Node)를 찾고, 이를 모든 Node를 방문할 수 있을 때까지 반복합니다. 해당 코드는 단순하게 구현했을 때, d[]를 확인하여 가장 짧은 노드(Node)를 찾고 이를 노드(Node) 개수만큼 반복하므로 실행시간은 O(V2)에 비례한다.
2. 우선 순위 Queue를 활용한 구현
#include <bits/stdc++.h>
using namespace std;
vector<bool> visited; // 방문 유무를 저장하는 벡터 선언
vector<int> d; // 시작 노드에서부터 각 노드까지의 최단 거리를 저장하는 벡터 선언
void dijkstra(int V, vector<pair<int,int>> graph[], int start)
{
priority_queue<pair<int,int>>pq; // (거리, 노드 인덱스)를 거리 순서대로 정렬할 queue 생성
pq.push({0,start}); //시작 노드로 가기위한 최단 경로는 0으로 설정하여, 큐에 삽입.
d[start]=0;
while(!pq.empty())
{
int dist = -pq.top().first; //현재 노드까지의 비용
int now = pq.top().second; // 현재 노드
pq.pop();
if(d[now]<dist) // 이미 최단경로를 체크한 노드인 경우 패스
continue;
for(int i=0; i<graph[now].size(); i++)
{
int cost = dist+graph[now][i].second; // 거쳐서 가는 노드의 비용을 계산
// 현재노드까지 비용 + 다음 노드 비용
if(cost<d[graph[now][i].first]) // 비용이 더 작다면 최단경로 테이블 값을 갱신.
{
d[graph[now][i].first]=cost;
pq.push(make_pair(-cost,graph[now][i].first));
}
}
}
}
int main()
{
int V, E, src; // (V: # of nodes, E: # of edges, start: source)
cin >> V >> E; // 모든 간선 정보를 입력 받기
cin >> src; // 시작점 입력 받기
vector<pair<int,int>> graph[V]; // pair를 저장하는 벡터 V 개 생성
for (int i = 0; i < E; i++)
{
int from, to, cost;
cin >> from >> to >> cost;
graph[from-1].push_back({to-1, cost}); // graph[] 에 from에서 to까지 가는 가는 비용 저장
}
d.assign(V, INT_MAX); // 초기 값을 무한대로 저장
visited.assign(V, 0); // 방문한 적 없음을 선언
dijkstra(V, graph, src-1);
// 모든 노드로 가기 위한 최단 거리를 출력
for (int i = 0; i < V; i++)
{
if (d[i] == INT_MAX)
cout << "INFINITY ";
else
cout << d[i] << " ";
}
return 0;
}
리스트 기반으로 작성했을 때 가장 짧은 거리를 찾기 위해 반복문을 도는 부분을 priority queue는 자동으로 처리해주어 해당 부분을 작성할 필요가 없다. 또한 반복문을 돌 경우 O(n)의 시간 복잡도를 가지지만, priority queue 내부에서 자동으로 처리해주는 정렬은 O(log(n))의 시간 복잡도를 가진다.
주의할 점은 우선 순위의 큐는 default는 내림차순이기 때문에 가장 큰 값이 front에 위치하게 된다. 가장 작은 값이 앞에 두기 위해서 큐에 '-'를 곱하여 음수로 넣고 다시 꺼낼 때 '-'를 다시 곱해서 본래의 값을 사용한다. '-'를 곱하면 가장 큰 값이 가장 작은 값이 되고 가장 작은 값이 가장 큰 값이 되기 때문이다.
또는 priority_queue<pair<int,int> ,vector<pair<int,int> >,greater<pair<int,int> > pq로 선언하시면 오름차순으로 명시해주었기 때문에 가장 작은 값이 가장 위로 정렬된다.
'프로그래밍 > 알고리즘' 카테고리의 다른 글
[C++] Programmers 동굴탐험 (2020 카카오 인턴십) (0) | 2021.12.07 |
---|---|
[Python] Programmers 보석 쇼핑 (2020 카카오 인턴십) (0) | 2021.11.24 |
[책 리뷰] 다이내믹 프로그래밍 완전 정복: 빠르고 우아한 상향식 문제 풀이법 (0) | 2021.11.19 |
[C++] Programmers 미로 탈출 풀이 (2021 카카오 채용연계형 인턴십) (0) | 2021.11.13 |
[C++] 백준 14499번 주사위 굴리기 (삼성 SW 역량 테스트 기출 문제) (0) | 2021.11.12 |