最短路径
我们把边带有权值的图称为带权图,边的权值可以表示
两点之间的距离。
一张图中任意两点间会有不同的路径相连。
最短路径就是指连接两点的这些路径中最短的一条。
有一点需要特别注意:边的权值可以为负。当出现负边权时,有些算法不适用。
dis[u][v]表示从u到v最短路径长度,w[u][v]表示连接u,v的边的长度。
Floyd-Warshall算法
简称Floyd(弗洛伊德)算法,是最简单的最短路径算法,可以计算图中任意两点间的最短路径。
Floyed的时间复杂度是O (n^3),适用于出现负边权的情况。
算法思想:
考虑任意两点(u和v)间的所有可能路径情况:
u直接到达v {u,v}。
u经过其他点到达v{u, vi, ……, v}。
算法策略:
如何穷尽Vi 到 Vj 的所有可能存在的路径?
可能的路径情况:
如果Vi 和Vj 邻接,{Vi , Vj }
通过一个其他顶点中转,表示为{Vi , Vk , Vj } ,其中k≠i、j。
通过两个其他顶点中转,可表示为
{Vi , Vk , Vl, Vj },其中k、l 与i、j均不等。
依次类推, Vi 到Vj 的路径最多可能通过n-2 个顶点中转可达,即除了路径的起点、终点,其他顶点都可能出现在该路径中。
Floyd算法设计:
1.利用矩阵完成路径检测过程
矩阵D(i) 包含中转顶点不超过 i(即中转顶点从0到 i) 的所有最短路径长度。
矩阵D(0) D(0)[i][j]:从V[i]到V[j],中间顶点可以是V[0]的最短路径长度。V[0]因为不确定是从0开始的还是从1开始的第一个点。所以用V[0]代替。
矩阵D(k) D(k)[i][j]:从V[i]到V[j],中间顶点序号不大于k的(即从V[0]到k)最短路径长度。
不断更新矩阵D。
……
直到最后D(n-1) D (n-1)[i][j]: 从V[I]到V[j]的最短路径长度。
2.路径长度矩阵序列求解过程
首先,设置初始状态:D(-1)(邻接矩阵)
然后,通过递推,求解每一个D(i)
矩阵D(k-1) --------> 矩阵D(k)过程
若不存在以k为中转点的vi到vj的最短路径:
D(k)[i][j] = D(k-1)[i][j];
若存在以k的vi到vj的最短路径:
D(k)[i][j] = D(k-1)[i][k] + D(k-1)[k][j];
3.如何记录最短路径的顶点序列?
用矩阵P(二维数组)来存储路径
P[i][j]存储当前最短路径更新时的中转点
最后以递归的方法来输出整个路径
大致代码
void Floyd( )
{
for( i=1; i<=n; ++i)
for(j=1; j<=n; ++j)
{
D[i][j] = G[i][j]; //第一步,初始化:用图G 的邻接矩阵初始化二维数组D。二维数组P 的元素初始化。
P[i][j] = -1;
}
for(k = 1; k<=n; ++k)
for(i=1; i<=n; ++i)
for(j=1; j<=n; ++j)
if(D[i][j] >D[i][k] + D[k][j] )//第二步,对每个顶点Vk,若D[i][k]+D[k][j]< D[i][j],则用D[i][k]+D[k][j]更新D[i][j],并且修改P[i][j]为k。
{
D[i][j] = D[i][k] + D[k][j] ; P[i][j] = k;
}
}
利用数组P进行递归输出各条最短路径:
比如,输出B到A的路径:
因为P[2][1]为4,说明这条路径经过了4号顶点,即:顶点D。因此它是由B到D的最短路和D到A的最短路构成;
同样根据P[2][4]是3,可知它由B到C的最短路和C到D最短路构成,以此类推,因此通过递归就可以输出该条路径。
void print(int i, int j)
{
if (P[i][j] == -1) return;
print( i, P[i][j]); //前半段路
cout << P[i][j]<<"->" ;
print( P[i][j] , j); //后半段路
}