非网图的最小路径就是指两顶点之间经过的边数最小的路径;而对网图来说,最短路径,是指梁鼎点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点为源点,最后一个顶点为终点。
下面讲解两种求最短路径的算法,分别为:迪杰斯特拉(Dijkstra)算法和弗洛伊德(Floyd)算法,具体介绍如下:
迪杰斯特拉算法并不是一下求出 v0 到 v8 的最短路径,而是一步步求出它们之间顶点的最短路径,过程中基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到结果。具体代码如下所示:
#define MAXVEX 9
#define INFINITY 65535
typedef int Patharc[MAXVEX]; //用于存储最短路径下标的数组
typedef int ShortPathTable[MAXVEX]; //用于存储个点最短路径的权值和
/*Dijkstra算法,求有向网G的顶点v0顶点到其与定点v的最短路径P[v]及带权长度D[v]*/
/*P[v]的值为前驱顶点下标,D[v]表示v0到v的最短路径之和*/
void Dijkstra(MGraph G,int v0,Patharc *P,ShortPathTable *D){
int v,w,k,min;
int final[MAXVEX]; //final[i]=1表示求得顶点v0到vi的最短路径
for(v=0; v//初始化数据
final[v]=0; //全初始化为未知最短路径状态
(*D)[v]=G.arc[v0][v]; //将与v0点有连线的顶点加上权值
(*P)[v]=0; //初始化路径数组
}
(*D)[v0]=0; //v0到v0的路径为0
final[v0]=1; //v0到v0不需要求路径
/*开始主循环,每次求得v0到某个v顶点的最短路径*/
for(v=1; vmin = INFINITY; //当前所知的离v0顶点的最近距离
for(w=0; w//寻找离v0最近的顶点
if((!final[w]) && (*D)[w]< min){ //final为0且发现有更小的值
k=w;
min=(*D)[w];
}
}
final[k]=1; //将目前找到的最近顶点置为1
for(w=0; w//修正当前最短路径及距离,修正其他结点和v0的最小距离。
if((!final[w]) && (min+G.arc[k][w])<(*D)[w]){
(*D)[w]=min+G.arc[k][w];
(*P)[w]=k;
}
}
}
}
详解:准备好上图对应的邻接矩阵, v0 到 v0 自身,权值和结果均为0,D数组为{0,1,5, ∞ , ∞ , ∞ , ∞ , ∞ , ∞ }, v0 算是求到最短路径,所以final[0]=1,此时final数组为{1,0,0,0,0,0,0,0,0},P={0,0,0,0,0,0,0,0,0},初始化工作完成。
在D数组中,D数组中final非1的最小值为1,比较 v0 与其他顶点的边得到最近顶点,所以k=1 ,D={0,1,4,8,6, ∞ , ∞ , ∞ , ∞ },final={1,1,0,0,0,0,0,0,0},P={0,0,1,1,1,0,0,0,0}。
在D数组中,D数组中final非1的最小值为4,比较 v1 与其他顶点的边得到最近顶点,所以k=2,D={0,1,4,8,5,11, ∞ , ∞ , ∞ },final={1,1,1,0,0,0,0,0,0},P={0,0,1,1,2,2,0,0,0}。
在D数组中,D数组中final非1的最小值为5,比较 v2 与其他顶点的边得到最近顶点,所以k=4,D={0,1,4,7,5,8,11,14, ∞ },final={1,1,1,0,1,0,0,0,0},P={0,0,1,4,2,4,4,4,0}。
在D数组中,D数组中final非1的最小值为7,比较 v4 与其他顶点的边得到最近顶点,所以k=3 ,D={0,1,4,7,5,8,10,14, ∞ },final={1,1,1,1,1,0,0,0,0},P={0,0,1,4,2,4,3,4,0}。
在D数组中,D数组中final非1的最小值为10,比较 v3 与其他顶点的边得到最近顶点,所以k=6,D={0,1,4,7,5,8,10,12,17},final={1,1,1,1,1,0,1,0,0},P={0,0,1,4,2,4,3,6,6}。
在D数组中,D数组中final非1的最小值为12,比较 v6 与其他顶点的边得到最近顶点,所以k=7,D={0,1,4,7,5,8,10,12,16},final={1,1,1,1,1,0,1,1,0},P={0,0,1,4,2,4,3,6,7}。
先定义两个二维数组D[n][n]和P[n][n],D代表顶点到顶点的最短路径权值,P代表对应顶点的最小路径的前驱矩阵。在未分析任何顶点前,D命名为 D−1 ,实质上为初始的图的邻接矩阵,P命名为 P−1 。
D0[v][w]=minD−1[v][w],D−1[v][0]+D−1[0][w]
具体代码如下所示:
typedef int Pathmatirx[MAXVEX][MAXVEX]; //用于存储最短路径下标的数组
//用于存储个点最短路径的权值和
typedef int ShortPathTable[MAXVEX][MAXVEX];
/*Floyd算法,求有向网G的各顶点v到其其余顶点w的最短路径P[v][w]及带权长度D[v][w]*/
void ShortestPath_Floyd(MGraph G,Pathmatirx *P,ShortPathTable *D){
int v,w,k;
for(v=0; v<G.numVertexes; ++v){ //初始化数据
for(w=0; w<G.numVertexes; ++w){
(*D)[v][w]=G.matirx[v][w]; //(*D)[v][w]表示对应点间的权值
(*P)[v][w]=w; //初始化P
}
}
for(k=0; k<G.numVertexes; ++k){ //三层嵌套 O(n^3)
for(v=0; v<G.numVertexes ; ++v){
for(w=0; w<G.numVertexes ; ++w){
if((*D)[v][w] > ((*D)[v][k]+(*D)[k][w])){
/*如果经过下标为k顶点路径比原来两点间路径更短,将当前两点间权值设为更小的一个*/
(*D)[v][w]=(*D)[v][k]+(*D)[k][w]);
(*P)[v][w]=(*P)[v][k]; //路径设置经过下标为k的顶点
}
}
}
}
}
详解:初始化上图的邻接矩阵D和矩阵P, 当K=0时,即所有顶点都经过 v0 ,计算是否有最短路径的变化,最后发现没有任何变化。
当K=1时,即所有顶点都经过 v1 ,计算是否有最短路径的变化,当v=0时,原本D[0][2]=5,现在由于D[0][1]+D[1][2]=4,同理D[0][3]=8,D[0][4]=6在路径矩阵P上也要做处理,将其改为当前的P[v][k]。
当K=2时一直到8结束,表示针对每个顶点做中转得到的结果, D0 是以 D−1 为基础, D1 是以 D0 为基础,……, D8 是以 D7 为基础,相互联系,路径矩阵P也是如此。
求最短路径的显示代码为:
for(v=0; vfor(w=v+1; wprintf("v%d-v%d weight:%d",v,w,D[v][w]);
k=P[v][w];
printf("path:%d",v);
while(k!=w){
printf("-> %d",k);
k=P[k][w];
}
printf("-> %d\n",w);
}
printf("\n");
}
迪杰斯特拉算法的时间复杂度为 O(n2) ,弗洛伊德算法的时间复杂度为 O(n3) ,若面临要求所有顶点至所有顶点的最短路径问题时,弗洛伊德(Floyd)算法是个不错的选择。