벨만-포드 알고리즘

2017. 9. 20. 18:06[개발] 지식/알고리즘 문제풀이

# 벨만-포드 최단 경로 알고리즘

다익스트라 알고리즘과 같은 단일 시작점 최단 경로 알고리즘이지만, 음수 간선이 있는 그래프에 대해서도 최단 경로를 찾을 수 있으며 그래프에 음수 사이클이 있어 최단거리가 제대로 정의되지 않은 경우도 찾을 수 있다.

수행 과정에서 각 정점까지의 최단 거리 상한을 담은 배열 upper[]를 유지한다. 이 값은 알고리즘이 진행됨에 따라 점점 감소하고, 최종적으로 각 정점까지의 최단거리를 담게 된다.

# 동작 과정

알고리즘이 시작할 때 알 수 있는 사실은 시작점 -> 시작점까지의 최단거리가 0이라는 것 뿐이다. 따라서 upper[s] = 0 으로 초기화하고, 나머지 원소들은 아주 큰 수(무한으로 상정되는) 로 초기화 한다.

이 예측 값을 실제 최단 거리에 더 가깝게 갱신하기 위해 아래와 같은 최단 거리의 특성을 이용한다. 

dist[v] <= dist[u] + w(u,v)

시작점 s에서 v까지 가는 최단거리는, s에서 u까지 가는 최단거리에 u->v 가중치를 더한 값보다 클 수 없다는 의미이다. 만약 전자가 더 크다면 그보다 작은 값이 있다는 것이기에 최단거리가 될 수없으므로 모순이다. 

# 종료 조건과 정당성 증명

S -> a -> b -> c -> u 라는 최단거리가 있다고 가정하자. upper[]가 실제 최단 거리와 같음이 보장된 정점은 대문자로 표시했다.
이제 모든 간선을 순회하면서 한 번식 완화를 시도하면 s -> a에 대한 '완화'도 이루어진다. 따라서

upper[a] <= upper[s] + w(s, a)가 항상 성립한다.

여기서 upper[s]는 0이므로 

upper[a] <= w(s, a) 가 된다.

w(s, a)는 s -> a 에서의 최단거리여야 한다. 이보다 짧은 경로가 있다면 애초에 S -> a -> b -> c -> u가 최단경로라는 사실이 거짓이 되기 때문이다. 따라서 upper[a] = w(s, a) 이며 이로써 s -> a의 최단 거리를 찾았음을 알 수 있다.

이 과정을 모든 간선에 대해 한번씩 반복하면 최종적으로 upper[u] = dist[u]가 됨을 알 수 있다. 이를 일반화하면 모든 간선에 대해 완화를 시도하는 작업을 x번 반복하면 x개 이하의 간선을 사용하는 최단 경로들은 전부 찾을 수 있다는 말이 된다. 따라서 모든 간선이 전부 완화가 실패할 때까지 반복하면 모든 최단 경로를 찾을 수 있다.

음수 사이클이 없는 경우 최단 경로가 한 정점을 두 번 지나는 일이 없으므로, 최대 V개의 정점이 있을 경우 V-1개의 간선을 가질 수 있다. 따라서 모든 간선에 대한 완화 과정은 V-1번 반복하면 된다.

# 음수 사이클

음수 사이클의 존재를 알려면 결과적으로 모든 간선에 대한 완화 과정을 V-1 이 아닌 V번 반복하면 된다. 그래프에 음수 사이클이 없다면 V-1째 반복까지 모든 최단거리를 찾아낼 수 있으므로 마지막 V번째 바놉ㄱ에서는 완화과정이 한번도 발생하지 않아야 한다. 하지만 음수 사이클이 있다면 V번째 반복에서도 최소 한 번은 성공하므로 이것을 이용하면 된다.

예를 들어 s -> a -> b -> s 라는 전체 가중치가 -1인 음수 사이클이 있다. 이 때 세 번 완화한 후 완화가 전부 실패한다고 가정해보자 (최단거리를 전부 찾았다고 가정). 그렇다면 아래와 같은 세 개의 부등식이 성립할 것이다.

upper[a] <= upper[s] + 10

