포스트

[백준] 최단거리 알고리즘 <2 : 벨만 - 포드>

0. 이전 글

최단거리 알고리즘 <1 : 다익스트라 >

이번 글에서는 다익스트라 알고리즘의 개념과 벨만 - 포드 알고리즘의 비교가 있을 예정이니 읽고 오면 좋다.

1. 벨만 - 포드 알고리즘의 소개

벨만 - 포드 알고리즘(Bellman-Ford Algorithm) 은 그래프에서 최단거리를 찾을 때 사용하는 알고리즘 중 하나이다. 특히 간선의 가중치가 음수가 되는 경우에도 사용할 수 있기 때문에 유용하다.

2. 다익스트라 vs 벨만 - 포드

그래프에서의 최단 거리 알고리즘을 사용한다면 역시 다익스트라 알고리즘과의 비교는 빼놓을 수 없다.

이전에 서술했듯이 다익스트라와 벨만 - 포드의 가장 큰 차이점은 음수인 간선이 존재하는 상태에서 풀 수 있느냐 없느냐로 나뉜다.

그림을 통해 다익스트라 알고리즘에서 음수인 간선이 있을 경우를 설명해보자.

해당 그래프에서 1 노드 출발, 3 노드 도착의 경우 최소경로는

1 → 2 → 3 의 가중치 0의 경우이다.

하지만 이 그래프를 다익스트라 알고리즘으로 풀 경우 1에서 이동할 수 있는 2와 3의 경우에서 반드시 가중치가 작은 3을 선택하기 때문에 1 → 3 의 가중치 5의 경우로 끝나버린다.

벨만 - 포드 알고리즘의 경우 한 간선을 이동해 가중치를 계산할때마다 반복문을 통해 이동 가능한 모든 간선을 확인하기 때문에 다익스트라에 비해 시간은 오래 걸리지만 음수의 간선이 있을 경우도 확실하게 확인할 수 있다.

3. 벨만 - 포드 알고리즘 메커니즘

  1. 출발 노드를 설정한다.
  2. 최단거리 배열을 설정하고 inf 값으로 초기화 한 후 출발 노드 값을 0으로 설정한다.
  3. 모든 간선에 대해 확인하면서 각 노드로 갈 수 있는 최단 거리를 갱신해준다.
  4. 3의 과정을 (노드의 갯수 - 1) 회 만큼 반복해준다.
  5. 마지막 반복에서 최단거리가 갱신된다면 음의 순환이 있는 것으로 간주해 종료한다.

여기서 말하는 음의 순환이라는 것은 무엇일까? 그림으로 설명해보자.

해당 그래프에서 1 에서 5로 간다고 해보자.

이때 2 → 3 → 4 를 순환하는 과정에서 총 가중치가 -1이 되도록 끊임없이 반복할 수 있기 때문에 5로 가는 전체 가중치가 -inf 값이 나온다.

그래프에선 아무리 많이 이동한다고 해도 최단 거리로 이동하기 위해선 최대 노드의 숫자-1 회 만큼 움직일 수 있다. 그렇기에 4의 과정에서 마지막 횟수에 최단거리 갱신이 일어난다면 그것은 음의 순환에 빠진 것이다.

그림의 예시로 벨만 - 포드 알고리즘의 작동 방식을 살펴보자.

  1. 최단 경로 리스트를 inf로 초기화 한 후 출발지점을 0으로 설정한다. (이때 0은 더미 인덱스이다.)

    012345
    inf0infinfinfinf
  2. 1에서 이동할 수 있는 모든 노드(2, 3, 4) 에 대한 거리를 계산 후 테이블에 적용한다.

    012345
    inf010560inf
  3. 다음번 이동 가능한 노드들에 대해서도 전부 확인한다. a. 1 → 2의 경우 10만큼 이동 후 2 → 3 으로 이동하는데 -30이 걸리므로 3은 -20이 된다. 이때 3의 최단경로는 5이기 때문에 -20이 더 작으므로 갱신 가능하다. b. 1 → 3의 경우 5의 가중치에 4 와 5로 이동 가능하므로 1 → 3 → 4의 경우 합 25, 1 → 3 → 5 의 경우 합 5 이다. 둘 다 현재 테이블의 최단경로보다 작으므로 갱신 가능하다. c. 1 → 4의 경우 현재 60의 가중치에 5로 이동하는 -10이 소모되어 50의 가중치로 이동 가능하다.

    이때 a b c 중 무엇이 먼저 일어나든 항상 최소값만 선택하기 때문에 상관이 없다.

    코드 상에서는 모든 노드에 대해서 확인하기 때문에 5번 노드 에서도 이동이 이루어진다. 하지만 5번 노드는 아직 inf 인 상태이기 때문에 5번 노드에서 이동할 수 있는 노드들은 갱신이 이루어지지 않는다. (inf가 inf 보다 작아지지 않으므로)

    012345
    inf010-20255
  4. 해당 과정을 노드의 갯수 - 1 회 만큼 반복해준다.

    012345
    inf010-200-20

4. 코드

11657번: 타임머신

기본적인 벨만 - 포드 알고리즘 사용을 테스트하는 문제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import sys
import math

read = sys.stdin.readline
node_count, edge_count = map(int,read().split())
graph = [[] for _ in range(node_count + 1)]
distance = [math.inf for _ in range(node_count + 1)]
infinit_cycle = False # 음의 순환을 감지하는 역할

for _ in range(edge_count):
    u, v, w = map(int,read().split())

    graph[u].append((v, w)) # 그래프의 인덱스에 해당 노드에서 이동 가능한 노드들을 입력

def bellman_ford(start):
    global infinit_cycle
    distance[start] = 0 # 출발 노드 0으로 초기화

    for i in range(node_count): # 사이클 반복
        for j in range(1,node_count+1): # 그래프 상의 모든 간선에 대해 확인한다.
            for way in graph[j]:
                desti = way[0]
                weight = way[1]
                if distance[desti] > distance[j] + weight: # 계산 결과가 기존 가중치보다 작은 경우
                    if i == node_count-1: # 마지막 cycle에서 갱신이 이루어지는 경우
                        infinit_cycle = True
                        break
                    distance[desti] = distance[j] + weight # 최단거리 그래프를 갱신
   
bellman_ford(1)

for answer in distance[2:]:
    if infinit_cycle: # 음의 사이클이 있다면 -1 출력
        print(-1)
        break
    else:
        if answer > 1e9:
            print(-1)
        else:
            print(answer)


이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.