모든 쌍 최단 경로 알고리즘
하나의 시작점과 하나의 도착점의 최단 거리를 구하는 경우 다익스트라 알고리즘을 사용하면 되는데 모든 정점쌍에 대해서 최단 거리를 구해야할 때도 다익스트라를 사용해서 구할 수 있다. 음수 가중치가 있는 경우라면 벨만-포드 알고리즘을 사용하면 된다. 그러나 모든 쌍에 대해 최단 거리를 구하는데에는 이 보다 더 효율적인 방법이 있는 바로 플로이드-와샬 알고리즘이다. 이 알고리즘은 그래프의 모든 정점 쌍의 최단 거리를 저장하는 2차원 배열을 저장한다. 는 정점 에서 정점 까지의 최단 거리를 나타낸다.
임의의 두 정점 와 를 지나는 어떤 경로가 있다고 할 때 이 경로를 지나쳐 가는 정점을 경유점이라고 하자. 이 경로는 와 를 반드시 포함한다. 이외에 와 를 직접 잇는 간선이 없거나 경로상에서 다른 정점을 들러 가는 것이 전체 경로를 더 짧게 만드는 경우도 존재한다. 정점 집합 에 포함된 정점만을 경유점으로 사용해 에서 로 가는 최단 경로를 라고 표현한다면 그림 1의 그래프에서 두 정점을 직접 잇는 경로 이지만 정점 과 를 경유하여 가는 이 된다.
에 포함된 정점 집합만을 이용해 에서 로 가는 최단 경로가 있다고 할 때 최단 경로는 의 임의의 정점 를 경유할 수도 있고 아닐 수도 있다.
-
경로가 를 경유하지 않는다.
- 이 경로는 로 에 포함된 정점들만 경유점으로 사용한다.
-
경로가 를 경유한다.
- 이 경로는 와 의 두 경로로 나뉘며 이 둘은 모두 와 , 와 를 잇는 최단 경로여야 한다.
- 또한 두 개의 경로 모두 에 포함된 정점들만 경유점으로 사용한다.
따라서 에서 로 가는 최단 경로 는 둘 중 더 짧은 것이 된다. 이를 점화식으로 나타내면 다음과 같다.
구현
앞의 점화식을 변형하여 동적 계획법으로 풀이할 수 있는데 라 하고 라고 하면 는 번 정점부터 번 정점까지만 경유점으로 사용했을 때 에서 로가는 최단 경로의 길이가 되고 이 값은 의 값은 에 저장된다. 가 두 정점 와 의 간선의 가중치라고 한다면 아래와 같이 나타낼 수 있다.
이를 이용해 코드로 구현하면 아래와 같이 나타낼 수 있다. 시간 복잡도와 공간 복잡도는 이다.
const V = 4, INF = 987654321;
const A = Array.from(Array(V), () => Array.from(Array(V), () => Array(V).fill(0)));
const data = `
0 3 7 X
X 0 X 9
5 12 0 X
X 1 8 0
`.trim().split('\n').map(d => d.split(' ').map(e => e === 'X' ? INF : parseInt(e)));
for (let i = 0; i < V; i += 1) {
for (let j = 0; j < V; j += 1) {
A[0][i][j] = Math.min(data[i][j], data[i][0] + data[0][j]);
}
}
for (let k = 1; k < V; k += 1) {
for (let i = 0; i < V; i += 1) {
for (let j = 0; j < V; j += 1) {
A[k][i][j] = Math.min(
A[k - 1][i][j],
A[k - 1][i][k] + A[k - 1][k][j]
);
}
}
}
그림 1의 초기 상태 를 인접 배열 형태로 나타내면 그림 2와 같다. 자기 자신으로 가는 간선은 없더라도 최단 거리가 0이고 두 정점을 직접 잇는 간선이 없는 경우는 매우 큰 값으로 설정한다.
의 첫 순회를 마치게 된다면 그림 3처럼 변하게 될 것이다. 변동된 값은 별색으로 칠하였다.
- 1번 노드에서 4번 노드를 잇는 간선은 없지만 1번 노드에서 2번 노드를 경유하고 2번 노드에서 4번 노드를 경유해 12가 된다.
- 4번 노드에서 3번 노드로 갈 수 있지만 3번 노드에서 4번 노드로 갈 수는 없었다. 그러나 3번에서 1번, 1번에서 2번, 2번에서 4번 노드를 경유하여 17이 된다.
순회를 끝낸 최종적인 결과는 그림 4와 같이 된다.
메모리 사용 줄이기
앞선 점화식을 보면 의 계산에 필요한 값은 와 이다. 도착 지점이 인 경우 와 는 크게 다르지 않다.
-
- 정점 부터 번 정점까지를 경유점으로 이용하여 로 가는 최단 경로의 길이
-
- 정점 부터 번 정점까지를 경유점으로 이용하여 로 가는 최단 경로의 길이
이를 이용해 코드를 고쳐 공간 복잡도를 으로 줄일 수 있다.
const V = 4, INF = 987654321;
const A = `
0 3 7 X
X 0 X 9
5 12 0 X
X 1 8 0
`.trim().split('\n').map(d => d.split(' ').map(e => e === 'X' ? INF : parseInt(e)));
for (let k = 0; k < V; k += 1) {
for (let i = 0; i < V; i += 1) {
for (let j = 0; j < V; j += 1) {
A[i][j] = Math.min(
A[i][j],
A[i][k] + A[k][j]
);
}
}
}
참조(References)
- 구종만, 알고리즘 문제 해결 전략, (인사이트, 2012).
- “플로이드-워셜 알고리즘”, Wikipedia, https://ko.wikipedia.org/wiki/%ED%94%8C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%9B%8C%EC%85%9C_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98