본문으로 이동

플로이드-워셜 알고리즘: 두 판 사이의 차이

위키백과, 우리 모두의 백과사전.
내용 삭제됨 내용 추가됨
222.118.64.55(토론)의 20497449판 편집을 되돌림
1번째 줄: 1번째 줄:
{{알고리즘 정보
'''플로이드-워셜 알고리즘'''({{lang|en|Floyd-Warshall Algorithm}})은 [[그래프]]에서 모든 꼭짓점 사이의 최단 [[경로 (그래프 이론)|경로]]의 거리를 구하는 [[알고리즘]]이다. 음수 가중치를 갖는 간선도 [[순환 (그래프)|순환]]만 없다면 잘 처리된다. 제일 바깥쪽 [[반복문]]은 거쳐가는 꼭짓점이고, 두 번째 반복문은 출발하는 꼭짓점, 세 번째 반복문은 도착하는 꼭짓점이다. 이 알고리즘은 플로이드 알고리즘이라고도 알려져 있다.
|class=(가중 그래프에서) [[전체-쌍 최단 경로 문제]]
|image=
|caption =
|data=[[그래프 (자료 구조)|그래프]]
|time=<math>\Theta (|V|^3)</math>
|average-time=<math>\Theta (|V|^3)</math>
|best-time=<math>\Theta (|V|^3)</math>
|space=<math>\Theta(|V|^2)</math>
}}


{{그래프 탐색 알고리즘}}
== 개요 ==
[[컴퓨터 과학]]에서, '''플로이드-워셜 알고리즘'''({{lang|en|Floyd-Warshall Algorithm}})은 변의 가중치가 음이거나 양인 (음수 사이클은 없는) [[가중 그래프]]에서 [[최단 경로 문제|최단 경로]]들을 찾는 [[알고리즘]]이다.<ref>{{Introduction to Algorithms|1}} See in particular Section 26.2, "The Floyd-Warshall algorithm", pp.&nbsp;558-565 and Section 26.4, "A general framework for solving path problems in directed graphs", pp.&nbsp;570-576.</ref><ref>{{서적 인용| author=Kenneth H. Rosen | title=Discrete Mathematics and Its Applications, 5th Edition | publisher = Addison Wesley | year=2003 | isbn=0-07-119881-4 }}</ref> 알고리즘을 한 번 수행하면 ''모든'' 꼭짓점 쌍 간의 최단 경로의 길이(가중치의 합)을 찾는다. 알고리즘 자체는 경로를 반환하지는 않지만, 알고리즘을 약간만 변형시키면 경로를 찾을 수 있다.
플로이드-워셜 알고리즘은 [[동적 계획법]] 접근으로, 그래프 상의 모든 두 정점을 잇는 경로의 최소 비용을 구한다. 여기에 경유지를 기록하면 최소 비용 경로까지 구할 수 있다. 플로이드-워셜 알고리즘은 다음과 같은 접근으로 설계되었다.
이 알고리즘의 일부 버전은 관계 <math>R</math>의 [[추이적 폐포]]를 찾거나, ([[슐츠 방법|슐츠 선거 제도]]와 결합해서) 가중 그래프의 모든 꼭짓점 쌍 간의 [[최대 폭 경로 문제|최대 폭 경로]]를 찾는 것이 가능하다.


==역사와 이름==
* 두 정점을 잇는 최소 비용 경로는 경유지를 거치거나 거치지 않는 경로 중 하나에 속한다.
플로이드-워셜 알고리즘은 [[동적 계획법]]의 한 예로, [[로버트 플로이드]]가 1962년에 현재 알려진 형태로 발표했다.<ref>{{저널 인용| first = Robert W. | last = Floyd | authorlink = 로버트 플로이드 | title = Algorithm 97: Shortest Path | journal = [[Communications of the ACM]] | volume = 5 | issue = 6 | page = 345 | date= June 1962 | doi = 10.1145/367766.368168 }}</ref> 하지만, 이 알고리즘은 본질적으로 [[버나드 로이]]가 1959년에 발표한 알고리즘<ref>{{저널 인용| first = Bernard | last = Roy |authorlink=버나드 로이| title = Transitivite et connexite. | journal = [[C. R. Acad. Sci. Paris]] | volume = 249 | pages = 216-218 | year= 1959 }}
* 만약 경유지를 거친다면 그것을 이루는 부분 경로 역시 최소 비용 경로여야 한다.
</ref>과, [[스티븐 워셜]]이 1962년에 발표한 알고리즘<ref>{{저널 인용| first = Stephen | last = Warshall | title = A theorem on Boolean matrices | journal = [[Journal of the ACM]] | volume = 9 | issue = 1 | pages = 11-12 | date= January 1962 | doi = 10.1145/321105.321107 }}</ref>과 그래프의 추이적 폐포를 찾는다는 점,<ref>{{매스월드|id=Floyd-WarshallAlgorithm | title = Floyd-Warshall Algorithm}}</ref>그리고 [[결정적 유한 오토마타]]를 [[정규 표현식]]으로 변환할 때, [[클레이니 알고리즘]](1956년에 발표됨)과 밀접한 관련이 있다는 점에서 동일하다.<ref>{{서적 인용| authorlink = 스티븐 클레이니 | first = S. C. | last = Kleene | chapter = Representation of events in nerve nets and finite automata | title = Automata Studies | editor = [[클로드 섀넌|C. E. Shannon]] and [[존 매카시 (컴퓨터 과학자)|J. McCarthy]] | pages = 3-42 | publisher = Princeton University Press | year= 1956 }}</ref> 이 알고리즘의 삼중 for 반복문의 공식은 1962년에 Peter Ingerman이 설명하였다.<ref>{{저널 인용| first = Peter Z. | last = Ingerman | title = Algorithm 141: Path Matrix | journal = [[Communications of the ACM]] | volume = 5 | number = 11 | page = 556 | date = November 1962 | doi = 10.1145/368996.369016 }}</ref>


이 알고리즘은 '''플로이드 알고리즘''', '''로이-워셜 알고리즘''', '''로이-플로이드 알고리즘''', 또는 '''WFI 알고리즘'''으로 알려져 있다.
이는 중첩된 부분문제로 볼 수 있으며 동적 계획법을 적용하여 효과적으로 접근할 수 있다. 한편 비용을 구하는 김에, 어떤 경유지를 지나서 최소 비용을 만들었는지 기록해두면, 최소 비용 뿐만 아니라 최소 비용 경로까지도 구할 수 있다. 플로이드-워셜 알고리즘을 의사코드로 표현하면 아래와 같다.


