Bellman-Ford
Bellman-Ford
Bellman-Ford는 음수 간선이 있을 수 있는 그래프에서 한 시작점 최단 거리를 구하는 알고리즘이다.
한 줄로 요약하면 다음과 같다.
모든 간선을 여러 번 완화하면서 최단 거리를 갱신하는 알고리즘
다익스트라와 비교하면 느리지만, 음수 간선과 음수 사이클 판별을 다룰 수 있다는 점이 강점이다.
1. 언제 쓰는가
아래 상황이면 Bellman-Ford를 고려한다.
- 음수 간선이 존재할 수 있음
- 한 시작점 최단 거리 문제
- 음수 사이클 여부까지 판단해야 함
- 다익스트라를 쓰면 안 되는 입력
즉 다음 분기가 핵심이다.
- 음수 간선 없음 -> Dijkstra 우선
- 음수 간선 있음 -> Bellman-Ford 검토
2. 핵심 아이디어
최단 경로가 잘 정의되어 있다면 같은 정점을 반복하지 않는다고 볼 수 있으므로,
간선을 최대 V - 1개만 사용한다.
따라서:
모든 간선을 V - 1번 반복해서 완화하면
최단 거리가 전파된다
여기서 완화(relaxation)란, 더 짧은 경로를 발견했을 때 거리값을 갱신하는 것이다.
flowchart TD
A["V-1번 반복"] --> B["모든 간선 검사"]
B --> C{"더 짧은 경로 발견"}
C -->|Yes| D["거리 갱신"]
C -->|No| E["현재 값 유지"]
D --> B
E --> B
즉 Bellman-Ford는 “가장 가까운 정점 하나를 확정”하는 방식이 아니라, 모든 간선을 반복해서 훑으며 최단 거리 정보가 한 단계씩 퍼져 나가게 만드는 알고리즘이다.
3. 완화가 무슨 뜻인가
간선 (u, v, w)가 있다고 하자.
현재:
dist[u] = 5dist[v] = 20w = 3
이면 u를 거쳐 v로 가는 비용은 8이다.
기존 20보다 짧으므로:
dist[v] = 8;
로 갱신한다.
즉 완화는:
더 짧은 길이 있으면 거리표를 줄이는 작업
이다.
4. 왜 V - 1번이면 충분한가
정점이 V개인 그래프에서,
도달 가능한 음수 사이클이 없다면,
사이클을 사용하지 않는 최단 경로는 최대 V - 1개의 간선만 가진다.
예:
- 정점 5개
- 한 경로가 모든 정점을 한 번씩 지난다고 해도 간선은 최대 4개
즉 1번 완화로 길이 1 경로가,
2번 완화로 길이 2 경로가,
…
V - 1번 완화로 길이 V - 1 경로까지 전파된다.
5. 작은 예시로 따라가기
그래프:
1 -> 2 (4)
1 -> 3 (5)
2 -> 3 (-2)
3 -> 4 (3)
시작점은 1이다.
초기 거리:
dist[1] = 0
dist[2] = INF
dist[3] = INF
dist[4] = INF
1번째 완화 라운드
1 -> 2: dist[2] = 41 -> 3: dist[3] = 52 -> 3: dist[3] = min(5, 4 + -2) = 23 -> 4: dist[4] = 5
결과:
0, 4, 2, 5
2번째 완화 라운드
더 줄어드는 값이 있는지 본다. 이번에는 이미 최단 거리가 모두 전파되어 더 이상 갱신이 없다.
즉 Bellman-Ford는 이렇게 간선을 반복해서 보면서, 최단 경로 정보가 멀리 퍼지게 만든다.
6. 전체 구현
import java.util.*;
class Edge {
int from;
int to;
int cost;
Edge(int from, int to, int cost) {
this.from = from;
this.to = to;
this.cost = cost;
}
}
long[] bellmanFord(int n, List<Edge> edges, int start) {
long INF = 1_000_000_000_000L;
long[] dist = new long[n + 1];
Arrays.fill(dist, INF);
dist[start] = 0;
for (int i = 1; i <= n - 1; i++) {
boolean updated = false;
for (Edge e : edges) {
if (dist[e.from] == INF) continue;
if (dist[e.to] > dist[e.from] + e.cost) {
dist[e.to] = dist[e.from] + e.cost;
updated = true;
}
}
if (!updated) break;
}
return dist;
}
여기서 updated 최적화는 중요하다.
어떤 라운드에서 더 이상 갱신이 없다면,
그 뒤로도 변하지 않으므로 일찍 종료할 수 있다.
7. 음수 사이클 판별
V - 1번 완화가 끝난 뒤에도,
한 번 더 완화를 했을 때 값이 줄어든다면 음수 사이클이 있다.
왜냐하면 정상적인 최단 경로는 이미 다 전파됐어야 하는데, 계속 줄어든다는 것은 음수 사이클을 돌면서 값을 무한히 낮출 수 있다는 뜻이기 때문이다.
boolean hasNegativeCycle(List<Edge> edges, long[] dist, long INF) {
for (Edge e : edges) {
if (dist[e.from] == INF) continue;
if (dist[e.to] > dist[e.from] + e.cost) {
return true;
}
}
return false;
}
여기서 핵심은 "한 번 더"라는 점이다.
V - 1번까지는 정상 최단 경로 전파- 그 다음 한 번은 음수 사이클 존재 여부 확인
즉 마지막 1회는 최단 거리 계산용이 아니라, 그래프의 모순 여부를 검사하는 단계라고 보면 된다.
위 함수의 INF는 앞선 bellmanFord와 같은 상수를 넘겨준다고 생각하면 된다.
8. 주의: 시작점에서 도달 가능한 음수 사이클만 잡힌다
Bellman-Ford에서 dist[e.from] == INF를 건너뛰므로,
시작점에서 갈 수 없는 음수 사이클은 현재 시작점 기준 최단 거리 계산에는 영향을 주지 않는다.
즉 문제에서 묻는 것이:
- 시작점 기준 최단 거리인가
- 그래프 전체의 어떤 음수 사이클이든 존재하는가
에 따라 해석이 달라질 수 있다.
작은 예시를 보자.
- 시작점이 1
- 1과 전혀 연결되지 않은 다른 컴포넌트에 음수 사이클 존재
이 경우 Bellman-Ford는 그 사이클을 현재 시작점 기준 계산에서는 감지하지 못할 수 있다.
즉 "그래프 전체 어디든 음수 사이클 있나"를 묻는 문제와 "시작점에서 영향을 받는 음수 사이클 있나"를 묻는 문제는 다를 수 있다.
이 차이를 문제에서 반드시 확인해야 한다.
그래프 전체 음수 사이클 검사는 super source를 붙이면 된다
만약 문제에서 “그래프 전체 어딘가에 음수 사이클이 있는가”를 묻는다면,
가상의 정점 0을 만들고 모든 정점으로 비용 0 간선을 연결하면 된다.
flowchart TD
S["가상 시작점 0"] --> A["1"]
S --> B["2"]
S --> C["3"]
S --> D["..."]
그다음 0에서 Bellman-Ford를 시작하면
모든 컴포넌트가 도달 가능한 상태가 된다.
구현 감각은 다음과 같다.
for (int v = 1; v <= n; v++) {
edges.add(new Edge(0, v, 0));
}
// 정점 번호 범위가 0..n 이므로 총 정점 수는 n + 1
long[] dist = bellmanFord(n + 1, edges, 0);
즉 시작점 하나의 문제를 “전체 그래프를 다 보는 시작점” 문제로 바꿔 주는 셈이다.
9. 음수 사이클 예시를 손으로 보기
그래프:
1 -> 2 (1)
2 -> 3 (-2)
3 -> 2 (-2)
시작점은 1이라고 하자.
2와 3 사이를 한 번 돌면 비용이:
-2 + -2 = -4
이다.
즉 2와 3을 계속 왕복할수록 거리값을 더 줄일 수 있다.
이 경우 V - 1번 완화 후에도
2, 3의 거리는 한 번 더 줄어들 수 있다.
그래서 Bellman-Ford는 마지막 1회 검사에서 이를 음수 사이클로 판별한다.
음수 사이클 다이어그램
graph LR
1 -->|"1"| 2
2 -->|"-2"| 3
3 -->|"-2"| 2
2 → 3 → 2 경로의 비용 합이 $-2 + (-2) = -4 < 0$ 이므로, 이 사이클을 반복할수록 거리가 무한히 줄어든다.
10. 왜 간선 리스트로 구현하는가
Bellman-Ford는 "모든 간선을 매 라운드마다 한 번씩 본다"가 핵심이다.
그래서 인접 리스트보다는 오히려:
List<Edge> edges
형태가 더 자연스럽다.
다익스트라는 현재 정점에서 나가는 간선만 보면 되므로 인접 리스트가 잘 맞고, Bellman-Ford는 그래프 전체 간선을 반복 스캔하므로 간선 리스트가 잘 맞는다.
11. 시간 복잡도
Bellman-Ford의 시간 복잡도는:
O(VE)
이다.
그래서 정점과 간선이 큰 그래프에서는 느리다. 음수 간선이 없다면 보통 Dijkstra가 훨씬 효율적이다.
12. 언제 Bellman-Ford를 실제로 고르는가
실전에서는 다음 식으로 판단하면 된다.
- 음수 간선이 없다 -> Dijkstra 먼저
- 음수 간선이 있다 -> Bellman-Ford 검토
- 모든 쌍이 필요하고 정점 수가 작다 -> Floyd-Warshall 검토
즉 Bellman-Ford는 가장 빠른 기본 선택은 아니지만, 음수 간선 때문에 다른 선택지가 막힐 때 매우 중요해진다.
13. Dijkstra와 비교
| 항목 | Bellman-Ford | Dijkstra |
|---|---|---|
| 음수 간선 | 가능 | 불가 |
| 음수 사이클 판별 | 가능 | 불가 |
| 속도 | 느림 | 빠름 |
| 핵심 구조 | 모든 간선 반복 완화 | 우선순위 큐 그리디 |
즉 Bellman-Ford는 “느리지만 더 일반적”인 알고리즘이다.
14. Floyd-Warshall과 비교
- Bellman-Ford: 시작점 하나
- Floyd-Warshall: 모든 정점 쌍
즉 둘 다 음수 간선을 다룰 수 있지만, 문제가 요구하는 시작점 범위가 다르다.
15. 자주 하는 실수
1) INF 상태에서 더함
도달하지 못한 정점은 건너뛰어야 한다.
2) V번이 아니라 V - 1번 완화해야 함
최단 경로 간선 수 상한은 V - 1개다.
3) 음수 사이클 판별 단계를 빼먹음
문제가 음수 사이클 존재 여부를 물으면 반드시 한 번 더 확인해야 한다.
4) int overflow
거리 합이 커질 수 있으므로 long이 더 안전하다.
16. 시험장용 최소 암기 버전
Bellman-Ford:
음수 간선 가능한 단일 시작점 최단 거리
핵심:
모든 간선을 V-1번 완화
음수 사이클:
한 번 더 완화했는데 갱신되면 존재
복잡도:
O(VE)
17. 최종 요약
Bellman-Ford는 다음 문장으로 정리할 수 있다.
음수 간선을 허용하는 그래프에서,
모든 간선을 반복 완화해 한 시작점 최단 거리를 구하는 알고리즘
문제를 보면 먼저 이 질문을 하면 된다.
음수 간선이 있을 수 있는가?
그 답이 예라면 Bellman-Ford를 검토해야 한다.
Comments
Sign in with GitHub to join the discussion.