<본 게시글은 권오흠 교수님의 알고리즘 강의을 공부하고 정리한 글 입니다. >
지난 글에 이어서 all to all 최단 경로 알고리즘인 Floyd-warshall 알고리즘을 알아보자.
● 최단 경로
- 한 노드에서 다른 노드까지 이동하는데 드는 비용이 최소인 경로를 찾는 문제
1. Single - source ( one to all ) - Dijkstra 알고리즘, Bellman-Ford 알고리즘 - 하나의 출발 노드에서 모든 노드까지의 최단 경로를 찾는 문제.
2. Single - destination ( all to one ) - 사실상 one to all 이랑 같은 유형.
- 모든 노드에서 하나의 목적 노드까지의 최단 경로를 찾는 문제.
3. Single - pair ( one to one ) - Dijkstra 알고리즘 으로 해결.
- 주어진 한 노드에서 다른 한 목적 노드까지의 최단 경로를 찾는 문제.
- 알려진 알고리즘 중에서 최악의 경우 시간복잡도가 one to all ( Dijkstra) 보다 나은 알고리즘이 없다.
그래서 Dijkstra로 모든 노드 구하다 목적 노드 t가 나오면 search를 멈추는 방법으로 해결.
4. All - pair ( all to all ) - Floyd-warshall 알고리즘.
- 모든 노드에서 모든 노드로 가는 최단 경로를 찾는 문제.
[ Floyd - Warshall ]
- Dijkstra 알고리즘과 여타 그래프 문제랑 마찬가지로 가중치 방향 그래프가 입력으로 주어진다.
( 허나 문제에 따라 graph를 그리지 않아도 될 때가 많다. 백준 11404 문제가 그러하다. )
- 이때, 모든 노드 쌍들간의 최단 경로의 길이를 구하는 문제이다.
- 다익스트라가 가장 적은 비용을 하나씩 선택했다면 플로이드는 '거쳐가는 정점'을 구해가면서 구한다.
- 전형적인 동적 계획법(DP) 전략에 기반한 알고리즘이다.
- d(k) [ i, j ] 는
- 중간 노드 집합 { 1,2,3,4 ... k } 에 속한 노드들만 거쳐서 노드 i 에서 j 까지 가는 최단 경로의 길이이다.
우리가 원하는 목표는
d(n) [ i, j ] = i 부터 j 까지 가는데 중간에 1 ~ n 의 속한 노드를 거쳐서 가는 최단 경로의 길이이다.
그렇다면 다이나믹 프로그래밍 답게 중간 단계인
d(k) [ i, j ] 에 대해서 생각해보자.
d(k) [ i, j ] = i 부터 j 까지 가는데 중간에 1 ~ k 사이에 속한 노드를 거쳐서 가는 경우인데, 이는 두 가지 경우가 있다.
- 노드 K를 지나지 않는 경우 : d(k-1) [ i , j ]
- 노드 K를 지나는 경우 : d(k-1) [ i , k ] + d(k-1) [ k , j ]
노드 K를 지나지 않는 경우는 i 부터 j 까지 가는데 1 ~ k-1 사이에 속한 노드만을 거쳐서 간다는 소리이고
이는 d(k-1) [ i , j ] 이다.
노드 k를 지나는 경우는 i 부터 j 까지 가는데 1 ~ k 사이에 속한 노드만을 거쳐서 간다는 소리이고 그 중 K를 꼭 지나야하니깐 1 부터 k 까지 의 최단 경로인 d(k-1) [ i , k ] 와 k 부터 j까지 최단 경로인 d(k-1) [ k , j ] 의 합이다.
결국 d(k) [ i, j ] 는 노드 K를 지나지 않는 경우와 지나는 경우 둘 중에 작은 값이 된다.
min ( d(k-1) [ i , j ] , d(k-1) [ i , k ] + d(k-1) [ k , j ] )
[ peseudo 코드 ]
위 알고리즘의 시간 복잡도는 자명하게 O(n^3) 이다.
그런데 시간 복잡도가 O(n^2) 인 Dijkstra 알고리즘을 모든 노드 N 에 대해서 실행하게 되면 All to All 이 되는데,
이는 이 경우에도 시간 복잡도가 O(n^3) 이다.
그런 의미에서 위 Floyd-Warshall 알고리즘은 자랑스러운것은 아니다.
또 한 가지,
위 수도 코드를 실제 구현할때는 k,i,j 를 d[k][i][j]의 3차원 배열로 해야하는데,
그렇지 않고아래 처럼 d[i][j]의 2차원 배열로 표현해도 충분하다.
이유 : d(k)[ i, k ] 는 d(k-1)[ i, k ] 랑 같기 때문에 그대로 덮어 씌어도 된다.
이 부분은 아래의 강의를 참조하는게 이해하는데 쉬울것 같다.
https://www.youtube.com/watch?v=5uqOvsVmfJw&list=PL52K_8WQO5oUuH06MLOrah4h05TZ4n38l&index=40
[ 구현 ]
우리의 목표는 d(n)[i,j] 를 구하는 것이다.
1. 초기화
- d[i,j] 를 초기화 해준다. ( i 랑 j 가 같으면 0 으로 아니라면 INF 로)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j)dist[i][j] = 0;
else { dist[i][j] = INF; }
}
}
2. 그래프 입력을 받으면서 서로 연결된 노드가 있다면 거리를 업데이트 해준다.
※ 주의 할 점은 문제에서 같은 노드를 연결하는 동일한 엣지를 다른 비용으로 하나 이상 줄 수도 있다.
이 경우, 가장 작은 값을 dist[i][j] 로 해야한다. --> 백준 11404 문제가 그러하다.
while (m--) {
int a, b, c;
cin >> a >> b >> c;
graph[a].push_back({ b,c });
dist[a][b] = std::min(dist[a][b], c);
}
3. 플로이드 와샬 알고리즘을 사용한다.
void floyd() {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dist[i][j] = std::min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
[ 경로 출력 ]
중간 k 값을 2차원 배열에 저장하여
recursion 으로 앞에서부터 하나씩 출력해주면 된다.
ref :: https://www.youtube.com/watch?v=5uqOvsVmfJw&list=PL52K_8WQO5oUuH06MLOrah4h05TZ4n38l&index=40
'CS > Algorithm' 카테고리의 다른 글
에라토스테네스의 체 (0) | 2021.12.25 |
---|---|
Back Tracking (0) | 2021.11.27 |
최단 경로 & 그래프 문제 유형 (0) | 2021.11.17 |
Kruskal ( Union - Find ) (0) | 2021.11.12 |
MST ( Minimum Spanning Tree ) - Kruskal , Prim (0) | 2021.11.11 |