벨만-포드 알고리즘이란 다익스트라 알고리즘과 같이 한 노드에서 다른 노드까지의 최단 거리를 구하는 알고리즘이다
다익스트라 알고리즘과 달리 음의 가중치가 있을 때도 사용할 수 있는 알고리즘이다
위 그림에서 1번 노드에서 4번 노드로 가는 최단 거리를 구해보자
1번에서 4번으로 가는 길은 3가지가 존재한다
1 -> 3 -> 4
: 20 + (-15) = 51 -> 4
= 151 -> 2 -> 4
: 10 + 10 = 20바로 확인할 수 있듯이 1->3->4
의 경로가 최단 경로이다. 다익스트라의 경우는 1번 정점에서 2, 3, 4번으로 가는 경로를 구하면서 dist[2] = 10
, dist[3] = 20
, dist[4] = 15
으로 dist
를 업데이트 한다
그리고 다음으로 비용이 가장 작은 1->2
를 선택하고 4번 노드를 탐색 후 dist[4]
를 업데이트하는데 1->2->4
는 20이고 1->4
는 15이므로 dist[4]
를 그대로 15로 둔다
4번 노드로 가는 경로가 우선순위 큐에 push
되고 이제 현재 노드가 목적지니까 바로 결과를 출력하게 된다
결국 1->3->4
경로는 고려도 하지 못하게 됨
하지만 벨만-포드 알고리즘을 사용하면 모든 간선을 확인하므로 4번 노드로 가는 최단 경로를 업데이트 할 수 있다
시간 복잡도는 필요한 간선만 찾는 다익스트라와 달리(O(ElogV)
) 모든 간선을 찾으니까(O(VE)
) 벨만-포드가 더 소요된다
벨만-포드 알고리즘의 수행 과정은 다음과 같다
V - 1
번 반복하면서, 현재 간선이 최단 거리를 갱신할 수 있다면 최단 거리를 업데이트 한다V - 1
번의 반복이 끝나고, 한번 더 모든 간선을 확인하여 최단 거리 갱신이 일어난다면 음수 사이클이 존재하는 것으로 판단함그래프에서 사이클이 발생할 때 경로가 큰 음수라면 계속 순환하면서 최단 거리 테이블이 음의 무한대로 될 수 있다
위의 그림을 토대로 구현해본 결과이다. 자세한 내용은 주석에 있으니 참고
#include <bits/stdc++.h>
using namespace std;
// 간선을 나타내는 구조체
struct Edge
{
int u, v, weight;
};
int V; // 정점 개수
int E; // 간선 개수
int start; // 시작 정점
vector<Edge> edges; // 모든 간선
int dist[10]; // 최단 경로
void BellmanFord(int start)
{
dist[start] = 0;
// 모든 간선을 V - 1번 반복
for (int i = 0; i < V - 1; i++)
{
for (int j = 0; j < E; j++)
{
int u = edges[j].u;
int v = edges[j].v;
int weight = edges[j].weight;
// 현재 정점의 거리가 무한대가 아니고 현재 정점에서 다음 정점으로
// 가는 거리의 합이 다음 정점의 최단 경로보다 작다면 업데이트
if (dist[u] != INT_MAX && dist[u] + weight < dist[v])
{
dist[v] = dist[u] + weight;
}
}
}
// 음수 사이클 확인
for (int j = 0; j < E; j++)
{
int u = edges[j].u;
int v = edges[j].v;
int weight = edges[j].weight;
// 최단 거리 갱신이 발생한다는 것은 음수 사이클이 존재함을 의미
if (dist[u] != INT_MAX && dist[u] + weight < dist[v])
{
cout << u << "->" << v << "에서 사이클 발생\n";
return;
}
}
cout << start << "번 정점부터 최단 경로 출력\n";
// 출력
for (int i = 1; i <= V; i++)
{
if(dist[i] == INT_MAX)
cout << i << "까지 도달불가" << '\n';
else
cout << i << "까지 " << dist[i] << '\n';
}
}
int main()
{
cin >> V >> E;
for (int i = 0; i < E; i++)
{
int u, v, weight;
cin >> u >> v >> weight;
edges.push_back({ u, v, weight });
}
cin >> start;
fill(dist, dist + 10, INT_MAX);
BellmanFord(start);
}
/*
Input
4
5
1 2 10
1 3 20
1 4 15
2 4 10
3 4 -15
1
Output
1번 정점부터 최단 경로 출력
1까지 0
2까지 10
3까지 20
4까지 5
*/
벨만-포드 알고리즘은 대부분 방향 그래프에서 사용해야 한다고 한다. 무방향이라면 사이클이 계속 생성돼서 그런 것 같다
챌린지 문제로 백준 1865번 : 웜홀이 주어졌는데 엄청 많이 틀렸다
도로는 무방향이므로 양방향 그래프를 추가하고 웜홀도 같이 추가해줬다
그렇게 벨만포드 알고리즘을 돌릴때 원래 나는 INT_MAX
로 dist
를 초기화했는데 이러면 오버플로 위험이 있어 적당히 큰 값으로 초기화했다
이 문제가 진짜 이해하기 힘든 점이 몇몇 있는데 시작점은 여러 곳이 가능한데 문제 풀이할 때 그냥 1을 시작 정점으로 해서 돌려도 정답이더라
이거 관련해서 계속 찾아봐야겠다...