==알고리즘==
1 '''procedure''' floyd-warshall(두 정점을 잇는 모서리의 가중치 테이블 ''W'')
플로이드-워셜 알고리즘은 각각의 꼭짓점 쌍을 지나는 그래프의 모든 경로를 비교한다. 이 방법은 그래프에서 <math>\Theta(|V|^3)</math>의 비교로 진행할 수 있다. 그 이유는 그래프에서는 최대 <math>\Omega (|V|^2)</math>의 변이 있을 수 있고, 모든 변의 조합을 확인하기 때문이다. 이 알고리즘은 두 꼭짓점 간의 추정 최단 경로를 최적이 될 때 까지 점진적으로 개선시켜서 최단경로를 찾는다.
2 ''D'' : 두 정점을 잇는 경로의 최소 비용 테이블. 모든 성분을 ∞로 초기화
3 ''M'' : 두 정점을 지나가는 최소 비용 경로가 거쳐야 할 마지막 경유지 테이블. 모든 성분을 null로 초기화
4 '''for''' ''i'' from 1 to |V|
5 '''for''' ''j'' from 1 to |V|
6 ''D''[''i''][''j''] := ''W''[''i''][''j'']
7 '''for''' v from 1 to |V|
8 ''D''[''v''][''v''] := 0
9 '''for''' k from 1 to |V|
10 '''for''' i from 1 to |V|
11 '''for''' j from 1 to |V|
12 '''if''' ''D[i][j] > D[i][k] + D[k][j]''
13 ''D''[''i''][''j''] := D[''i''][''k''] + ''D''[''k''][''j'']
14 ''M''[''i''][''j''] := ''k''
15 '''return''' D


1에서 <math>N</math>까지 번호가 매겨진 <math>V</math>를 꼭짓점으로 갖는 그래프 <math>G</math>를 생각한다. 그 후 <math>i</math>에서 <math>j</math>로 집합 <math>\{1,2,\ldots,k\}</math>의 꼭짓점들 만을 '''''경유지'''''로 거쳐 가는 최단 경로를 반환하는 함수 <math>\mathrm{shortestPath}(i,j,k)</math>를 생각한다. 함수가 주어졌을 때, 목표는 <math>\{1,2,\ldots,N\}</math>에 있는 꼭짓점만을 이용해서 모든 꼭지점 <math>i</math>에서 모든 꼭짓점 <math>j</math>로 가는 최단 경로를 찾는 것이다.
이렇게 구한 비용 & 경유지를 활용하여 경로를 복구하는 알고리즘은 아래와 같다.


각각의 꼭짓점 쌍에 대해서, <math>\mathrm{shortestPath}(i,j,k)</math>는 다음 중 한 가지에 속한다:
1 '''procedure''' floyd-warshall-path(정점 ''v1'', 정점 ''v2'', 경유지 테이블 ''M'')
2 ''S'' : 경로를 역으로 저장하는 [[스택]]
3 ''P'' : 경로
4 ''S''.'''push'''(''v2'')
5 '''while''' ''M''[''v1''][''S''.'''top'''()]
6 ''S''.'''push'''(''M''[''v1''][''S''.'''top'''()])
7 '''while''' ~''S''.'''empty'''()
8 ''P''.'''add'''(''S''.'''top'''())
9 ''S''.'''pop'''()
10 '''return''' ''P''


:(1) <math>k</math>를 '''통과하지 않는''' 경로 (집합 <math>\{1,\ldots,k-1\}</math>에 있는 꼭짓점만 거쳐간다.)
== 계산 복잡도 ==
플로이드-워셜 알고리즘으로 모든 정점 간 경로의 최소비용을 구하는 것은 <math>O(|V|^3)</math>의 [[시간 복잡도]]를 갖는다. 경유지를 기록한 경우, 경로를 역으로 추출하는 알고리즘의 복잡도는 <math>O(|V|)</math>의 시간 복잡도를 갖는다. 종종 [[데이크스트라 알고리즘]]과 자주 비교되곤 하는데, 두 알고리즘 모두 각각의 장단점이 있기 때문에 상황에 맞는 알고리즘 선정 필요성이 요구된다.


:(2) <math>k</math>를 '''통과 하는''' 경로 (<math>i</math>에서 <math>k</math>까지와 <math>k</math>에서 <math>j</math>까지 가는 경로 모두 <math>\{1,\ldots,k-1\}</math>에 있는 꼭짓점 만을 거쳐간다)
== 소스 코드 ==


<math>i</math>에서 <math>j</math>까지 <math>1</math>에서 <math>k-1</math>의 꼭짓점 만을 거쳐가는 경로 중 최선의 경로는 <math>\mathrm{shortestPath}(i,j,k-1)</math>에 의해 정의된다는 것은 알고 있으며, 만약 <math>i</math>에서 <math>k</math>를 거쳐 <math>j</math>로 가는 더 나은 경로가 있다면, 그 경로는 <math>i</math>에서 <math>k</math>까지 (<math>\{1,\ldots,k-1\}</math>만을 거쳐서) 가는 경로와 <math>k</math>에서 <math>j</math>까지 (<math>\{1,\ldots,k-1\}</math>만을 거쳐서) 가는 경로를 합친 것이라는 것은 자명하다.
=== [[C 언어]] ===
<source lang="c" line="1">
int d[N][N]; // Cost Table. d[v1][v2] : Minimum cost of path v1 -> v2
int via[N][N]; // Via Table.
int weight[N][N]; //Weight
void doFloyd()
{
// Initialize
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
d[i][j] = weight[i][j]; // k = 0
via[i][j] = -1;
}
}


