이놈을 사용하는 코테문제
- 본 작물의 backlink를 확인하거나
- #floyd-warshall 태그로 검색해보자.
Problem
- Floyd-Warshall 알고리즘이 풀려고 하는 문제는 “최단경로 계산” 이다.
Condition
- 이 알고리즘을 적용할 수 있는 조건은 “음수 사이클” 이 없어야 한다는 것이다.
- 즉,
A->BB->CC->A에서 각각의 weight가-2,-3,1이면,A->B->C를 한바퀴 돌았을 때-4가 되므로 계속 돌면 음의 무한대로 가게 된다.
- 즉,
Key Idea
- 핵심 아이디어는 전체 노드 집합의 임의의 세 노드 조합
{i, j, k}에 대해,i->j로 가는 것 보다i->k->j로 가는 것이 더 weight가 작은지를 검사해서i->j에 대한 최소 weight를 찾는 것이다.
주의할 점
- 모든
{i, j, k}조합에 대해 검사해야 하므로 3중 loop 를 돌게 코드를 짜면 된다. 다만, outmost loop 이k(즉, 경유 노드) 가 되어야 한다.
틀린 loop 설명
- 만약에
i, j, k의 순서대로 inner loop 를 구성했다고 해보자. 이건 다르게 생각하면, 어떤 출발-도착 조합i->j에 대해, 모든k를 경유노드로 검사하며 최소 weight를 찾는다는 것이다.
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
...
}
}
}- 하지만 이것은 전제가 필요하다:
i->k->j의 최소 weight를 알려면,i->k와k->j의 최소 weight를 알아야 가능하다. - 당연하게도 이건 보장이 안된다. 예를 들어
1->2(i=1, j=2) 의 최소 weight 를 찾기 위해1->3->2(i=1, j=2, k=3) 를 판단하고자 하면,1->3(i=1, j=3) 의 최소 weight가 결정되어있어야 하지만j=3은 아직 처리하지 않은 상태이기 때문에 불가능하다.
올바른 loop 설명
- 하지만 반대로
k, i, j의 순서대로 inner loop 를 구성하면 어떨까?
for (int k = 0; k < N; k++) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
...
}
}
}- 이건 이렇게 생각할 수 있다: 어떤 경유노드
k에 대해, 이 노드를 지나가는 모든 경로의 weight를 업데이트하는 것이다. - 이건 위 처럼 생각하면 이렇게 된다: 이때도
i->k->j의 weight를 계산하지만,i->k와k->j의 최소 weight가 확정되어있어야 할 필요는 없다. - 왜냐면 이 접근법은
i->j에 대한 최소 weight를 “확정”하는 것이 아닌,k를 거쳐갈 때의 최소 weight를 “반영”하는 것에 불과하기 때문이다.
flowchart LR
i((i))
k((k))
j((j))
kp((k'))
i -- 3 --> k -- 3 --> j
i -- 7 --> j
k -- 1 --> kp -- 1 --> j
- 가령 위의 그래프를 생각해 보자:
- 경유노드를
k,k'순서로 잡아 처리하는 경우에 어떻게i->j의 최소값을 구하는지 보자. - 우선
k를 경유노드로 정하는 경우에i->j인 7보다i->k->j인 6이 더 weight가 적기 때문에i->j의 최소 weight는 6으로 업데이트된다.- 아직
k'를 경유노드로 삼았을 때를 처리하지 않았기 때문에,k->j의 최소 weight는 아직까지는 3이다. 그래서 이 된다.
- 아직
- 하지만
k->j의 “최소” weight는 3이 아니라 2이다. 이건k를 처리한 후k'를 경유노드로 지정했을 때 반영된다.- 우선,
k가 경유노드일 때의 weight는 전부 반영이 되어 있기 때문에,i->k'의 최소 weight는 4로 계산되어 있다. - 따라서
k'를 경유노드로 잡았을 때i->k'의 최소가 4이고,k'->j의 최소는 1이기 때문에,i->j의 최소가 5로 업데이트된다.
- 우선,
- 경유노드를
- 즉, 이렇게 계산하면
i->k->j를 고려할 때i->k혹은k->j의 최소 weight 가 정확하지 않아도 된다. 나중에 다른 경유노드를 계산하며 조정되기 때문이다.