벨만-포드 (Bellman–Ford Algorithm) 알고리즘
Bellman-Ford 알고리즘은 (Richard Bellman과 Lester Ford의 이름)
가중 그래프에서 정점과 다른 모든 정점 사이의 최단 경로를 찾는 데 사용되는 최단 경로 알고리즘입니다.
간단하게 말하면 한 정점에서 다른 정점까지의 최단 거리를 구하는 알고리즘입니다.
작동하는 과정
https://www.geeksforgeeks.org/bellman-ford-algorithm-dp-23/
1단계: 출발 정점을 0으로 둡니다.
출발 정점의 거리를 제외하고 모든 거리를 무한으로 초기화합니다.
2단계: 모든 정점을 다음 순서로 처리합니다
(B, E), (D, B), (B, D), (A, B), (A, C), (D, C), (B, C), (E, D)
모든 모서리가 처음으로 처리될 때 다음과 같은 거리를 얻습니다.
첫 번째 행은 초기의 거리를 보여줍니다.
두 번째 행은 가장자리 (B, E), (D, B), (B, D) 및 (A, B)가 처리될 때 거리를 보여줍니다.
세 번째 행은 (A, C)가 처리될 때의 거리를 보여줍니다.
네 번째 행은 (D, C), (B, C) 및 (E, D) 를 처리한 거리를 보여줍니다.
3단계: 첫 번째 반복은 최대 1 정점 길이의 모든 최단 경로를 제공합니다.
모든 정점이 두 번째로 처리 될 때 다음과 같은 거리를 얻습니다 (마지막 행은 최종 값을 나타냅니다).
4단계: 두 번째 반복은 최대 2 개의 가장자리 길이인 모든 최단 경로를 제공합니다.
알고리즘은 모든 정점을 2번 더 처리합니다.
두 번째 반복 후에는 거리가 최소화되므로
세 번째 및 네 번째 반복에서는 거리가 업데이트되지 않습니다.
주의 사항
Bellman-Ford 알고리즘을 사용할 경우 아래와 같은 음의 가중치 주기를 주의해야 합니다.
위의 예제는 1 -> 2 -> 3 순서를 계속 반복하면 가중치가 계속 줄어듭니다. (잘못된 결과)
위와 같은 상황에서는 Bellman-Ford 알고리즘으로 답을 구할 수 없습니다.
그래프에 음의 가중치 주기가 포함되어 있는지 확인하려면
Bellman-Ford 알고리즘 이후에 모든 간선을 다시 한번 확인합니다.
만약 업데이트가 일어난다면 음의 가중치가 포함되어있다는 의미 입니다.
구현 코드
백준 11657번 ‘타임머신’ 문제를 Bellman-Ford 알고리즘 코드 해결
코드 C++
#include <iostream> #include <vector> using namespace std; constexpr long long int INF = INT64_MAX; constexpr int MAXN = 501; long long int disArr[MAXN]; bool check = false; // 음의 순환이 있는지 확인 // N : 도시의 개수, M : 버스 노선의 개수 // A : 시작 도시, B : 도착 도시, C : 걸리는 시간 int N, M, A, B, C; vector<pair<int, int>> graph[MAXN]; int main() { ios_base::sync_with_stdio(false); // scanf와 동기화를 비활성화 // cin.tie(null); 코드는 cin과 cout의 묶음을 풀어줍니다. cin.tie(NULL); std::cout.tie(NULL); cin >> N >> M; for (int i = 0; i < M; i++) { cin >> A >> B >> C; // 단방향 graph[A].push_back(make_pair(B, C)); } // 배열 초기화 fill(disArr, disArr + MAXN, INF); disArr[1] = 0; for (int k = 1; k <= N; k++) {// (모든 정점의 수 - 1) 번 확인한다. // 모든 노선을 확인한다. for (int i = 1; i <= N; i++) { for (int j = 0; j < graph[i].size(); j++) { int u = i; // 시작점 int v = graph[i][j].first; // 도착점 int weight = graph[i][j].second; // 가중치 // 만약 임시 배열이 무한대가 아니고 && // 시작 임시 배열의 가중치가 도착지의 가중치보다 작다면 if (disArr[u] != INF && disArr[u] + weight < disArr[v]) { disArr[v] = disArr[u] + weight; // 임시배열을 업데이트 해준다. // K가 N-1일때 모든 정점을 확인한 후 // K가 N일때 업데이트가 있다면 음의 순환이 있다는 이야기 if (k == N) { cout << "-1"; return 0; } } } } } for (int i = 2; i <= N; i++) { if (disArr[i] == INF) cout << "-1" << "\n"; else cout << disArr[i] << "\n"; } return 0; }
Dijkstra 알고리즘 vs Bellman–Ford 알고리즘
1. 가중치의 차이
예전에 정리한 Dijkstra 도 Bellman–Ford 와 같은 최단 경로 알고리즘입니다.
하지만 Dijkstra 알고리즘은 가중치가 양수일 때만 사용 가능하다는 중요한 특징을 가지고 있습니다.
따라서 가중치가 전부 양수라면 Dijkstra 알고리즘을 사용하고
하나라도 음수의 가중치가 있다면
Bellman–Ford 알고리즘을 사용해야 합니다.
위 : Dijkstra / 아래 Bellman–Ford (.gif)
2. 시간 복잡도의 차이
V는 꼭짓점의 수이고 E는 간선의 개수라고 한다면
Dijkstra 알고리즘은 O(E*logV)의 시간 복잡도를 가지고
Bellman-Ford 알고리즘은 O(V*E)의 시간 복잡도를 가집니다.
따라서 음의 가중치가 없다면 Bellman-Ford 알고리즘보다는
Dijkstra 알고리즘을 이용하여 해결하는 것이 더 좋은 방법입니다.
(Dijkstra 알고리즘은 우선순위 대기열을 사용하여 처리해야 하는 정점을 유지하는 반면,
Bellman-Ford 알고리즘은 가장 짧은 경로를 찾기 위해 모든 정점에 대해 반복합니다.)
알고리즘 – 벨만-포드 (Bellman–Ford Algorithm) 알고리즘 정리
백준 1738번 (골목길, C++, Bellman–Ford) / 추가 반례 [BAEKJOON]
백준 1865번 (웜홀, C++, Bellman–Ford) [BAEKJOON]
백준 1219번 (오민식의 고민, C++, Bellman–Ford) [BAEKJOON]
백준 11657번 (타임머신, C++, Bellman–Ford) [BAEKJOON]
https://www.acmicpc.net/problemset?sort=ac_desc&algo=10 <- 백준 Bellman-Ford 알고리즘 문제