upper[b] <= upper[a] -21

upper[s] <= upper[b] + 10

부등식들의 좌변과 우변을 각각 더하면 아래와 같다.

upper[a] + upper[b] + upper[s] <= upper[s] + upper[a] + upper[b] - 1

중복된 항을 지우면 0 <= -1 이므로 모순임을 알 수 있다.
따라서 V번째 반복에서 음수사이클은 항상 완화가 최소 1번 이상 성공한다.

# Template

알고리즘의 기본적인 구현은 아래와 같다.

package swcertificate;

import java.util.ArrayList;
import java.util.List;

public class Interstellar2 {
	
	public static class Pair{
		
		public int linkedNode;
		public int weight;
		public Pair(int linkedNode, int weight) {
			this.linkedNode = linkedNode;
			this.weight = weight;
		}
		
	}
	
	// 정점의 수
	public static int V;	
	// 각 정점에서의 간선 리스트
	public static List<Pair>[] adj;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		for(int i=0; i<V; i++){
			// 각 정점에 연결된 간선을 전부 add한다.
			adj[i] = new ArrayList<Pair>();
			int n = 2;	// 연결딘 노드
			int w = 10;	// 가중치
			Pair pair = new Pair(n, w);
			
			//TODO 각 노드에 간선들 추가
			
		}
		
		List<Integer> result = bellmanFord(0);
		

	}
	
	public static List<Integer> bellmanFord(int src){
		
		// 시작점을 제외한 모든 정점가지의 최단 거리 상한을 INF로 둔다.
		List<Integer> upper = new ArrayList<Integer>();
		for(int i=0; i<V; i++){
			upper.add(Integer.MAX_VALUE);
		}
		upper.set(src, 0);
		boolean updated = false;
		
		// V번 순회한다.
		for(int i=0; i<V; i++){
			updated = false;
			for(int here=0; here<V; here++){
				for(int j=0; j<adj[here].size(); j++){
					int there = adj[here].get(j).linkedNode;
					int cost = adj[here].get(j).weight;
					
					// (here, there) 간선을 따라 완화를 시도한다.
					if(upper.get(there) > upper.get(here) + cost){
						// 성공
						upper.set(there, upper.get(here) + cost);
						updated = true;
					}
				}
				
				// 모든 간선에 대해 완화가 실패했을 경우 V-1번도 돌 필요 없이 곧장 종료.
				if(!updated) break;
			}
			
			
		}
		
		// V번째 순회에서도 완화가 성공했다면 음수 사이클이 있다.
		if(updated) upper.clear();
		return upper;
		
	}

}

각 반복마다 모든 간선을 순회하므로 O(|E|)수행되고, 전체적으로 V번 반복되므로 전체 시간복잡도는 O(|V||E|) 이다.

# 실제 경로 계산

각 정점을 마지막으로 완화시킨 간선들을 모드면 스패닝 트리를 얻을 수 있다. 각 정점에서부터 스패닝 트리의 루트인 시작점까지 거슬러 올라가는 경로는 항상 시작점에서 해당 경로까지의 최단 경로이다.

# 빠지기 쉬운 함정

s -> u 까지 경로의 존재 자체를 확인하고 싶다면, 알고리즘 종료 후 upper[u] 가 초기값(무한)만 아니면 경로가 존재한다고 판단하는 오류를 범하기 쉽다. 이는 음수 가중치가 있을 수 있다는 사실을 간과했기 때문이다.

따라서 u로 가는 경로가 존재하는지 확인하려면 적당히 큰 값 M에 대해 upper[u] < INF - M 임을 확인해야 한다.


* 알고리즘 문제해결 전략 Vol2 기반으로 작성됨

'[개발] 지식 > 알고리즘 문제풀이' 카테고리의 다른 글

[문제] 감시 카메라 설치 (GALLERY)  (0) 2017.11.11
[문제] SORTGAME  (0) 2017.10.13
[DFS] 오일러 서킷  (1) 2017.08.20
[DFS] 문제 : 고대어 사전  (0) 2017.08.07
[DFS] DFS  (0) 2017.08.07
<