기타등등/알고리즘 기록

[C++] 벨만포드 알고리즘 - 기초

CodeJB 2021. 8. 10. 20:31
다익스트라에서 Greedy를 뺌, 간선이 음의 값이어도 됌

 

#include <iostream>
#include <vector>
using namespace std;
int dist[101];
struct Edge{
    int s;
    int e;
    int val;
    Edge(int a, int b, int c){
        s = a;
        e = b;
        val = c;
    }
};
int main() {
    vector<Edge> Ed;
    int i, n, m, a, b, c, j ,s ,e;
    scanf("%d %d",&n,&m);
    
    for(i = 1; i <= m; i++){//그래프 생성 인접리스트
        scanf("%d %d %d", &a, &b, &c);
        Ed.push_back(Edge(a,b,c));
    }
    for(i = 1; i <= n; i++){
        dist[i] = 2147000000;
    }
    scanf("%d %d", &s, &e);
    dist[s] = 0;
    
    for(i = 1; i < n; i++){
        for(j = 0; j < Ed.size(); j++){
            int u = Ed[j].s;
            int v = Ed[j].e;
            int w = Ed[j].val;
            if(dist[u]!=2147000000 && dist[u]+w < dist[v]){ 
                dist[v] = dist[u]+w;
            }
        }
    }
    for(j = 0; j < Ed.size(); j++){
        int u = Ed[j].s;
        int v = Ed[j].e;
        int w = Ed[j].val;
        if(dist[u]!=2147000000 && dist[u]+w < dist[v]){ //음의 싸이클
            printf("-1\n");
            exit(0);
        }
    }
    printf("%d\n", dist[e]);
    return 0;
}

특징

  • 벨만포드는 다익스트라처럼 출발도시로부터 갈 수 있는 모든 도시까지의 최단거리를 구하는 알고리즘임
  • 하지만, 벨만포드는 Greedy하지 않기 때문에 정렬 과정이 없음
  • Kruskal에서처럼 벡터에 그래프 정보를 저장하고 다익스트라처럼 dist배열에 무한대 값을 넣어놓고 최저값을 계속 할당시킴.
  • 간선 값이 음의 값이어도 괜찮음(하지만 음의 사이클은 만들어져선 안됌)
  • 만약 문제에서 간선값이 음의 값인데 음의 사이클에 대한 예외처리를 요구한다면? 벨만 포드일 확률이 높음.

 

 

1. distance 배열을 할당하 되, 전부 무한대로 할당해 놓는다.(다익스트라&벨만포드 고유의 방법)

vector<int> dist(n+1, 2147000000);//벡터 초기화
//위 코드에서는 배열로 선언함

2. 벡터에 그래프 정보를 저장한다(Kruskal)

struct Edge{
    int s;
    int e;
    int val;
    Edge(int a, int b, int c){
        s = a;
        e = b;
        val = c;
    }
};

for(i = 1; i <= m; i++){//그래프 생성 인접리스트
    scanf("%d %d %d", &a, &b, &c);
    Ed.push_back(Edge(a,b,c));
}

3. 시작노드는 dist 배열에 0으로 초기화 한다.

dist[s] = 0;

4. 벡터에 저장되어 있는 그래프 정보를 쭉 훑으면서 dist배열에 할당한다.

for(i = 1; i < n; i++){
        for(j = 0; j < Ed.size(); j++){
            int u = Ed[j].s;
            int v = Ed[j].e;
            int w = Ed[j].val;
            if(dist[u]!=2147000000 && dist[u]+w < dist[v]){ //u:1에서 v:2까지의 비용 < 원래저장된 v까지 비용(이전 경로횟수에서 저장된 값)
                dist[v] = dist[u]+w;
        }
}

4-1. 위의 코드에서 바깥쪽 for문은 굳이 왜 있는걸까?

  • 해당 문제는 입력 순서(=Vector 저장 순서)가 정답이 도출되는 순서대로 입력되었기 때문에 문제가 없다.
  • 하지만, 입력순서가 무조건 경로순으로 입력되어있지 않다. 예를 들어,
  • 출발 : 3 도착 : 5 , 2 5 13 간선을 2 5 1로 수정하면 최소비용값 9가 계산되지 않는다.
  • 만약, i가 1이고 안쪽 j for문이 2 3 -3을 가리켰다면 2는 처음에 무한대이기 때문에 3이 relax되지 않는다.
  • 2 5 1도 2가 무한대이기 때문에 relax되지 않는다.
  • 3이 출발노드 이므로 3은 무한대가 아닌 0이라서 3 4 5는 relax된다.
  • 이렇게 돌리다보면, i =1 일 때, 무한대 8 0 5 12로 마무리된다.
  • 그러면 결국 2에서 3으로 가거나 2에서 5로 가는 경로는 무시된 값이라는 것이다. 이렇게 입력 순서가 입력순서가 경로와는 다르게 꼬여서 제시된다면 경로가 무시될 수 있기 때문에
  • 혹시 무한대여서 무시된 경로가 있나? 다시 한번 살펴볼 필요가 있다(무시된 노드에 대해서 모두 한 번씩 기회를 줘야한다.)
  • 그래서 도착노드를 제외한 노드의 수 만큼 반복하여 무한대여서 무시당한 모든 노드에게 한 번씩 기회를 주어야 한다.

5. 음의 사이클 유무 확인

for(j = 0; j < Ed.size(); j++){
        int u = Ed[j].s;
        int v = Ed[j].e;
        int w = Ed[j].val;
        if(dist[u]!=2147000000 && dist[u]+w < dist[v]){ //음의 싸이클
            printf("\n -1\n");
            exit(0);
     }
}
  • 위에서 안쪽 j for문 돌린 것과 똑같이 한번 더 해보는 것이다.
  • 위쪽에서 이미 relax가 끝났는데, 또 relax하려고한다.? 그럼 cycle이 있다는 뜻(끊임 없이 relax)이다.
  • 따라서, -1을 출력하고 바로 끝내버린다.
https://www.inflearn.com/course/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/dashboard