14*:图的应用(最短路径问题):(迪杰斯特拉(Dijkstra)算法、弗洛伊德(Floyd)算法)
问题
迪杰斯特拉(Dijkstra)算法
/*用于存储最短路径下标的数组*/ typedef int Patharc[MAXVEX]; /*用于存储到各点最短路径权值的和*/ typedef int ShortPathTable[MAXVEX];
弗洛伊德(Floyd)算法
//Patharc 父节点和 ShortPathTable 最短路径 都是二维数组; typedef int Patharc[MAXVEX][MAXVEX]; typedef int ShortPathTable[MAXVEX][MAXVEX];
目录
1:迪杰斯特拉(Dijkstra)算法
2:弗洛伊德(Floyd)算法
预备
正文
最短路径
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径
不用覆盖所有的点
1:迪杰斯特拉(Dijkstra)算法
Dijkstra 的时间复杂度是 O(n^2)
1:算法特点:
迪科斯彻算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。
2:算法的思路:
如何求得这些路径?迪杰斯特拉(Dijkstra)提出了一个按路径长度递增的次序产生最短路径的算法。首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从顶点 v 到其它各顶点的最短路径全部求出为止。
解决步骤描述:
- 设置辅助数组dist。它的每一个分量dist[i]表示当前找到的从源点 v0到终点 vi的最短路径的长度;
- 初始状态:
2.1. 若从源点 v0 到顶点 vi有边:dist[i]为该边上的权值;
2.2. 若从源点 v0 到顶点 vi无边:dist[i]为∞。
根据以上描述,可以得到如下描述的算法:
假设用带权的邻接矩阵Edge[i][j]表示边(vi,vj)上的权值。若(vi,vj)不存在,则置Edge[i][j]为∞。S为已.找到从v出发的最短路径的终点的集合,它的初始状态为空集。
1.初始化: S ← {v0 };dist[j] ← Edge[0][j], j = 1, 2, …, n-1;
2.找出最短路径所对应的点 K:dist[k] == min { dist[i] }, i ∈ V- S ;S ← S U { k };
3.对于每一个 i ∈ V- S 修改:dist[i] ← min{ dist[i],dist[k] + Edge[k][i] };
4.判断:若 S = V, 则算法结束,否则转2。
3:执行步骤分析
把图用邻接矩阵存储起来
V0→V1→V2->V4->V3->V6->V7->V8
4:分步解释
1:从源点到其余各顶点的最短路径问题。
2:从V0开始找,找到V1,V0->V1
从V0开始向后依次(从未达到顶点中找,避免形成循环路径)找与V0连接的顶点:V1和V2,路径权重和如下:
V0->V1:1
V0->V2:5
比较大小,取最短路径:V0->V1
3:从V1开始找到与V1向后连接的点,找到最短路径。V0→V1→V2
从V1开始向后依次(从未达到顶点中找,避免形成循环路径)找与V1连接的顶点:V2、V3、V4,路径权重和如下:
V0→V1→V2=1+3=4,
V0→V1→ V3=1+7=8,
V0→V1→V4=1+5=6。
比较路径权重和的大小,取最短路径:V0→V1→V2
现在,我问v0到v2的最短距离,如果你不假思索地说是5,那就犯错了。因为边上都有权值,刚才已经有V0→V1→V2的结果是4,比5还要小1个单位,它才是最短距离,如下图所示。
从V2开始向后依次(从未达到顶点中找,避免形成循环路径)找与V2连接的顶点:V4、V5,路径权重和如下:
V0→V1→V2->V4=1+3+1=5,
V0→V1→V2->V5=1+3+7=11,
比较路径权重和的大小,取最短路径:V0→V1→V2->V4
4:从V4开始找到与V4向后连接的点,找到最短路径。V0→V1→V2->V4->V3
从V4开始向后依次向后依次(从未达到顶点中找,避免形成循环路径)找与V4连接的顶点:V3、V6、V7、V5,路径权重和如下:
V0→V1→V2->V4->V3=1+3+1+2=7,
V0→V1→V2->V4->V6=1+3+1+6=11,
V0→V1→V2->V4->V7=1+3+1+9=14,
V0→V1→V2->V4->V5=1+3+1+3=8,
比较路径权重和的大小,取最短路径:V0→V1→V2->V4->V3
5:从V3开始找到与V3向后连接的点,找到最短路径。V0→V1→V2->V4->V3->V6
从V3开始向后依次向后依次(从未达到顶点中找,避免形成循环路径)找与V3连接的顶点:V6,路径权重和如下:
V0→V1→V2->V4->V3->V6=1+3+1+2+3=10,
比较路径权重和的大小,取最短路径:V0→V1→V2->V4->V3->V6
6:从V6开始找到与V6向后连接的点,找到最短路径。V0→V1→V2->V4->V3->V6->V7
从V6开始向后依次向后依次(从未达到顶点中找,避免形成循环路径)找与V6连接的顶点:V7、V8,路径权重和如下:
V0→V1→V2->V4->V3->V6->V7=1+3+1+2+3+2=12,
V0→V1→V2->V4->V3->V6->V8=1+3+1+2+3+7=17,
比较路径权重和的大小,取最短路径:V0→V1→V2->V4->V3->V6->V7
7:从V7开始找到与V7向后连接的点,找到最短路径。V0→V1→V2->V4->V3->V6->V7->V8
从V6开始向后依次向后依次(从未达到顶点中找,避免形成循环路径)找与V6连接的顶点:V8,路径权重和如下:
V0→V1→V2->V4->V3->V6->V7->V8=1+3+1+2+3+2+4=16,
比较路径权重和的大小,取最短路径:V0→V1→V2->V4->V3->V6->V7->V8
1:首先初始化三个数组
//V0到V0的路路径为0 最短路路径-Dijkstra 算法代码分析 从0开始 (*D)[v0] = 0; //V0到V0 是没有路路径的. final[v0] = 1; //V0到V0是没有路路径的.所以-1; (*P)[v0] = -1;
代码实现:
#include "stdio.h" #include "stdlib.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXEDGE 20 #define MAXVEX 20 #define INFINITYC 65535 typedef int Status; typedef struct { int vexs[MAXVEX]; int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; }MGraph; /*用于存储最短路径下标的数组*/ typedef int Patharc[MAXVEX]; /*用于存储到各点最短路径权值的和*/ typedef int ShortPathTable[MAXVEX]; /*10.1 创建邻近矩阵*/ void CreateMGraph(MGraph *G) { int i, j; G->numEdges=16; G->numVertexes=9; for (i = 0; i < G->numVertexes; i++) { G->vexs[i]=i; } for (i = 0; i < G->numVertexes; i++) { for ( j = 0; j < G->numVertexes; j++) { if (i==j) G->arc[i][j]=0; else G->arc[i][j] = G->arc[j][i] = INFINITYC; } } G->arc[0][1]=1; G->arc[0][2]=5; G->arc[1][2]=3; G->arc[1][3]=7; G->arc[1][4]=5; G->arc[2][4]=1; G->arc[2][5]=7; G->arc[3][4]=2; G->arc[3][6]=3; G->arc[4][5]=3; G->arc[4][6]=6; G->arc[4][7]=9; G->arc[5][7]=5; G->arc[6][7]=2; G->arc[6][8]=7; G->arc[7][8]=4; for(i = 0; i < G->numVertexes; i++) { for(j = i; j < G->numVertexes; j++) { G->arc[j][i] =G->arc[i][j]; } } } /*10.2 求得网图中2点间最短路径 Dijkstra 算法 G: 网图; v0: V0开始的顶点; p[v]: 前驱顶点下标; D[v]: 表示从V0到V的最短路径长度和; */ void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D) { int v,w,k,min; k = 0; /*final[w] = 1 表示求得顶点V0~Vw的最短路径*/ int final[MAXVEX]; /*1.初始化数据*/ for(v=0; v<G.numVertexes; v++) { //全部顶点初始化为未知最短路径状态0 final[v] = 0; //将与V0 点有连线的顶点最短路径值; (*D)[v] = G.arc[v0][v]; //初始化路径数组p = 0; (*P)[v] = 0; } //V0到V0的路径为0 (*D)[v0] = 0; //V0到V0 是没有路径的. final[v0] = 1; //v0到V0是没有路径的 (*P)[v0] = -1; //2. 开始主循环,每次求得V0到某个顶点的最短路径 for(v=1; v<G.numVertexes; v++) { //当前所知距离V0顶点最近的距离 min=INFINITYC; /*3.寻找离V0最近的顶点*/ for(w=0; w<G.numVertexes; w++) { if(!final[w] && (*D)[w]<min) { k=w; //w顶点距离V0顶点更近 min = (*D)[w]; } } //将目前找到最近的顶点置为1; final[k] = 1; /*4.把刚刚找到v0到v1最短路径的基础上,对于v1 与 其他顶点的边进行计算,得到v0与它们的当前最短距离;*/ for(w=0; w<G.numVertexes; w++) { //如果经过v顶点的路径比现在这条路径长度短,则更新 if(!final[w] && (min + G.arc[k][w]<(*D)[w])) { //找到更短路径, 则修改D[W],P[W] //修改当前路径的长度 (*D)[w] = min + G.arc[k][w]; (*P)[w]=k; } } } } int main(void) { printf("最短路径-Dijkstra算法\n"); int i,j,v0; MGraph G; Patharc P; ShortPathTable D; v0=0; CreateMGraph(&G); ShortestPath_Dijkstra(G, v0, &P, &D); printf("最短路径路线:\n"); for(i=1;i<G.numVertexes;++i) { printf("v%d -> v%d : ",v0,i); j=i; while(P[j]!=-1) { printf("%d ",P[j]); j=P[j]; } printf("\n"); } printf("\n最短路径权值和\n"); for(i=1;i<G.numVertexes;++i) printf("v%d -> v%d : %d \n",G.vexs[0],G.vexs[i],D[i]); printf("\n"); return 0; }
2:弗洛伊德(Floyd)算法)
1:定义
所有顶点之间的最短路径:已知一个各边权值均大于0的带权有向图,对每一对顶点 vi≠vj,要求求出vi与vj之间的最短路径和最短路径长度。
解决这个问题的一个方法是:每次以一个顶点为源点,重复执行迪杰斯特拉算法n次。这样,便可求得每一对顶点之间的最短路径。总的执行时间为O(n3)。虽然能实现,但是明显实现起来比较复杂。
下面介绍一下由弗洛伊德提出的另一个算法。这个算法的时间复杂度也是O(n3),但形势上简单些。
其中:A(-1)[i][j] = Edge[i][j]
A(k) [i][j] = min { A(k-1)[i][j],A(k-1)[i][k] + A(k-1)[k][j]}
k = 0, 1, …, n-1
A(0)[i][j]是从顶点vi到vj, 中间顶点是v0的最短路径的长度;
A(k) [i][j]是从顶点vi 到vj, 中间顶点的序号不大于k的最短路径的长度;
A(n-1)[i]j]是从顶点 vi 到 vj的最短路径长度。
至此,我们的最短路径就算是完成了,你可以看到矩阵第v0行的数值与迪杰斯特拉(Dijkstra)算法求得的D数组的数值是完全相同,都是{0,1,4,7,5,8,10,12,16}。而且这里是所有顶点到所有顶点的最短路径权值和都可以计算出。
那么如何由P这个路径数组得出具体的最短路径呢?以v0到v8为例,从上图的右图第v8列,P[0][8]=1,得到要经过顶点v1,然后将1取代0得到P[1][8]=2,说明要经过v2,然后将2取代1得到P[2][8]=4,说明要经过v4,然后将4取代2得到P[4][8]=3,说明要经过v3,……,这样很容易就推导出最终的最短路径值为v0→v1→v2→v4→v3→v6→v7→v8。
1:从任意顶点i到任意顶点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个顶点k到j。
Dis(i,j)
为顶点u到顶点v的最短路径的距离,对于每一个顶点k,算法检查Dis(i,k) + Dis(k,j) < Dis(i,j)
是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,便设置Dis(i,j) = Dis(i,k) + Dis(k,j)
,这样一来,Dis(i,j)
中记录的便是i到j的最短路径的距离。//Patharc 和 ShortPathTable 都是二维数组; typedef int Patharc[MAXVEX][MAXVEX]; typedef int ShortPathTable[MAXVEX][MAXVEX]; void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D){ int v,w,k; /* 1. 初始化D与P 矩阵*/ for(v=0; v<G.numVertexes; ++v){ for(w=0; w<G.numVertexes; ++w){ /* D[v][w]值即为对应点间的权值 */ (*D)[v][w]=G.arc[v][w]; /* 初始化P P[v][w] = w*/ (*P)[v][w]=w; } } //2.K表示经过的中转顶点 for(k=0; k<G.numVertexes; ++k){ for(v=0; v<G.numVertexes; ++v){ for(w=0; w<G.numVertexes; ++w){ /*如果经过下标为k顶点路径比原两点间路径更短 */ if ((*D)[v][w]>(*D)[v][k]+(*D)[k][w]){ /* 将当前两点间权值设为更小的一个 */ (*D)[v][w]=(*D)[v][k]+(*D)[k][w]; /* 路径设置为经过下标为k的顶点 */ (*P)[v][w]=(*P)[v][k]; } } } } } int main(void){ printf("Hello,最短路径弗洛伊德Floyd算法"); int v,w,k; MGraph G; Patharc P; ShortPathTable D; /* 求某点到其余各点的最短路径 */ CreateMGraph(&G); ShortestPath_Floyd(G,&P,&D); //打印所有可能的顶点之间的最短路径以及路线值 printf("各顶点间最短路径如下:\n"); for(v=0; v<G.numVertexes; ++v){ for(w=v+1; w<G.numVertexes; w++){ printf("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"); } //打印最终变换后的最短路径D数组 printf("最短路径D数组\n"); for(v=0; v<G.numVertexes; ++v){ for(w=0; w<G.numVertexes; ++w){ printf("%d\t",D[v][w]); } printf("\n"); } //打印最终变换后的最短路径P数组 printf("最短路径P数组\n"); for(v=0; v<G.numVertexes; ++v){ for(w=0; w<G.numVertexes; ++w){ printf("%d ",P[v][w]); } printf("\n"); } return 0; }
引用
1:图的应用[最短路径Dijkstra算法+Floyd算法]