问题引入:
求任意两个城市之间的最短路程。
上图中有4个城市8条公路,公路上的数字表示这条公路的长短。(公路是单向的,就这么规定的)
我们现在需要求两个城市之间的最短路径,也就是求任意两个点之间的最短路径。这个问题也被成为“多源最短路径”问题。
二维数组存储图的信息:
e[i][j]的值表示i点到j点的距离。图片如下。
通过之前的学习,我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。所以进行n^2遍深度或广度优先搜索,即对每两个点都进行一次深度或广度优先搜索,便可以求得任意两点之间的最短路径。
如果要让任意两点(例如从顶点a到顶点b)之间的路程变短,只能引入第三个k(甚至更多点),并通过这个顶点k中转即a -> k -> b,才可能缩短a到b的路程。k = 1,2,...,n。
举个栗子:4号城市到3号城市的路程e[4][3]原本是12,如果只通过1号城市中转:路程将缩短为11(e[4][1] + e[1][3] = 5 + 6 = 11)。其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(e[1][2] + e[2][3] = 5)。所以!4号城市到3号城市的路程将缩短为10(e[4][1] + e[1][3] = 5 + 5 = 10)!
当然啦,当任意两点之间不允许经过第三个点时,这些城市之间的最短路程就是初试路程。
循序渐进,假如现在只允许经1号顶点进行中转:
// 经过1号顶点
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(e[i][j] > e[i][1] + e[1][j]) e[i][j] = e[i][1] + e[1][j];
}
}
则任意两点之间的最短的距离更新为
通过上图我们可以发现:在只通过1号顶点中转的情况下,3号顶点到2号顶点(e[3][2])、4号顶点到2号顶点(e[4][2])以及4号顶点到3号顶点(e[4][3])的路程变短了。
以此类推,经过k号顶点路程会有更多路程变短!
直接上代码:
// 经过1号顶点
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(e[i][1] + e[1][j] < e[i][j]) e[i][j] = e[i][1] + e[1][j];
}
}
// 经过2号顶点
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(e[i][2] + e[2][j] < e[i][j]) e[i][j] = e[i][2] + e[2][j];
}
}
//类推:经过k号顶点(k = 1,2,...,n)
for(k = 1 ; k <= n ; k ++)
{
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(e[i][k] + e[k][j] < e[i][j]) e[i][j] = e[i][k] + e[k][j];
}
}
}
核心代码:
//类推:经过k号顶点(k = 1,2,...,n)
for(k = 1 ; k <= n ; k ++)
{
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(e[i][k] + e[k][j] < e[i][j]) e[i][j] = e[i][k] + e[k][j];
}
}
}
这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转......允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。其实这是一种“动态规划”的思想。
完整代码:
#include<bits/stdc++.h>
using namespace std;
int e[101][101];
int main()
{
int k,i,j;
int n,m;
int t1,t2,t3;
int inf = 99999999; // 用inf(infinity的缩写)存储一个我们认为的正无穷值
//读入n和m,n表示顶点个数,m表示边的条数
cin >> n >> m;
//初始化
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(i == j) e[i][j] = 0;
else e[i][j] = inf;
}
}
//读入边
for(i = 1 ; i <= m ; i ++)
{
cin >> t1 >> t2 >> t3;
e[t1][t2] = t3;
}
//FLoyd-Warshall算法核心语句
for(k = 1 ; k <= n ; k ++)
{
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
if(e[i][k] + e[k][j] < e[i][j]) e[i][j] = e[i][k] + e[k][j];
}
}
}
//输出最终的结果
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= n ; j ++)
{
printf("%10d",e[i][j]);
}
puts(" ");
}
return 0;
}
注意!:
(1)通过这种方法我们可以求出任意两个点之间的最短路径。它的时间复杂度是O(N^3)。如果时间复杂度要求不高,使用FLoyd-Warshall来求指定两点之间的最短路径或者指定一个点到其余各个顶点的最短路径也是可行的。
(2)FLoyd-Warshall算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路径。例如下面这个图就不存在1号顶点到3号顶点的最短路径,因为1-->2-->3-->1-->2-->3-->...1-->2-->3这样路径中,没绕过一次1-->2-->3这样的环,最短路径就会减少1,永远找不到最短路径。其实如果一个图中带有“负权回路”,那么这个图则没有最短路径。