<math>w(i,j)</math>이 꼭짓점 <math>i</math>와 <math>j</math> 간의 변의 가중치라면, <math>\mathrm{shortestPath}(i,j,k)</math>를 다음의 [[재귀|재귀적]] 공식으로 정의할 수 있다: 기본적인 경우는
// Do Floyd-Warshall Algorithm
: <math>\mathrm{shortestPath}(i,j,0) = w(i,j)</math>
for (int k = 0; k < N; ++k)
이고 재귀적인 경우는 다음과 같다
{
: <math>\mathrm{shortestPath}(i,j,k) =</math>
for (int i = 0; i < N; ++i)
:: <math>\mathrm{min}\Big(\mathrm{shortestPath}(i,j,k-1),</math>
{
::: <math>\mathrm{shortestPath}(i,k,k-1)+\mathrm{shortestPath}(k,j,k-1)\Big)</math>.
for (int j = 0; j < N; ++j)
{
// i -> j 보다 k를 경유해서 가는 값이 더 작다면 경유지를 해서 가는 가중치로 교환
if (d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j];
// k -> j 가는 경로가 기록이 없다면 신규 경로 이고, 있다면 경유지의 경유지이다.
if (via[k][j] == -1) {
via[i][j] = k;
}
else {
via[i][j] = via[k][j];
}
}
}
}
}
}
// ps. 의사코드, 수도코드 편집 부탁 드립니다.


이 공식은 플로이드-워셜 알고리즘의 핵심이다. 이 알고리즘은 처음에 모든 <math>(i,j)</math>쌍에 대해서 <math>k=1</math>일 때 <math>\mathrm{shortestPath}(i,j,k)</math>를 계산하고, 다음으로 <math>k=2</math>일 때를 계산하는 식으로 <math>k=N</math>이 될 때까지 계속하면, 모든 <math>(i,j)</math>쌍에 대해서 최단 경로를 찾게 된다. 기본적인 알고리즘의 의사코드는 다음과 같다:


1 '''let''' dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
</source>[[C++|<big>'''C++'''</big>]]<syntaxhighlight lang="c++" line="1">
2 '''for each''' edge (''u'',''v'')
#include <iostream>
3 dist[''u''][''v''] &larr; w(''u'',''v'') ''// 변 (''u'',''v'')의 가중치
#include <vector>
4 '''for each''' vertex ''v''
#include <algorithm>
5 dist[''v''][''v''] &larr; 0
#include <stack>
6 '''for''' ''k'' '''from''' 1 '''to''' |V|
7 '''for''' ''i'' '''from''' 1 '''to''' |V|
8 '''for''' ''j'' '''from''' 1 '''to''' |V|
9 '''if''' dist[''i''][''j''] > dist[''i''][''k''] + dist[''k''][''j'']
10 dist[''i''][''j''] &larr; dist[''i''][''k''] + dist[''k''][''j'']
11 '''end if'''


==예시==
const int nagative = 10000001;
위의 알고리즘은 좌측 하단의 그래프에서 수행된다:


[[파일:Floyd-Warshall example.svg|600px]]
int n;
vector <vector <int> > w;
vector <vector <int> > v;


위에서 첫 번째 외부 반복문의 반복 이전은 {{math|1=''k'' = 0}}으로 표시했으며, 이것을 통해 알게 된 경로는 그래프의 한 변에 대응한다. {{math|1=''k'' = 1}}일 때, 꼭짓점 1을 통과하는 경로를 찾을 수 있다: 특히, 경로 [2,1,3]을 찾았기 때문에, 변이 더 적지만 더 (가중치의 관점에서)긴 경로인 [2,3]을 대체한다. {{math|1=''k'' = 2}}일 때, 꼭짓점 {1,2}를 통과하는 경로를 찾았다. 빨간 색과 파란 색의 네모는 경로 [4,2,1,3]이 경로 [4,2]와 이전 반복에서 찾은 [2,1,3]이 2를 교차점으로 결합해서 이루어져 있다는 것을 나타낸다. 경로 [4,2,3]은 2에서 3까지 가는 경로가 [2,1,3]보다 길기 때문에 고려하지 않는다. {{math|1=''k'' = 3}}일 때, {1,2,3}을 지나는 경로를 찾는다. 마지막으로, {{math|1=''k'' = 4}}일 때, 모든 최단경로를 찾게 된다.
void init(int N, vector <vector <int> > d) {
n = N;
w.resize(n, vector<int>(n, nagative));
v.resize(n, vector<int>(n, -1));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
w[i][j] = d[i][j];
}
}
for (int i = 0; i < n; i++)
w[i][i] = 0;
}


각각의 {{mvar|k}}의 반복에 대한 거리 행렬, 업데이트 된 거리는 '''굵은 글씨'''로 표시했다:
void floyd() {
{| class=wikitable style="float:left; margin:10px; text-align:center;"
for (int k = 0; k < n; k++) {
|+
for (int i = 0; i < n; i++) {
| colspan="2" rowspan="2" |{{math|1=''k'' = 0}}
for (int j = 0; j < n; j++) {
| colspan="4" |{{mvar|j}}
// i -> j 보다 k를 경유해서 가는 값이 더 작다면 경유해서 가는 가중치로 교환
|-
if (w[i][j] > w[i][k] + w[k][j]) {
! 1 !! 2 !! 3 !! 4
w[i][j] = w[i][k] + w[k][j];
|-
// k -> j 가는 경로가 기록이 없다면 신규 경로 이고, 있다면 경유지의 경유지이다.
| rowspan="4" |{{mvar|i}}
if (v[k][j] == -1) v[i][j] = k;
! 1
else v[i][j] = v[k][j];
| 0 || &infin; || -2 || &infin;
}
|-
}
! 2
}
| 4 || 0 || 3 || &infin;
}
|-
}
! 3
| &infin; || &infin; || 0 || 2
|-
! 4
| &infin; || -1 || &infin; || 0
|}


{| class=wikitable style="float:left; margin:10px; text-align:center;"
void path(int i, int j) {
|+
stack <int> s;
| colspan="2" rowspan="2" |{{math|1=''k'' = 1}}
s.push(j);
| colspan="4" |{{mvar|j}}
while (v[i][s.top()] != -1) {
|-
s.push(v[i][s.top()]);
! 1 !! 2 !! 3 !! 4
}
|-
s.push(i);
| rowspan="4" |{{mvar|i}}
while (!s.empty()) {
! 1
cout << s.top() << " ";
| 0 || &infin; || -2 || &infin;
s.pop();
|-
}
! 2
cout << "\n";
| 4 || 0 || '''2''' || &infin;
}
|-
</syntaxhighlight>
! 3
| &infin; || &infin; || 0 || 2
|-
! 4
| &infin; || -1 || &infin; || 0
|}


