Bellman-Ford 알고리즘
흔히 그래프에서 최단 거리를 구하는 알고리즘은 다익스트라를 떠올리기 쉽다. 하지만 다익스트라는 음수 가중치가 있는 경우에는 사용이 불가능하다.
다익스트라에 대한 자세한 설명과 과정들은 다음 링크에서 볼 수 있다.
다익스트라, Dijkstra Algorithm
코딩 문제에서 많이 볼 수 있는 그래프와 관련된 알고리즘을 공부해보자. 그래프에서 최단 거리를 구하는 알고리즘은 다음과 같은 것들이 있다. 💡 그래프의 최단 거리 구하는 알고리즘 1. 다익
rue-mi.tistory.com
그럼 음수 가중치가 있는 그래프에 다익스트라를 적용하면 어떻게 되는지 한 번 해보자.
음수 가중치에 다익스트라?
다음 예시를 보자.
위 그래프에 다익스트라를 적용해보자. 시작점을 1로 하여 다른 모든 노드로의 최단 거리를 다익스트라를 활용해 구해보자.
시간/노드 번호 | 1 | 2 | 3 | 4 | PQ (방문할 노드, 비용) |
0 | 0 | 무한 | 무한 | 무한 | (2, 1) |
1 | 0 | 1 | 무한 | 무한 | (3, 2) |
2 | 0 | 1 | 2 | 무한 | (4, 4) |
3 | 0 | 1 | 2 | 4 | - |
노드 4에서 2로 가는 가중치가 -5인 간선이 있지만, 노드 2는 이미 방문했기 때문에 이를 고려하지 않게 된다. 사실 다익스트라 알고리즘은 모든 가중치가 ‘양수’라는 가정이 있기 때문에 이가 가능하다. 이미 방문한 노드에 대해서 한 번 더 방문한다는 것은, 양의 가중치만 있다면 무조건 비용이 더 커지기 때문에 끊어버린 것이다.
하지만 이 상황처럼 음의 가중치가 존재한다면, 해당 간선을 통해 가는 게 비용적으로는 훨씬 이득이다. 즉, 다익스트라의 결과로 나온 1→2의 최단 경로는 1이지만, 실제로는 마이너스 무한대가 된다.
이러한 다익스트라의 문제를 해결하기 위해 나온 것이 밸만 포드 알고리즘이다.
Bellman-Ford, 밸만 포드 알고리즘
🔎 Bellman-Ford, 밸만 포드
한 노드에서 다른 모든 노드까지의 최단거리를 구하는 일고리즘으로, 다익스트라와 달리 가중치에 음수가 있어도 사용이 가능하다.
시간 복잡도 : O(NE) (N : 노드의 수, E : 엣지의 수)
알고리즘 동작 과정
- 출발 노드를 설정한다.
- 최단 거리 테이블을 초기화한다 (dist[] : 무한대로 초기화)
- 다음의 과정을 N-1번 반복한다.
- 전체 간선 E개를 하나씩 확인한다
- 각 간선을 거쳐 다른 노드로 가는 비용을 계산하여, 최단 거리 테이블을 갱신한다.
- 하지만 여기서 음수 간선의 순환이 발생하는지 체크하고 싶다면, 3번 과정을 한 번 더 수행한다.
- 만약 최단 거리 테이블이 갱신된다면, 음수 간선 순환이 존재하는 것이다. 음수 간선 순환은 돌면 돌수록 값이 작아지기 때문이다.
🔎 음수 순환 간선이란, 간선의 비용이 음수일 경우 해당 간선을 계속 지나가면 이어진 다른 노드로 가는 비용이 무한대로 작아지는 경우를 말한다.
코드
다음 문제를 하나 풀어보면서 필요한 자료구조를 정리해보자
11657번: 타임머신
첫째 줄에 도시의 개수 N (1 ≤ N ≤ 500), 버스 노선의 개수 M (1 ≤ M ≤ 6,000)이 주어진다. 둘째 줄부터 M개의 줄에는 버스 노선의 정보 A, B, C (1 ≤ A, B ≤ N, -10,000 ≤ C ≤ 10,000)가 주어진다.
www.acmicpc.net
필요한 자료구조는 다음과 같이 두 개이다.
1. Edge를 저장한 배열 - (시작, 끝, 비용) 정보를 담는 class 배열
static class Edge {
int start;
int end;
int cost;
public Edge(int start, int end, int cost){
this.start = start;
this.end = end;
this.cost = cost;
}
}
2. dist를 저장하는 배열 - N * M * cost 크기에 따라 자료형 결정
static long[] dist;
현재 문제에는 500 * 6000 * 10000이므로 long으로 결정!
위 두 개의 자료구조를 활용해 문제를 해결하면 다음과 같이 해결할 수 있다.
package ps.ㄱSolving;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public class BOJ_G4_11657_타임머신 {
static class Edge {
int start;
int end;
int cost;
public Edge(int start, int end, int cost){
this.start = start;
this.end = end;
this.cost = cost;
}
}
static int N, M;
static List<Edge> graph;
static long[] dist;
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
//음수 순환되면 첫째줄에 -1
//아니면 N개의 줄에 각각 도시로 가는 시간을 순서대로 출력. 해당 도시로 가는 경로가 없다면 -1 출력.
StringTokenizer st = new StringTokenizer(in.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
graph = new ArrayList<>();
for (int i = 0; i < M ;i++){
st = new StringTokenizer(in.readLine());
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
int cost = Integer.parseInt(st.nextToken());
graph.add(new Edge(start, end, cost));
}
//밸만포드
dist = new long[N+1];
Arrays.fill(dist, INF);
dist[1] = 0;
if (bellmanFord()){
System.out.println(-1);
}
else{
StringBuilder sb = new StringBuilder();
for (int i = 2; i < N+1; i++){
sb.append((dist[i] == INF)? -1 : dist[i]).append("\\n");
}
System.out.println(sb);
}
}
static Long INF = Long.MAX_VALUE - 10001;
private static boolean bellmanFord() {
//N-1번 반복
for (int i = 0; i < N-1; i++){
//모든 간선을 다 보면서 dist 업데이트
for (Edge e : graph){
if (dist[e.start] == INF) continue;
if (dist[e.end] > dist[e.start] + e.cost){
dist[e.end] = dist[e.start] + e.cost;
}
}
}
//한 번 반복해서 순환하는지
boolean isCycle = false;
for (Edge e : graph){
if (dist[e.start] == INF) continue;
if (dist[e.end] > dist[e.start] + e.cost){
isCycle = true;
}
}
return isCycle;
}
}
다익스트라와 다른 점
- Edge를 (시작점, 끝점, 비용) 형태로 입력 받고, 하나의 배열에 다 저장한다
- 해당 배열을 계속 반복 순회하면서 비용을 갱신한다
- 음수 순환이 될 수도 있다. 해당 경우에는 최단 거리가 무한대로 작아진다.
참고자료
[JAVA] 벨만포드 알고리즘 (Bellman-Ford)
Bellman-Ford 알고리즘 이란? 한 노드에서 다른 모든 노드까지의 최단거리를 구하는 알고리즘 + 음수 사이클 존재 여부를 알 수 있음 다익스트라와는 달리 가중치에 음수가 있어도 사용 가능 시간복
chb2005.tistory.com
[알고리즘/ 그래프] 벨만 포드 알고리즘 (Bellman-Ford Algorithm) (Java)
최단경로 알고리즘 최단 경로 문제는 다음과 같이 분류할 수 있다. 모든 간선이 양수인 경우 음수 간선이 있는 경우 음수 간선 순환은 없는 경우 음수 간선 순환이 있는 경우 위의 분류를 3가지
loosie.tistory.com