一、算法介绍:
Floyd–Warshall(简称Floyd算法)是一种著名的解决任意两点间的最短路径(All Paris Shortest Paths,APSP)的算法。从表面上粗看,Floyd算法是一个非常简单的三重循环,而且纯粹的Floyd算法的循环体内的语句也十分简洁。Floyd算法可以说是Warshall算法的扩展,三个for循环就可以解决问题,所以它的时间复杂度为O(n^3)。从本质上说,Floyd是一个动态规划(Dynamic Programming,DP)思想的体现,网上不少文章介绍得很笼统,没有解释清楚基本原理。故在此献上我的想法。
二、Floyd算法:
首先引入一个非常重要的概念:
我们假设Dis(k,i,j)代表着从源点i到终点j的一个最短路径值,且k是该路径中所有点编号的最大值 (而不仅仅只是经过和不经过的问题)。即i到j的路径中所有点的编号都小于等于k。
有了这个概念之后,我们来看看是否能够找出一个迭代关系。
一定的是,对于Dis(k,i,j)的路径中,可能没有包含编号为k的点,也可能包含了编号为k的点,利用这仅有的两种情况可以定义他的迭代关系。
第一种情况下,由于没有经过包含编号k的点,所以Dis(k,i,j) == Dis(k-1,i,j)。
第二种情况下,由于经过了编号为k的点,我们将路径以k点分为两部分Dis(k-1,i,k)和Dis(k-1,k,j),即等式“Dis(k-1,i,k)+Dis(k-1,k,j) == Dis(k,i,j)”成立。由于k作为两部分前者的终点后者的源点,所以两部分中其他的点都应该不大于k-1这个值,所以编号的最大值都是k-1。同等最大编号情况下的两条最短子路径相加必然等于同等最大编号情况下的总路径最小值。
至此,我们在这仅有的两种情况中进行选择,路径更小的就可以更新为源点i终点j且路径中各个点的编号最大值为k的最短路径了( – -真绕口)。即有Dis(k,i,j)=Min{ Dis(k-1,i,k)+Dis(k-1,k,j), Dis(k-1,i,j) }。
现在,我们有了迭代关系,缺少的只有初始条件与结束条件。
对于初始条件,i和j代表各个点,所以取值范围只要在点的编号范围内即可。由于k-1的存在,k的最小值为1。此时有Dis(1,i,j) = Min{ Dis(0,i,1) + Dis(0,1,j), Dis(0,i,j) }。Dis(0,i,j)代表经过点编号最大值为0的点,显然不存在这样的编号,那么Dis(0,i,j)就可以顺理成章地表示为i到j不经过任何中间点的直接距离,也就是我们会输入的邻接矩阵的值。
对于结束条件,当k达到编号的最大值时,Dis(k,i,j)表示的就是经过点编号最大值为所有编号的最大值的路径,此时也就是源点i到终点j的最大值了,因为k为最大值已经包含所有的点了。
这样一来,我们就有了完整的递推关系。要求任意点到任意点的最短距离就是当k值为最大值时候的Dis(k,i,j)了。
整个过程梳理一下:
1、用Dis[0,i,j]记录每一对顶点的最短距离。
2、依次扫描每一个点,并以该顶点的编号值为k再遍历所有每一对顶点Dis(k,i,j)的值,看看是否可用过该顶点的路径让这对顶点间的距离更小。
for(int k = 0; k < N; k++)
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N; j++)
d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
PS:1.整个算法中k-1代表的不是数字意义上的-1而是编号的更小一位。如编号分别为(1,10,100,1000)当k代表编号1000时,k-1代表编号100。根据实际情况为准。
2.由于计算到Dis(k,i,j)时, 任意顶点间的路径不大于k-1的值已经全部求出(k是递增的),所以可以保存该算法的正确性。
三、例题:
http://poj.org/problem?id=2139
题意:奶牛们最近要拍电影了……(黑白电影么)
1、若两个的奶牛一起拍过电影,则他们之间的距离为1;
2、若两只奶牛a、b没有一起拍过电影,但与另有一只奶牛c都和他们拍过电影,则a、b的距离为2(通过c得到)。
求奶牛的与其他奶牛的距离的平均值的一百倍的整数。
所以我做为例题的题目都是基础题,仅仅是做为算法练手。。
六、Talk is cheap,show me the code(例题代码)
#include <iostream>
using namespace std;
#define INFINITY 301;
void Floyd(int **p, int N, int **path = NULL)
{
int i, j, k;
for (k = 0; k < N; k++)
{
for (i = 0; i < N; i++)
{
for (j = 0; j < N; j++)
{
if (i != j && p[i][j] > p[i][k] + p[k][j])
{
p[i][j] = p[i][k] + p[k][j];
#ifdef PATH
path[i][j] = path[k][j];
#endif
}
}
}
}
}
int main(void)
{
int N, M;
cin >> N >> M;
int **Cows = new int*[N];
int i, j;
for (i = 0; i < N; i++)
{
Cows[i] = new int[N];
for (j = 0; j < N; j++)
{
if (i == j)
Cows[i][j] = 0;
else
Cows[i][j] = INFINITY;
}
}
int *input;
while (M--)
{
int num;
cin >> num;
input = new int[num];
for (i = 0; i < num; i++)
cin >> input[i];
for (i = 0; i < num; i++)
for (j = i + 1; j < num; j++)
Cows[input[i] - 1][input[j] - 1] = Cows[input[j] - 1][input[i] - 1] = 1;
}
//Floyd
Floyd(Cows, N);
/* for (i = 0; i < N; i++) { for (j = 0; j < N; j++) cout << Cows[i][j]<<" "; cout << endl; }*/
int min = N * INFINITY;
for (i = 0; i < N; i++)
{
int sum = 0;
for (j = 0; j < N; j++)
if (i != j)
sum += Cows[i][j];
min = sum < min ? sum : min;
}
cout << min * 100 / (N - 1) << endl;
system("PAUSE");
return 0;
}