{| class=wikitable style="float:left; margin:10px; text-align:center;"
=== [[JAVA]] ===
|+
<syntaxhighlight lang="java" line="1">
| colspan="2" rowspan="2" |{{math|1=''k'' = 2}}
private static void floyd(int d[][], int V){
| colspan="4" |{{mvar|j}}
for (int k = 1; k <= V; k++)
|-
for (int i = 1; i <= V; i++)
! 1 !! 2 !! 3 !! 4
for (int j = 1; j <= V; j++) {
|-
if (d[i][k] == Integer.MAX_VALUE || d[k][j] == Integer.MAX_VALUE) continue;
| rowspan="4" |{{mvar|i}}
if (d[i][j] > d[i][k] + d[k][j]){
! 1
d[i][j] = d[i][k] + d[k][j];
| 0 || &infin; || -2 || &infin;
}
|-
}
! 2
}
| 4 || 0 || 2 || &infin;
</syntaxhighlight>{{토막글|컴퓨터 과학}};
|-
! 3
| &infin; || &infin; || 0 || 2
|-
! 4
| '''3''' || -1 || '''1''' || 0
|}

{| class=wikitable style="float:left; margin:10px; text-align:center;"
|+
| colspan="2" rowspan="2" |{{math|1=''k'' = 3}}
| colspan="4" |{{mvar|j}}
|-
! 1 !! 2 !! 3 !! 4
|-
| rowspan="4" |{{mvar|i}}
! 1
| 0 || &infin; || -2 || '''0'''
|-
! 2
| 4 || 0 || 2 || '''4'''
|-
! 3
| &infin; || &infin; || 0 || 2
|-
! 4
| 3 || -1 || 1 || 0
|}

{| class=wikitable style="float:left; margin:10px; text-align:center;"
|+
| colspan="2" rowspan="2" |{{math|1=''k'' = 4}}
| colspan="4" |{{mvar|j}}
|-
! 1 !! 2 !! 3 !! 4
|-
| rowspan="4" |{{mvar|i}}
! 1
| 0 || '''-1''' || -2 || 0
|-
! 2
| 4 || 0 || 2 || 4
|-
! 3
| '''5''' || '''1''' || 0 || 2
|-
! 4
| 3 || -1 || 1 || 0
|}
{{clear}}

==음수 사이클에서 행동==

음수 사이클은 변의 가중치의 합이 음수를 갖는 사이클이다. 음수 사이클을 구성하는 어떤 꼭짓점 쌍<math>i</math>, <math>j</math>에 대해서든지 <math>i</math>에서 <math>j</math>로 가는 경로의 길이는 음의 무한으로 내려갈 수 있기 때문에 최단 경로가 존재하지 않는다. 수치적으로 의미있는 결과를 얻기 위해서, 플로이드-워셜 알고리즘은 입력에 음수 사이클이 없다고 가정한다. 반면, 음수 사이클이 존재할 경우에는 플로이드-워셜 알고리즘으로 감지할 수 있다. 그 과정은 다음과 같다:

* 플로이드-워셜 알고리즘은 모든 꼭짓점 쌍 <math>(i,j)</math>에 대해서 경로를 반복적으로 개선하며, <math>i=j</math>일 때에도 개선한다.
* 초기에, 경로 <math>(i,i)</math>의 거리는 0이다.
* 경로 <math>[i,k,\ldots,i]</math>는 길이가 0보다 작을 때만 개선이 되므로, 이는 음수 사이클이 존재한다는 것을 의미한다.
* 그렇기 때문에, 이 알고리즘을 수행한 이후에 <math>(i,i)</math>가 음수라면 <math>i</math>에서 <math>i</math>로 되돌아오는 음수 사이클이 존재한다는 것을 의미한다.

