Dot Algo∙ DS/알고리즘 개념
[알고리즘/ 그래프] 벨만 포드 알고리즘 (Bellman-Ford Algorithm) (Java)
루지
2021. 4. 25. 18:12
최단경로 알고리즘
최단 경로 문제는 다음과 같이 분류할 수 있다.
- 모든 간선이 양수인 경우
- 음수 간선이 있는 경우
- 음수 간선 순환은 없는 경우
- 음수 간선 순환이 있는 경우
위의 분류를 3가지 알고리즘을 통해 구할 수 있다.
- 벨만포드 알고리즘 (Bellman-Ford Algorithm) → 2-1),2-2) 음수 간선이 있는 경우
- 다익스트라 알고리즘(Dijkstra Algorithm) → 1)모든 간선 양수
- 플로이드 와샬 알고리즘(Floyd-Warshall Algorithm) → 1)모든 간선 양수
벨만포드 알고리즘
벨만포드 최단 경로 알고리즘은 음수 간선이 포함된 상황에서의 최단 거리 문제를 풀 때 사용할 수 있다. 또한, 음수 간선의 순환을 감지할 수 있다.
동작 과정
- 출발 노드를 설정한다. (to, from, cost)
- 최단 거리 테이블을 초기화한다. (dist[] : 최단 거리 갱신할 때마다 저장)
- 다음의 과정을 N-1번 반복한다. (for 반복문)
- 전체 간선 E개를 하나씩 확인한다.
- 각 간선을 거쳐 다른 노드로 가는 비용을 계산하여 최단 거리 테이블을 갱신한다.
- N번째 조회 : 만약 음수 간선 순환이 발생하는지 체크하고 싶다면 3번의 과정을 한 번 더 수행한다.
- 최단 거리 테이블이 갱신된다면 음수 간선 순환이 존재 (왜냐하면 음수 간선 순환은 돌면 돌수록 값이 작아지기 때문이다.)
// 현재 간선을 거쳐서 다른 노드로 이동하는 거리가 짧은 경우
if(dist[next] > (dist[cur] + cost)) {
dist[next] = dist[cur] + cost;
// n번째 라운드에서 값이 갱신된다면 음수 순환 존재
if (i == n-1) {
return true;
}
}
벨만 포드 vs 다익스트라
다익스트라
- 매번 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택한다.
- 음수 간선이 없다면 최적의 해를 찾을 수 있다.
벨만 포드
- 매번 모든 간선을 전부 확인한다.
- 따라서 다익스트라 알고리즘에서의 최적의 해를 항상 포함한다.
- 다익스트라 알고리즘에 비해서 시간이 오래 걸리지만 음수 간선 순환을 탐지할 수 있다.
벨만포드 알고리즘에서는 다익스트라에서 할 수 없었던 음의 가중치도 계산 할 수 있도록 한 방식이지만 다익스트라보다 시간 복잡도가 높기에(더 느리기에) 어떤 상황에서 이용해야 할 지 잘 생각하여 써야한다.
- 다익스트라 기본 시간 복잡도는 O(|E||log|V|), 벨만 포드의 기본 시간 복잡도는 O(VE)이다.
다익스트라 코드
public class DijkstraExample {
static List<Node>[] list;
static int[] dp;
static boolean[] check;
public static void main(String[] args) {
int[][] a = {{1,2,2}, {1,4,1}, {1,3,5}, {2,3,3}, {2,4,2},
{3,4,3}, {3,5,1}, {4,5,1}, {5,6,2}, {3,6,5}};
int n =6;
solution(n,a);
}
// Dijkstra 배열 초기화
static void solution(int n , int maps[][]) {
check = new boolean[n+1];
dp = new int[n+1];
list = new ArrayList[n+1];
for(int i=1; i<n+1; i++ ) {
list[i] = new ArrayList<>();
}
for(int i=0; i<maps.length; i++) {
int a = maps[i][0];
int b = maps[i][1];
int c = maps[i][2];
list[a].add(new Node(b,c));
list[b].add(new Node(a,c));
}
dijkstra(1);
for(int num : dp) {
System.out.print(num +" ");
}
}
// Dijktstra 알고리즘 (우선순위 큐(Heap)을 이용한 알고리즘)
static void dijkstra(int start) {
Queue<Node> q = new PriorityQueue<>();
Arrays.fill(dp, Integer.MAX_VALUE);
q.add(new Node(start,0));
dp[start] = 0;
while(!q.isEmpty()) {
Node node = q.poll();
int to = node.to;
if(check[to]) continue;
else check[to] = true;
for(Node nxt : list[to]) {
if(dp[nxt.to] >= dp[to] + nxt.weight) {
dp[nxt.to] = dp[to] + nxt.weight;
q.add(new Node(nxt.to, dp[nxt.to]));
}
}
}
}
}
class Node implements Comparable<Node>{
int to;
int weight;
Node(int to, int weight){
this.to = to;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
return Integer.compare(this.weight, o.weight);
}
}
벨만포드 코드
// BOJ #11657 graph 타임머신 (벨만포드)
import java.io.*;
import java.util.StringTokenizer;
class Bus{
int u;
int v;
int val;
public Bus(int u,int v, int val) {
this.u = u;
this.v = v;
this.val = val;
}
}
public class TimeMachine {
static int n,m;
static Bus[] e;
static long[] dist;
static int INF = Integer.MAX_VALUE;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
m = Integer.parseInt(st.nextToken());
e = new Bus[m];
// 1. 출발 노드 설정
for(int i=0; i<m; i++) {
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
int val = Integer.parseInt(st.nextToken());
e[i] = new Bus(u,v,val);
}
// 2. 최단거리 테이블 초기화
dist = new long[n+1];
for(int i=1; i<n+1; i++) {
dist[i] = INF;
}
// 벨만포드 알고리즘 실행 (true: 음수 순환 존재, false: 음수 순환 존재x)
if(bellmanford(1)) { // 음수 순환 존재하면 -1 출력
System.out.println(-1);
}
else {
// 1번 노드를 제외한 다른 모든 노드로 가기 위한 최단거리 출력
for(int i=2; i<n+1; i++) {
if(dist[i] == INF) {// 도달할 수 없으면 -1
System.out.println("-1");
}
else { // 최단 거리 출력
System.out.println(dist[i]);
}
}
}
}
static boolean bellmanford(int start){
dist[start] = 0;
// n번 반복 (음수 간선 순환 체크안하려면 n-1번 반복)
for(int i=0; i<n; i++) {
// 매 반복마다 모든 간선을 확인
for(int j=0; j<m; j++) {
int cur = e[j].u;
int next = e[j].v;
int cost = e[j].val;
if(dist[e[j].u] == INF)
continue;
// 현재 간선을 거쳐서 다른 노드로 이동하는 거리가 짧은 경우
if(dist[next] > (dist[cur] + cost)) {
dist[next] = dist[cur] + cost;
// n번째 라운드에서 값이 갱신된다면 음수 순환 존재
if (i == n-1) {
return true;
}
}
}
}
return false;
}
}