最短路径
对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。
迪杰斯特拉算法
它并不是一下子就求出V0到V8的最短路径,而一步步求出它们之间顶点的最短路径,过程中都是基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到你要的结果。
我们来看一下思路
1. 顶点v0到v1的最短距离,答案是1,路径就是直接v0连线到v1
2. 由于顶点v1还与v2、v3、v4连线,所以
我们同时求得了
v0->v1->v2=1+3=4,
v0->v1->v3=1+7=8
v0->v1->v4=1+5=6
现在,问v0到v2的最短距离是?答案是4
由于顶点v2还与v4、v5连线,所以此时我们同时求得v0->v2->v4 = 4+1=5
v0->v2->v5=4+7=11。这里v0->v2我们用的是刚才计算出来 的较小的4。此时我们也发现v0->v1->v2->v4=5要比v0->v1->v4=6还要小。所以v0到v4目前的最小距离是5。如图
当我们要求v0到v3的最短距离时,通向v3有三条边,除v6没研究过外,
v0->v1->v3=结果是8
v0->v4->v3=5+2=7。因此v0到v3的最短距离是7
我们来看一下代码模拟运行
#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 ShortesPath_Dijkstra(MGraph G,int v0,Patharc *P,ShortPathTable *D)
{
int v,w,k,min;
int final[MAXVEX]; //final[w]=1表示顶点v0到vw的最短路径
for(v=0;v<G.numVertexes;v++)
{
final[v]=0; //全部顶点初始化为未知最短路径状态
(*D)[v] = G.arc[v0][v];
(*P)[v] = 0;
}
(*D)[v0]=0;
final[v0]=1;
/**开始主循环,每次求得v0到某个v顶点的最短路径/
for(v=1;v<G.numVertexes;v++)
{
min=INFIITY;
for(w=0;w<G.numvertexes;w++)
{
if(!final[w]&&(*D)[w]<min)
{
k=w;
min = (*D)[w];
}
}
final[k]=1; //将目前找到的最近的顶点置为1
for(w=0;w<G.numVertexes;w++)
{
if(!final[w]&&(min+G.arc[k][w]<(*D)[w]))
{
(*D)[w]=min*G.arc[k][w];
(*P)[w] = k;
}
}
}
}
分析过程:
1. 程序开始运行,第4行的final数组是为了v0到某顶点是否已经求得最短路径的标记,如果v0到vw已经有结果,则final[w]=1;
2. 第5~10行,是在对数据进行初始化的工作。此时final数组值均为0,表示所有的点都未求得最短路径。D数组为{65535,1,5,65535,65535,65535,65535,65535}。因为v0与v1和v2的边权值为1和5。P数组全是0,表示目前没有路径。
3. 第11行,表示v0到v0自身,权值和结果为0。D数组为{0,1,5,65535,65535,65535,65535,65535}。第12行,表示v0点算是已经求得最短路径,因此final[0]=1。此时final数组为{1,0,0,0,0,0,0,0,0}此时整个初始化操作完成
4. 第13行~33,为主循环,每次循环求得v0与一个顶点的最短路径。因此v从1而不是0开始。
5. 先令min=65535的极大值,通过w循环,与D[w]比较找到最小值min=1,k=1。
6. 第24行,由k=1,表示与v0最近的顶点是v1,并且由D[1]=1,适量此时v0到v1的最短距离是1。因此将v1对应final[1]设置为1。此时final数组为{1,1,0,0,0,0,0,0,0}
7. 第25~32行是一循环,此循环甚为关键。它的目的是在刚才已经找到的v0到v1的最短路径的基础上,对v1与其它顶点的边进行计算,得到v0与它们的最短距离,如图
因为此时min=1,所以本来的D[2]=5,现在v0->v1->v2=D[2]=min+3=4;
v0->v1->v3=D[3]=min+7=8, v0->v1->v4=D[4]=min+5=6,因此D数组当前值为{0,1,4,8,6,65535, 65535, 65535, 65535}。而P[2]=1,P[3]=1,P[4]=1,它表示意思是v0到v2,v3,v4点的最短路径它们的前驱是v1。此时P数组值为{0,0,1,1,1,0,0,0,0}
8. 重新开始循环,此时v=2。第15~23行,对w循环,注意思为final[0]=1和final1[1]=1,由第18行的!final[w]可知,v0与v1并不参与最小值获取。通过循环比较,找到最小值min=4,k=2。
9. 第24行,由k=2,表示已经求出v0到v2的最短路径,并且由D[2]=4,知道最短距离是4。因此将v2对应的final[2]设置为1,此时final数组为{1,1,1,0,0,0,0,0,0}
10. 第25~32行。在刚才已经找到V0与v2的最短路径的基础上,对v0与其他顶点的边,进行计算,得到v0与它们的当前最短距离,如图。因为min=4,所以本来D[4]=6,现在v0->v2->v4=D[4]=min+1 = 4; v0->v2->v5=D[4]=min+7 = 11,因此D数组当前值为:{0,1,4,8,5,11, 65535, 65535, 65535}。而原来的P[4]=1而现在的P[4]=2,P[5]=2,它表示v0到v4、v5点的最短路径它们的前驱均是v2。此时P数组值为:{0,0,1,1,2,2,0,0,0}。
11. 重新开始循环,此时v=3。第15~23行,得到最小值min=5,k=4。
12. 第24行,由k=4已经求出v0到v4的最短路径,并且由D[4]=5知道最短距离是5。因此将v4对应的final[4]设置为1。此时final数组为{1,1,1,0,1,0,0,0,0}
13. 第25~32行,对v4与其他顶点的边进行计算。得到v0与它们的当前最短距离,因为min=5,所以本来D[3]=8,现在v0->v4->v3=D[3]=min+2=7,本来的D[5]=11,现在的v0->v4->v5=D[5]=min+3=8,另外v0->v4->v6=D[6]=min+6=11,v0->v4->v7=D[7]=min+9=14。因此D数组的当前值为:{0,1,4,7,5,8,11,14,65535}。而原本的P[3]=1,此时P[3]=4,原本的P[5]=2,此时的P[5]=4,另外P[6]=4,P[7]=4,它表示v0到v3、v5、v6、v7点的最短路径它们的前驱是v4。此时P数组的值为:{0,0,1,4,2,4,4,4,0}
14. 之后的循环就是完全类似的了。得到最终结果
final数组{1,1,1,1,1,1,1,1,1},它表示所有顶点均完成了最短路径的查找工作。此时D数组为{0,1,4,7,5,8,10,12,16},它表示v0到各个顶点的最短路径数,
此时的P数组为{0,0,1,4,2,4,3,6,7}。比如P[8]=7它的意思是V0到V8的最短路径,顶点v8的前驱顶点是v7,再由P[7]=6,表示v7的前驱是v6,P[6]=3,前驱是v3。这样就可以得到,v0到v8的最短路径为v8<-v7<-v6<-v3<-v2<-v1<-v0即v0->v1->v2->v4->v3->v6->v7->v8。
其实最终返回的数组D和P,是可以得到V0到任意一个顶点的最短路径和路径长度的。例如v0到v8的最短路径并没有经过v5,但我们已经知道v0到v5的最短路径。D[5]=8,由P[5]可知道它的前驱顶点是v4。所以v0到v5的最短路径是v0->v1->v2->v4->v5。
迪杰斯特拉(Dijkstra)算法解决了从某个源点到其余顶点的最短路径问题。时间复杂度O(n2)
现在如果我想求任决一点到其它点的最短路径。则还得有一个循环来处理复杂度就变成了O(n3)
那么我们再求一种求最短路径的算法如下
弗洛伊德(Floyd),它求所有顶点到所有顶点的时间复杂度也是O(n3),但其算法非常简洁优雅。
弗洛伊德(Floyd)算法
我们先来看一个简单的安全如下图的3个顶点的连通网图。
我们先定义两个二维数组D[3][3]和P[3][3],D代表顶点到顶点的最短路径权值
P代表对应顶点的最小路径前驱矩阵。
在未分析任何顶点前,我们将D命名为D-1其实它就是初始的图的邻接矩阵。将P命名为P-1,初始化为图中所示的矩阵
首先我们来分析,所有顶点经过v0后到达另一顶点的最短路径。因为只有三个顶点,因此需要查看v1->v0->v2,得到D-1[1][0]+D-1[0][2]=2+1=3。
D-1[1][2]表示的是v1->v2的权值为5,我们发现D-1[1][0]>D-1[1][0]+D-1[0][2]。通俗的说就是v1->v0->v2比直接v1->v2的距离还要近。所以我们就让D-1[1][2]=D-1[1][0]+D-1[0][2]=3,同样的D-1[2][1]也等于3。于是就有了D0的矩阵
因为有了变化,所以P矩阵对应的P-1[1][2]和P-1[2][1]也修改为当前中转的顶点v0的下标0,于是就有了P0看上图的转换过程
接下来有了D0和P0的基础上继续处理所有顶点经过v1和v2后到另一顶点的最短路径,得到D1和P1、D2和P2完成所有顶点 到所有顶点的最短路径计算工作
下面来个复杂的图的推演
代码如下
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(w=0;v<G.numVertexes;++v)
{
for(w=0;w<G.numVertexes;++w)
{
(*D)[v][w] = G.matirx[v][w]; //初始化,即为对应点间的权值
(*P)[v][w] = w; //初始化P
}
}
for(k=0;k<G.numVertexes;++k)
{
for(v=0;v<G.numVertexes;++v)
{
for(w=0;w<G.numVertexes;++w)
{
if((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
{
(*D)[v][w] = (*D)[v][k]+(*D)[k][w];
(*P)[v][w]=(*P)[v][k];
}
}
}
}
}
1. 程序开始运行,第4~11行就是初始化D和P,使得它们成为下图的两个矩阵。从矩阵也得到v0->v1路权值是1,v0->v2路权值是5,v0->v3无边连线,所以路径权值为极大值65535。
2. 第12~25行,是算法的主循环,一共三个嵌套,k代表的就是中转顶点的下标。v代表起始顶点,w代表结束顶点。
3. 当K=0时,也就是所有的顶点都经过v0中转,计算是否有最短路径的变化。可惜结果是,没有任何变化,如下图
4. 当K=1时,也就是所有的顶点都经过v1中转。此时,当v=0,原本D[0][2]=5,现在由于D[0][1]+D[1][2]=4。因此由代码的第20行,二者取其最小值,得到D[0][2]=4,同理可得D[0][3]=8、D[0][4]=6,当v=2、3、4时,也修改一些数据。对权值进行修改也要对P[v][k]
至此,我们的最短路径就算是完成了,你可以看到矩阵第v0行的数值与迪杰斯特拉算法求得的D数组是完全相同的{0,1,4,7,5,8,10,12,16}
路径v0到v8 P[0][8]=1 先经过v1,然后将1取代0得到P[1][8]=2说明经过2,然后以2取代1 P[2][8]=4说明经过4,然后以4取代2 P[4][8]=3说明经过3…
最后昨到v0->v1->v2->v4->v3->v6->v7->v8