따라서, 플로이드-워셜 알고리즘을 이용해서 음수 [[순환 (그래프 이론)|사이클]]을 감지하기 위해서는, 경로 행렬의 대각 성분을 확인해서 음수가 나타나는지를 확인한다. 음수가 발견되면 적어도 하나 이상의 음수 사이클이 존재한다는 것을 나타낸다.<ref>{{웹 인용| first = Dorit | last = Hochbaum | authorlink = Dorit S. Hochbaum | url = http://www.ieor.berkeley.edu/~hochbaum/files/ieor266-2014.pdf | title = Section 8.9: Floyd-Warshall algorithm for all pairs shortest paths | work = Lecture Notes for IEOR 266: Graph Algorithms and Network Flows | date = 2014 | format = [[PDF]] | publisher = Department of Industrial Engineering and Operations Research, [[캘리포니아 대학교 버클리|University of California, Berkeley]]}}</ref> 수치적 문제를 피하기 위해서는 알고리즘의 안쪽 반복문에서 경로 행렬의 대각 성분에서 음수가 나타나는지를 확인해야 한다.<ref>
{{저널 인용
| title = The Floyd-Warshall algorithm on graphs with negative cycles
| author = Stefan Hougardy
| journal = Information Processing Letters
| url = http://www.sciencedirect.com/science/article/pii/S002001901000027X
| volume = 110
| number = 8-9
| date = April 2010
| pages = 279-281
| doi=10.1016/j.ipl.2010.02.001
}}</ref> 무향 그래프에서 음의 가중치가 있으면 그 인접한 꼭짓점 사이에서 음수 사이클이 생기는 것은 명백하다<!-- "사이클"은 반복되는 변이 없을 것을 암시할 수 있다 --> (예: 닫힌 보행). [[#예시|위의 예시]]의 모든 변을 방향이 없다고 생각하면, 꼭짓점 수열 4 - 2 - 4는 가중치의 합이 -2인 사이클이다.

==경로 복원==

플로이드-워셜 알고리즘은 모든 꼭짓점 간의 경로의 길이 만을 제공한다. 간단한 수정으로 두 끝점 사이의 실제 경로를 재현하는 방법을 만들 수 있다. 그중 한 방법으로 각각의 꼭짓점에서 다른 꼭짓점 까지의 실제 경로를 저장할 지도 모르지만, 그럴 필요가 없을 뿐 더러 메모리의 엄청난 낭비가 생긴다. 대신, 각각의 꼭짓점에 대해서 [[최단 경로 트리]]를 <math>\Theta(|E|)</math>시간복잡도에 각 트리를 저장하는데 <math>\Theta(|V|)</math>공간복잡도를 사용해서 계산 할 수 있으며, 이 트리를 이용해서 두 연결된 꼭짓점간의 경로를 효율적으로 재현할 수 있다.

'''let''' dist be a <math>|V| \times |V|</math> array of minimum distances initialized to <math>\infty</math> (infinity)
'''let''' next be a <math>|V| \times |V|</math> array of vertex indices initialized to '''null'''
'''procedure''' ''FloydWarshallWithPathReconstruction'' ()
'''for each''' edge (u,v)
dist[u][v] &larr; w(u,v) ''// 변 (u,v)의 가중치
next[u][v] &larr; v
'''for''' k '''from''' 1 '''to''' |V| ''// 일반적인 플로이드-워셜의 수행
'''for''' i '''from''' 1 '''to''' |V|
'''for''' j '''from''' 1 '''to''' |V|
'''if''' dist[i][j] > dist[i][k] + dist[k][j] '''then'''
dist[i][j] &larr; dist[i][k] + dist[k][j]
next[i][j] &larr; next[i][k]
'''procedure''' Path(u, v)
'''if''' next[u][v] = null '''then'''
'''return''' []
path = [u]
'''while u ≠ v'''
u &larr; next[u][v]
path.append(u)
'''return''' path

==해석==
<math>n</math>을 꼭짓점의 갯수 <math>|V|</math>로 두자. <math>n^2</math>개의
<math>\mathrm{shortestPath}(i,j,k)</math> (모든 <math>i</math>와 <math>j</math>에 대해서)를
<math>\mathrm{shortestPath}(i,j,k-1)</math>에서 찾기 위해서는 <math>2n^2</math>개의 연산이 필요하다. 처음에는
<math>\mathrm{shortestPath}(i,j,0) = \mathrm{edgeCost}(i,j)</math>에서 시작했고 and compute the sequence of <math>n</math>개의 행렬 <math>\mathrm{shortestPath}(i,j,1)</math>, <math>\mathrm{shortestPath}(i,j,2)</math>, <math>\ldots</math>, <math>\mathrm{shortestPath}(i,j,n)</math>을 계산했기 때문에, 사용된 전체 연산의 수는
<math>n \cdot 2n^2 = 2n^3</math>이다. 따라서, 이 알고리즘의 [[계산 복잡도 이론|복잡도]]는 [[점근 표기법|<math>\Theta(n^3)</math>]]이다.

==적용과 일반화==
플로이드-워셜 알고리즘은 다음의 문제들을 다른 알고리즘과 같이 해결할 수 있다:
* 유향 그래프에서 최단 경로(플로이드 알고리즘).
* 유향 그래프의 [[추이적 폐포]](워셜 알고리즘). 워셜 알고리즘의 원래 공식에서, 그래프는 가중치가 없고 진릿값 인접 행렬로 나타냈었다. 그리고 덧셈 연산 대신에 [[논리곱]] (AND)이, 뺄셈 연산을 [[논리합]] (OR)으로 사용했었다.
* [[유한 오토마타]]에서 받아들이는 [[정규 언어]]를 나타내는 [[정규식]] 찾기([[클레이니 알고리즘]], 플로이드-워셜 알고리즘의 밀접한 일반화)<ref>{{인용|title=Handbook of Graph Theory|series=Discrete Mathematics and Its Applications|first1=Jonathan L.|last1=Gross|first2=Jay|last2=Yellen|publisher=CRC Press|year=2003|page=65|url=https://books.google.com/books?id=mKkIGIea_BkC&pg=PA65|isbn=9780203490204}}.</ref>
* [[실수|실]][[행렬]]의 [[역행렬]] ([[가우스-요르단 소거법|가우스 요르단 알고리즘]]) <ref>{{웹 인용| url = http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.71.7650 | title = Algebraic Structures for Transitive Closure | first = Rafael | last = Penaloza}}</ref>
* 최적 라우팅. 이 적용에서는 두 꼭짓점 간의 최대 흐름을 찾는것이 목표이다. 즉, 위의 의사코드에서 최솟값을 적용하는 것이 아니라 최댓값을 적용해야 한다는 것이다. 변의 가중치는 흐름의 제약을 나타낸다. 경로의 가중치는 병목을 나타내므로 위의 덧셈 연산은 뺄셈 연산으로 대체한다.
* [[Pathfinder network]]의 빠른 계산.
* [[최대 폭 경로 문제|최대 폭 경로/최대 대역폭 경로]]
* difference bound matrices (DBMs)의 표준식 계산
* 그래프 간의 유사도 계산

==수행==
플로이드-워셜 알고리즘은 많은 [[프로그래밍 언어]]에서 수행할 수 있다.
* [[C++]]: [http://www.boost.org/libs/graph/doc/ boost::graph] 라이브러리
* [[C#]]: [http://www.codeplex.com/quickgraph QuickGraph]
* [[C#]]: [https://www.nuget.org/packages/QuickGraphPCL/3.6.61114.2 QuickGraphPCL] (A fork of QuickGraph with better compatibility with projects using Portable Class Libraries.)
* [[자바 (프로그래밍 언어)|자바]]: [http://commons.apache.org/sandbox/commons-graph/ Apache Commons Graph] 라이브러리
* [[자바스크립트]]: [[Cytoscape]] 라이브러리
* [[MATLAB]]: [http://www.mathworks.com/matlabcentral/fileexchange/10922 Matlab_bgl] 패키지
* [[펄]]: [https://metacpan.org/module/Graph Graph] 모듈
* [[파이썬]]: [[SciPy]] 라이브러리(모듈 [http://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csgraph.floyd_warshall.html#scipy.sparse.csgraph.floyd_warshall scipy.sparse.csgraph])또는 [[NetworkX]] 라이브러리
* [[R (프로그래밍 언어)|R]]: 패키지 [https://cran.r-project.org/web/packages/e1071/index.html e1071]과 [https://cran.r-project.org/web/packages/Rfast/index.html Rfast]

==다른 최단 경로 알고리즘과 비교==
플로이드-워셜 알고리즘은 거의 대부분이나 모든 꼭짓점 쌍이 변으로 연결된 [[밀집 그래프]]에서 모든 꼭짓점 쌍 간의 경로를 계산할 때 적합하다. 음수 가중치가 없는 [[희소 그래프]]에서는 모든 꼭짓점에서 출발하는 [[데이크스트라 알고리즘]]이 반복 데이크스트라의 수행 시간([[피보나치 힙]]을 사용하면 <math>O(|E||V|+|V|^2\log|V|)</math>)은 <math>|E|</math>가 <math>|V|^2</math>에 비해 무시할 수 있을 정도면 플로이드-워셜 알고리즘의 수행 시간인 <math>O(|V|^3)</math>보다 적으므로 더 적합하다. 희소 그래프에서 음수 가중치가 있지만 음수 사이클은 없는 경우에는 [[존슨 알고리즘]]은 수행 시간이 반복 데이크스트라 시간과 같으므로 사용할 수 있다.

알려진 알고리즘 중에는 밀집 그래프에서 모든 쌍의 최단 경로 계산을 빠르게 하기 위해 [[행렬 곱셈|빠른 행렬 곱셈]]을 사용하는 알고리즘도 있지만, 이러한 알고리즘들은 변의 가중치에 추가적인 제약 조건이 따른다(예를 들면 작은 정수여야 한다라는 식으로).<ref>{{인용
| last = Zwick | first = Uri | authorlink = 유리 즈윅
| date = May 2002
| doi = 10.1145/567112.567114
| issue = 3
| journal = [[Journal of the ACM]]
| pages = 289-317
| title = All pairs shortest paths using bridging sets and rectangular matrix multiplication
| volume = 49| arxiv = cs/0008011}}.</ref><ref>{{인용
| last = Chan | first = Timothy M. | authorlink = 티모시 M. 찬
| date = January 2010
| doi = 10.1137/08071990x
| issue = 5
| journal = [[SIAM Journal on Computing]]
| pages = 2075-2089
| title = More algorithms for all-pairs shortest paths in weighted graphs
| volume = 39}}.</ref> 게다가, 수행 시간에서 큰 상수 인자 때문에, 이런 알고리즘들은 매우 큰 그래프에서만 플로이드-워셜 알고리즘보다 빠른 속도를 낼 수 있다.

==참고 문헌==
{{각주}}

==외부 링크==
{{위키공용분류|Floyd-Warshall algorithm}}
* [http://www.pms.informatik.uni-muenchen.de/lehre/compgeometry/Gosper/shortest_path/shortest_path.html#visualization 플로이드-워셜 알고리즘의 조작 가능한 애니메이션]


[[분류:그래프 알고리즘]]
[[분류:그래프 알고리즘]]
[[분류:라우팅 알고리즘]]
[[분류:다항 시간 문제]]
[[분류:의사코드가 있는 문서]]
[[분류:동적 계획법]]
[[분류:동적 계획법]]

2018년 7월 9일 (월) 13:12 판

플로이드-워셜 알고리즘
분류(가중 그래프에서) 전체-쌍 최단 경로 문제
자료 구조그래프
최악 시간복잡도
최선 시간복잡도
평균 시간복잡도
공간복잡도

컴퓨터 과학에서, 플로이드-워셜 알고리즘(Floyd-Warshall Algorithm)은 변의 가중치가 음이거나 양인 (음수 사이클은 없는) 가중 그래프에서 최단 경로들을 찾는 알고리즘이다.[1][2] 알고리즘을 한 번 수행하면 모든 꼭짓점 쌍 간의 최단 경로의 길이(가중치의 합)을 찾는다. 알고리즘 자체는 경로를 반환하지는 않지만, 알고리즘을 약간만 변형시키면 경로를 찾을 수 있다. 이 알고리즘의 일부 버전은 관계 추이적 폐포를 찾거나, (슐츠 선거 제도와 결합해서) 가중 그래프의 모든 꼭짓점 쌍 간의 최대 폭 경로를 찾는 것이 가능하다.

역사와 이름

플로이드-워셜 알고리즘은 동적 계획법의 한 예로, 로버트 플로이드가 1962년에 현재 알려진 형태로 발표했다.[3] 하지만, 이 알고리즘은 본질적으로 버나드 로이가 1959년에 발표한 알고리즘[4]과, 스티븐 워셜이 1962년에 발표한 알고리즘[5]과 그래프의 추이적 폐포를 찾는다는 점,[6]그리고 결정적 유한 오토마타정규 표현식으로 변환할 때, 클레이니 알고리즘(1956년에 발표됨)과 밀접한 관련이 있다는 점에서 동일하다.[7] 이 알고리즘의 삼중 for 반복문의 공식은 1962년에 Peter Ingerman이 설명하였다.[8]

이 알고리즘은 플로이드 알고리즘, 로이-워셜 알고리즘, 로이-플로이드 알고리즘, 또는 WFI 알고리즘으로 알려져 있다.

알고리즘

플로이드-워셜 알고리즘은 각각의 꼭짓점 쌍을 지나는 그래프의 모든 경로를 비교한다. 이 방법은 그래프에서 의 비교로 진행할 수 있다. 그 이유는 그래프에서는 최대 의 변이 있을 수 있고, 모든 변의 조합을 확인하기 때문이다. 이 알고리즘은 두 꼭짓점 간의 추정 최단 경로를 최적이 될 때 까지 점진적으로 개선시켜서 최단경로를 찾는다.

1에서 까지 번호가 매겨진 를 꼭짓점으로 갖는 그래프 를 생각한다. 그 후 에서 로 집합 의 꼭짓점들 만을 경유지로 거쳐 가는 최단 경로를 반환하는 함수 를 생각한다. 함수가 주어졌을 때, 목표는 에 있는 꼭짓점만을 이용해서 모든 꼭지점 에서 모든 꼭짓점 로 가는 최단 경로를 찾는 것이다.

각각의 꼭짓점 쌍에 대해서, 는 다음 중 한 가지에 속한다:

(1) 통과하지 않는 경로 (집합 에 있는 꼭짓점만 거쳐간다.)
(2) 통과 하는 경로 (에서 까지와 에서 까지 가는 경로 모두 에 있는 꼭짓점 만을 거쳐간다)

에서 까지 에서 의 꼭짓점 만을 거쳐가는 경로 중 최선의 경로는 에 의해 정의된다는 것은 알고 있으며, 만약 에서 를 거쳐 로 가는 더 나은 경로가 있다면, 그 경로는 에서 까지 (만을 거쳐서) 가는 경로와 에서 까지 (만을 거쳐서) 가는 경로를 합친 것이라는 것은 자명하다.

이 꼭짓점 간의 변의 가중치라면, 를 다음의 재귀적 공식으로 정의할 수 있다: 기본적인 경우는

이고 재귀적인 경우는 다음과 같다

.

이 공식은 플로이드-워셜 알고리즘의 핵심이다. 이 알고리즘은 처음에 모든 쌍에 대해서 일 때 를 계산하고, 다음으로 일 때를 계산하는 식으로 이 될 때까지 계속하면, 모든 쌍에 대해서 최단 경로를 찾게 된다. 기본적인 알고리즘의 의사코드는 다음과 같다:

1 let dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
2 for each edge (u,v)
3    dist[u][v] ← w(u,v)  // 변 (u,v)의 가중치
4 for each vertex v
5    dist[v][v] ← 0
6 for k from 1 to |V|
7    for i from 1 to |V|
8       for j from 1 to |V|
9          if dist[i][j] > dist[i][k] + dist[k][j] 
10             dist[i][j] ← dist[i][k] + dist[k][j]
11         end if

예시

위의 알고리즘은 좌측 하단의 그래프에서 수행된다:

위에서 첫 번째 외부 반복문의 반복 이전은 k = 0으로 표시했으며, 이것을 통해 알게 된 경로는 그래프의 한 변에 대응한다. k = 1일 때, 꼭짓점 1을 통과하는 경로를 찾을 수 있다: 특히, 경로 [2,1,3]을 찾았기 때문에, 변이 더 적지만 더 (가중치의 관점에서)긴 경로인 [2,3]을 대체한다. k = 2일 때, 꼭짓점 {1,2}를 통과하는 경로를 찾았다. 빨간 색과 파란 색의 네모는 경로 [4,2,1,3]이 경로 [4,2]와 이전 반복에서 찾은 [2,1,3]이 2를 교차점으로 결합해서 이루어져 있다는 것을 나타낸다. 경로 [4,2,3]은 2에서 3까지 가는 경로가 [2,1,3]보다 길기 때문에 고려하지 않는다. k = 3일 때, {1,2,3}을 지나는 경로를 찾는다. 마지막으로, k = 4일 때, 모든 최단경로를 찾게 된다.

각각의 k의 반복에 대한 거리 행렬, 업데이트 된 거리는 굵은 글씨로 표시했다:

k = 0 j
1 2 3 4
i 1 0 -2
2 4 0 3
3 0 2
4 -1 0
k = 1 j
1 2 3 4
i 1 0 -2
2 4 0 2
3 0 2
4 -1 0
k = 2 j
1 2 3 4
i 1 0 -2
2 4 0 2
3 0 2
4 3 -1 1 0
k = 3 j
1 2 3 4
i 1 0 -2 0
2 4 0 2 4
3 0 2
4 3 -1 1 0
k = 4 j
1 2 3 4
i 1 0 -1 -2 0
2 4 0 2 4
3 5 1 0 2
4 3 -1 1 0

음수 사이클에서 행동

음수 사이클은 변의 가중치의 합이 음수를 갖는 사이클이다. 음수 사이클을 구성하는 어떤 꼭짓점 쌍, 에 대해서든지 에서 로 가는 경로의 길이는 음의 무한으로 내려갈 수 있기 때문에 최단 경로가 존재하지 않는다. 수치적으로 의미있는 결과를 얻기 위해서, 플로이드-워셜 알고리즘은 입력에 음수 사이클이 없다고 가정한다. 반면, 음수 사이클이 존재할 경우에는 플로이드-워셜 알고리즘으로 감지할 수 있다. 그 과정은 다음과 같다:

  • 플로이드-워셜 알고리즘은 모든 꼭짓점 쌍 에 대해서 경로를 반복적으로 개선하며, 일 때에도 개선한다.
  • 초기에, 경로 의 거리는 0이다.
  • 경로 는 길이가 0보다 작을 때만 개선이 되므로, 이는 음수 사이클이 존재한다는 것을 의미한다.
  • 그렇기 때문에, 이 알고리즘을 수행한 이후에 가 음수라면 에서 로 되돌아오는 음수 사이클이 존재한다는 것을 의미한다.

따라서, 플로이드-워셜 알고리즘을 이용해서 음수 사이클을 감지하기 위해서는, 경로 행렬의 대각 성분을 확인해서 음수가 나타나는지를 확인한다. 음수가 발견되면 적어도 하나 이상의 음수 사이클이 존재한다는 것을 나타낸다.[9] 수치적 문제를 피하기 위해서는 알고리즘의 안쪽 반복문에서 경로 행렬의 대각 성분에서 음수가 나타나는지를 확인해야 한다.[10] 무향 그래프에서 음의 가중치가 있으면 그 인접한 꼭짓점 사이에서 음수 사이클이 생기는 것은 명백하다 (예: 닫힌 보행). 위의 예시의 모든 변을 방향이 없다고 생각하면, 꼭짓점 수열 4 - 2 - 4는 가중치의 합이 -2인 사이클이다.

경로 복원

플로이드-워셜 알고리즘은 모든 꼭짓점 간의 경로의 길이 만을 제공한다. 간단한 수정으로 두 끝점 사이의 실제 경로를 재현하는 방법을 만들 수 있다. 그중 한 방법으로 각각의 꼭짓점에서 다른 꼭짓점 까지의 실제 경로를 저장할 지도 모르지만, 그럴 필요가 없을 뿐 더러 메모리의 엄청난 낭비가 생긴다. 대신, 각각의 꼭짓점에 대해서 최단 경로 트리시간복잡도에 각 트리를 저장하는데 공간복잡도를 사용해서 계산 할 수 있으며, 이 트리를 이용해서 두 연결된 꼭짓점간의 경로를 효율적으로 재현할 수 있다.

let dist be a  array of minimum distances initialized to  (infinity)
let next be a  array of vertex indices initialized to null

procedure FloydWarshallWithPathReconstruction ()
   for each edge (u,v)
      dist[u][v] ← w(u,v)  // 변 (u,v)의 가중치
      next[u][v] ← v
   for k from 1 to |V| // 일반적인 플로이드-워셜의 수행
      for i from 1 to |V|
         for j from 1 to |V|
            if dist[i][j] > dist[i][k] + dist[k][j] then
               dist[i][j] ← dist[i][k] + dist[k][j]
               next[i][j] ← next[i][k]

procedure Path(u, v)
   if next[u][v] = null then
       return []
   path = [u]
   while u ≠ v
       u ← next[u][v]
       path.append(u)
   return path

해석

을 꼭짓점의 갯수 로 두자. 개의 (모든 에 대해서)를 에서 찾기 위해서는 개의 연산이 필요하다. 처음에는 에서 시작했고 and compute the sequence of 개의 행렬 , , , 을 계산했기 때문에, 사용된 전체 연산의 수는 이다. 따라서, 이 알고리즘의 복잡도이다.

적용과 일반화

플로이드-워셜 알고리즘은 다음의 문제들을 다른 알고리즘과 같이 해결할 수 있다:

  • 유향 그래프에서 최단 경로(플로이드 알고리즘).
  • 유향 그래프의 추이적 폐포(워셜 알고리즘). 워셜 알고리즘의 원래 공식에서, 그래프는 가중치가 없고 진릿값 인접 행렬로 나타냈었다. 그리고 덧셈 연산 대신에 논리곱 (AND)이, 뺄셈 연산을 논리합 (OR)으로 사용했었다.
  • 유한 오토마타에서 받아들이는 정규 언어를 나타내는 정규식 찾기(클레이니 알고리즘, 플로이드-워셜 알고리즘의 밀접한 일반화)[11]
  • 행렬역행렬 (가우스 요르단 알고리즘) [12]
  • 최적 라우팅. 이 적용에서는 두 꼭짓점 간의 최대 흐름을 찾는것이 목표이다. 즉, 위의 의사코드에서 최솟값을 적용하는 것이 아니라 최댓값을 적용해야 한다는 것이다. 변의 가중치는 흐름의 제약을 나타낸다. 경로의 가중치는 병목을 나타내므로 위의 덧셈 연산은 뺄셈 연산으로 대체한다.
  • Pathfinder network의 빠른 계산.
  • 최대 폭 경로/최대 대역폭 경로
  • difference bound matrices (DBMs)의 표준식 계산
  • 그래프 간의 유사도 계산

수행

플로이드-워셜 알고리즘은 많은 프로그래밍 언어에서 수행할 수 있다.

다른 최단 경로 알고리즘과 비교

플로이드-워셜 알고리즘은 거의 대부분이나 모든 꼭짓점 쌍이 변으로 연결된 밀집 그래프에서 모든 꼭짓점 쌍 간의 경로를 계산할 때 적합하다. 음수 가중치가 없는 희소 그래프에서는 모든 꼭짓점에서 출발하는 데이크스트라 알고리즘이 반복 데이크스트라의 수행 시간(피보나치 힙을 사용하면 )은 에 비해 무시할 수 있을 정도면 플로이드-워셜 알고리즘의 수행 시간인 보다 적으므로 더 적합하다. 희소 그래프에서 음수 가중치가 있지만 음수 사이클은 없는 경우에는 존슨 알고리즘은 수행 시간이 반복 데이크스트라 시간과 같으므로 사용할 수 있다.

알려진 알고리즘 중에는 밀집 그래프에서 모든 쌍의 최단 경로 계산을 빠르게 하기 위해 빠른 행렬 곱셈을 사용하는 알고리즘도 있지만, 이러한 알고리즘들은 변의 가중치에 추가적인 제약 조건이 따른다(예를 들면 작은 정수여야 한다라는 식으로).[13][14] 게다가, 수행 시간에서 큰 상수 인자 때문에, 이런 알고리즘들은 매우 큰 그래프에서만 플로이드-워셜 알고리즘보다 빠른 속도를 낼 수 있다.

참고 문헌

  1. Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L. (1990). 《Introduction to Algorithms》 1판. MIT Press and McGraw-Hill. ISBN 0-262-03141-8.  See in particular Section 26.2, "The Floyd-Warshall algorithm", pp. 558-565 and Section 26.4, "A general framework for solving path problems in directed graphs", pp. 570-576.
  2. Kenneth H. Rosen (2003). 《Discrete Mathematics and Its Applications, 5th Edition》. Addison Wesley. ISBN 0-07-119881-4. 
  3. Floyd, Robert W. (June 1962). “Algorithm 97: Shortest Path”. 《Communications of the ACM5 (6): 345. doi:10.1145/367766.368168. 
  4. Roy, Bernard (1959). “Transitivite et connexite.”. 《C. R. Acad. Sci. Paris249: 216–218. 
  5. Warshall, Stephen (January 1962). “A theorem on Boolean matrices”. 《Journal of the ACM9 (1): 11–12. doi:10.1145/321105.321107. 
  6. Weisstein, Eric Wolfgang. “Floyd-Warshall Algorithm”. 《Wolfram MathWorld》 (영어). Wolfram Research. 
  7. Kleene, S. C. (1956). 〈Representation of events in nerve nets and finite automata〉. C. E. Shannon and J. McCarthy. 《Automata Studies》. Princeton University Press. 3–42쪽. 
  8. Ingerman, Peter Z. (November 1962). “Algorithm 141: Path Matrix”. 《Communications of the ACM5 (11): 556. doi:10.1145/368996.369016. 
  9. Hochbaum, Dorit (2014). “Section 8.9: Floyd-Warshall algorithm for all pairs shortest paths” (PDF). 《Lecture Notes for IEOR 266: Graph Algorithms and Network Flows》. Department of Industrial Engineering and Operations Research, University of California, Berkeley. 
  10. Stefan Hougardy (April 2010). “The Floyd-Warshall algorithm on graphs with negative cycles”. 《Information Processing Letters》 110 (8-9): 279–281. doi:10.1016/j.ipl.2010.02.001. 
  11. Gross, Jonathan L.; Yellen, Jay (2003), 《Handbook of Graph Theory》, Discrete Mathematics and Its Applications, CRC Press, 65쪽, ISBN 9780203490204 .
  12. Penaloza, Rafael. “Algebraic Structures for Transitive Closure”. 
  13. Zwick, Uri (May 2002), “All pairs shortest paths using bridging sets and rectangular matrix multiplication”, 《Journal of the ACM49 (3): 289–317, arXiv:cs/0008011, doi:10.1145/567112.567114 .
  14. Chan, Timothy M. (January 2010), “More algorithms for all-pairs shortest paths in weighted graphs”, 《SIAM Journal on Computing39 (5): 2075–2089, doi:10.1137/08071990x .

외부 링크