위의 내용대로 공부해본다.
그리고
나의 전형적인 구현방식을 정의하고,
각 방식끼리의 차이점을 정리한다.
그리고 각각 최단 경로의 경로 자체를 출력하는 방식도 살펴본다.
https://www.acmicpc.net/problem/1753
양수 / BFS + priority_queue / 한점-다른점
ex) A->C를 구하기 위해
A->B가 1, B->C가 4라서 5가 걸렸을 때,
A->D가 3, D->C가 1이면, 4가 된다.
자료구조
1. 연결을 담을 connection 배열
vector<pair<int, int>> connection[N]
(통일성을 위헤 앞은 가중치, 뒤는 정점 인덱스를 담는다)
2. 최단 거리를 담을 d 배열
int d[N]
d[5]라면, 점 5까지 가는 최단 거리를 의미.
3. 다익스트라의 핵심인 priority_queue
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
구현법
1. 987654321로 d배열 초기화
(문제의 조건에 따라 달라짐. E * 가중치의 최대값 보다 커야 함)
2. BFS처럼 시작점의 거리d[Start]=0
으로 두고,
priority_queue에 넣음pq.push({d[Start], Start})
3. BFS문 진행
- 현재 큐에서 뽑은 가중치가, 현재 d의 최단거리와 다르다면,
현재 큐로는 최단거리를 만들 수 없으므로 스킵.
(항상 큐에서 뽑은 가중치 >= d 이다)- 연결된 간선을 순회하며, 계산 값이 더 작다면 갱신하고 큐에 넣는다.
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int V, E, St;
vector<pair<int, int>> connection[20001]; // 1입력 수가 많아서 vector로 표현
int d[20001]; // 2최단 거리
int track[20001];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin >> V >> E >> St;
for(int i=0;i<E;i++)
{
int u, v, w;
cin >> u >> v >> w;
connection[u].push_back({w,v});
}
// 모든 최단거리를 일단 987654321로 초기화
// 해당 값은 범위가 중요한데, E * 가중치 최대값을 초과하는 값으로 설정
for(int i=0; i<=V; i++)
{
d[i] = 987654321;
}
// 3priority_queue
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
// 가중치가 0이기 때문에 St라는 시작점에서 가장 먼저 시작하게 된다.
d[St] = 0;
pq.push({d[St], St});
// [BFS 방식과의 차이점]
// 1 일반 queue가 아니라 오름차순 정렬하는 priority_queue에 push
while(!pq.empty())
{
int w = pq.top().first;
int v = pq.top().second;
pq.pop();
// 새로운 경로가 더 커서 굳이 갱신할 필요 없다면 스킵.
if(d[v] != w)
{
continue;
}
// 현재 점에 연결된 간선을 순회
for(auto next : connection[v])
{
// 새로운 경로로 갱신할 수 없다면, 스킵.
if(d[v]+next.first < d[next.second])
{
d[next.second] = d[v]+next.first;
track[next.second] = v; // 여기서 갱신
pq.push({d[next.second], next.second});
}
}
}
// 출력부
for (int i = 1; i <= V; i++)
{
if (d[i] == 987654321)
{
cout << "INF\n";
}
else
{
cout << d[i] << "\n";
}
}
// 출력부 2
cout << "Track\n";
for (int i = 1; i <= V; i++)
{
if (track[i] == 0)
{
cout << i << "\n";
}
else
{
cout << track[i] << "\n";
}
}
return 0;
}
// 갱신할 수 있다면, 갱신하고, priority_queue에 push.
else
{
d[next.second] = d[v] + next.first;
track[next.second] = v; // 여기서 갱신
pq.push({d[next.second],next.second});
}
// 출력부 2
cout << "Track\n";
for (int i = 1; i <= V; i++)
{
if (track[i] == 0)
{
cout << i << "\n";
}
else
{
cout << track[i] << "\n";
}
}
1부터 시작하는데 1의 전은 없으므로 1,
2는 1, 3은 1
4는 2거쳐서 오므로 2
5도 전이 없으므로 5로 출력되게 만들었다.
https://www.acmicpc.net/problem/11657
음수 / 사이클은 탐지만 가능 / 한점-다른점 / 다익스트라보다 무거움
- 정말 이해가 안 돼서 찾아본 글
https://yabmoons.tistory.com/365
그 이유는 V-1번째에 모든 정점의 최소비용이 정의되는 것이 증명되었기 때문.
내가 생각할 때에는 위의 그래프는 다익스트라로도 풀 수 있을 것 같다.
그 이유는 아무리 음수 사이클이더라도 무한히 음수가 되는 사이클이 없기 때문.
자료구조
1. 연결을 담을 connection 배열
vector<pair<int, pair<int, int>>> connection
시작점, {끝, 가중치} 를 의미
2. 최단 거리를 담을 d배열
long long d[N]
문제마다 int나 long long을 붙여서 사용.
(웬만하면 long long 사용하자)
3. (음수) 사이클이 있는지 판단할 플래그 변수
bool isCycle = false
구현법
1. INF를 직접 정의하여 d배열 초기화
#define INF 1e9
e9는 10의 9승을 의미, 따라서 1e9는 10억
int의 최대값이 21억이므로 2e9를 사용하기도 한다.
2. 시작점의 거리d[Start] = 0
으로만 두고 벨만포드 실행
3. 벨만포드 진행
- V번 반복 (해당 인덱스가 쓰이는 일 없고, 단순 반복문)
- 모든 간선을 순회
- 간선의 from의 d값이 INF라면, continue
(중요한 부분. 해당 부분이 없으면 동작하지 않는다)d[from]+cost < d[to]
갱신해야하면 갱신.
- 만약 갱신시에 V번째 반복이라면,
사이클이 존재하므로isCycle = true
#include <iostream>
#include <vector>
#include <queue>
#define INF 1e9
using namespace std;
int V, E;
//vector<pair<int, int>> connection[501];
vector<pair<int, pair<int, int>>> connection; // 시작점, {끝, 가중치}
long long d[501];
bool isCycle = false;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> V >> E;
for (int i = 0; i < E; i++)
{
int u, v, w;
cin >> u >> v >> w;
// 시작점, {끝, 가중치}
connection.push_back({u,{v,w}});
}
// 모든 노드의 최소값을 일단 987654321로 초기화
for (int i = 0; i <= V; i++)
{
d[i] = INF;
}
// 시작은 0으로 둔다.
// 시작점은 여기서 설정하게 된다.
d[1] = 0;
// i는 정점의 개수 V번 반복, V-1번째에 최단 거리가 정해지지만,
// 마지막 V번째 작업을 통해 사이클을 확인
for(int i=1; i<=V; i++)
{
// 모든 간선을 순회한다.
for(int k=0; k<E; k++)
{
int from = connection[k].first;
int to = connection[k].second.first;
int cost = connection[k].second.second;
// 해당 코드가 없으면 안됨
// 방문한 적이 없는 정점에서 출발하는 간선은 무시해야 함.
if(d[from] == INF)
{
continue;
}
// 갱신할 수 있으면 갱신
if(d[from]+cost < d[to])
{
d[to] = d[from]+cost;
// 갱신을 하긴 했는데, V번째에 갱신되었다면, 음수 사이클 존재.
if(i==V)
{
isCycle = true;
}
}
}
}
// 출력부
if (isCycle)
{
cout << -1;
return 0;
}
else
{
// 문제는 1이 시작점이라서, 2부터 출력
// 2가 시작점이라면 1부터 출력하고, i==2인 경우를 생략하면 됨.
for(int i=2;i<=V;i++)
{
cout << (d[i] != INF ? d[i] : -1) << "\n";
}
}
return 0;
}
/* for문을 3개 사용하는 버전.. vector 배열 말고
벡터 하나에 모두 넣으면 되므로 사용하지 않는다.
// 이전 자료구조
connection[u].push_back({w, v});
for(int n=1; n<=V; n++)
{
// 정점 i의 j번째 연결
for(int i=1;i<=V;i++)
{
for(int j=0;j<connection[i].size();j++)
{
int w = connection[i][j].first;
int v = connection[i][j].second;
if(d[i]+w < d[v])
{
d[v] = d[i] + w;
if(n==V)
{
isCycle = true;
}
}
}
}
}
*/
https://www.acmicpc.net/problem/11404
음수 / 사이클이 있어도 가능 / 모든 한점-다른점
시간복잡도가 직접적인 수행시간을 의미하지는 않는다. 혼란주의
d[i][j] = min(d[i][j], d[i][k] + d[k][j])
자료구조
1. 최단 거리를 담을 2차원 d배열
int d[101][101]
벡터가 아니고 그냥 배열이어야 하는 이유는,
간선의 시작과 끝을 명확히 인덱스로 표시할 수 있어야 하기 때문.
- 대신 connection 배열을 생성하지 않는다.
구현법
1. INF를 직접 정의하여 2차원 d배열 초기화
2. 각 점의 본인과의 거리는 0으로 설정
3. 플로이드 진행
- for문 3번 (kij)
- 아래 코드 실행. 끝.
d[i][j] = min(d[i][j], d[i][k] + d[k][j])
#include <iostream>
#define INF 1e9
using namespace std;
int V, E;
int d[101][101];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin >> V >> E;
// 1 일단 INF로 모든 값을 채움
for(int i=1; i<=V; i++)
{
for(int j=1; j<=V; j++)
{
d[i][j] = INF;
}
}
// 2 입력 받아서 바로 d에 저장
for(int i=1; i<=E; i++)
{
int u, v, w;
cin >> u >> v >> w;
//d[u][v] = w; // 1 일반적인 경우
d[u][v] = min(d[u][v], w); // 2 현재 문제의 조건에 따름 - 두 점 잇는 간선은 여러개
}
// 3 본인과의 거리는 0으로 설정
for(int i=1; i<=V; i++)
{
d[i][i] = 0;
}
// 4 Floyd
for(int k=1; k<=V; k++)
{
for(int i=1;i<=V;i++)
{
for(int j=1;j<=V;j++)
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
// 출력
for(int i=1; i<=V; i++)
{
for(int j=1; j<=V; j++)
{
if (d[i][j] == INF)
{
cout << "0 ";
}
else
{
cout << d[i][j] << " ";
}
}
cout << "\n";
}
return 0;
}
생략.. 3차원 배열을 만들어야 할 것 같다.
아래의 방법으로는 구해지지 않는다.
그리고 구하는 사람도 문제도 찾지 못했다..ㅇ
int track[101][101]
if(d[u][v] > w)
{
d[u][v] = w;
track[u][v] = u;
}
track[u][v]라는 것은, u를 시작점으로 보았을 때,
v의 이전 노드를 의미.
if(d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j];
track[i][j] = k;
track[i][k] = i;
}
간단하게 track[1][i]로 1에 대한 경로만 출력해 보았다.
Track - 1
1 before 0
2 before 1
3 before 1
4 before 1
5 before 3