一、Floyd-Warshall算法
我第一次接触这个算法是通过《算法导论》,感觉这里写的还是很好的!不愧是MIT
《算法导论》在介绍这个算法之前,写了一个小引子——利用矩阵乘法的思想解决每对顶点间的最短路问题,通过对比这个引子和Floyd-Warshall算法,就会发现有时候自然的规律就是这么简单,和我们普通人稍微动一下脑袋得出的结论其实就差了那么一点儿!
1、引子部分的思想一句话概括
要求i到j的最短路V(i,j),可以看成是求所有的i到j经过N-1条边的路径中最短那一条。
那么我们如何求解i到j经过k条边的最短路呢?
分为两种情况,第一种,i到j经过k条边的最短路等于i到j经过k-1条边的最短路。V(i,j,k) = V(i,j,k-1)。第二种,i到j经过k条边的最短路等于i到其他所有点h经过k-1条边的最短路加上W(h,j)中最短的路径。V(i,j,k) = min{V(i,h,k-1)+W(h,j)}
有了上面的递归式,我们可以顺利地写出程序,甚至说不用写代码也可以知道,这个程序要有4层循环,前两层是i和j,作用是迭代所有顶点,第三层是k,作用是迭代i到j经过k条边<k从1~N-1>,第四次循环h,作用是求i到其他所有点h经过k-1条边的最短路加上W(h,j)的最短那一条。
所以,很容易分析出时间复杂度为O(n^4),暂且不讨论空间复杂度
2、Floyd-Warshall思想一句话概括
要求i到j的最短路V(i,j),可以看成是求i到j的路P上最大节点为N-1的路中最短那一条。
那么如何求解i到j的路P上最大节点为k的最短路呢?
还是分成两种情况:第一种,节点k不在所求的路P上,即所求V(i,j,k) = V(i,j,k-1)。
第二种,节点k在所求的路P上,即所求变为V(i,j,k) = V(i,k,k-1)+V(k,j,k-1)<看到这儿,是不是知道Floyd-Warshall的聪明之处了吧!>
有了上面的递归式,我们还是像之前那样分析,思路还是很清晰的~三层循环就够了,第一层k<1~N>,迭代出路径P上最大点是1~N,后两层循环是i和j,迭代所有点。
时间复杂度为O(n^3),空间复杂度O(n^2)<经过证明,可以直接在原矩阵上进行,所以空间复杂度降为O(n^2)>
参考《算法导论》第25章每对顶点间最短路 Floyd-Warshall算法部分
二、问题描述
POJ 1125:http://poj.org/problem?id=1125
有N个股票经纪人,给出每个经纪人经过i秒可以将一条信息传递给经纪人j,要求从中找出一个经纪人k,以k为起点到达其他经纪人时间最短。
三、问题分析
编程之前需要解决两个问题:
问题一、图的构造?
问题二、单源最短路or每对顶点间最短路以及如何找到经纪人k?
问题一:这道题目图的构造还是不算难的!以每个经纪人为节点,经纪人之间传递信息的时间作为这两个经纪人节点之间边的权重,没有联系的两个经纪人节点之间边权重为INF,这样图就构造出来了。
问题二:这道题目所求就为最有效的起点,所以,起点未知,所以考虑使用每对顶点间的最短路算法,也就是考虑使用Floyd-Warshall算法。注意到Floyd-Warshall算法最后迭代出的矩阵中第i行表示,i到其他所有点的最短路径长度,所以,我们可以通过这个矩阵求出各个点到其他所有点的最短路径长度的最大值,然后取这N个最大值中的最小值,这个最小值即为所求时间,对应的节点就为经纪人k!
四、程序实现
1 #include<iostream> 2 using namespace std; 3 #define LEN 102 4 #define INF 1000000000 5 6 int N;//经纪人数目 7 int values[LEN][LEN]; 8 int MIN,Index; 9 10 void input() 11 { 12 for(int i = 1;i < N+1;i++) 13 for(int j = 1; j < N+1;j++) 14 if(i != j) values[i][j] = INF; 15 else values[i][j] = 0; 16 17 for(int u = 1;u < N+1;u++) 18 { 19 int num = 0; 20 cin >> num; 21 while(num--) 22 { 23 int v = 0; 24 cin >> v ; 25 cin >> values[u][v]; 26 } 27 } 28 } 29 30 void Floyd_Warshall() 31 { 32 for(int k = 1;k < N+1;k++) 33 for(int i = 1;i < N+1;i++) 34 for(int j = 1;j < N+1;j++) 35 if(values[i][j] > values[i][k]+values[k][j]) 36 values[i][j] = values[i][k]+values[k][j]; 37 } 38 39 void find_MaxMin() 40 { 41 MIN = INF;Index = 0; 42 for(int i = 1;i < N+1;i++) 43 { 44 int i_max = -1; 45 for(int j = 1;j < N+1;j++) 46 if(i_max < values[i][j]) 47 i_max = values[i][j]; 48 if(MIN > i_max) 49 { 50 MIN = i_max; 51 Index = i; 52 } 53 } 54 } 55 56 int main() 57 { 58 while(cin >> N) 59 { 60 if(N == 0) break; 61 input(); 62 Floyd_Warshall(); 63 find_MaxMin(); 64 cout << Index <<" "<<MIN << endl; 65 } 66 return 0; 67 }
四、出现问题
问题1——memset函数:
之前,在做android和java web开发,对C++一直使用较少,从开这个博客<也就是开始刷ACM题>的时候才开始使用C++,所以出现各种问题,这个就是其中之一。
在每组测试用例之前,要先将values[数组]设置成无穷大,我第一次使用了以为已经习以为常的memset(values,INF,sizeof(int)*LEN*LEN),结果,debug的时候,发现数据没有被置成INF,后来我发现,除了将INF改为0,会有效以外,其他的情况都不可靠。
后来查了之后才知道,memset函数,执行的时候是针对字节来进行的,所以除了数组清零操作可靠之后,其他初始化还是推荐使用循环来做。
五、感想
自己通过自己独立分析问题,自己独立编程实现,最后这个题目一次就AC,这个节奏还是很好的~
最后,YZY,我想你!~
浙公网安备 33010602011771号