在数据结构的学习当中,图的处理是必不可少的一项,对图进行处理时,最短路径的求取具有较强的使用性和可行性,在此,我简单地对求取最短路径的算法之一——弗洛伊德算法做出简单的讲解。
弗洛伊德算法在实现的时候一个重要的特点就是用循环的嵌套来进行逐个的判断和更新。此算法区分于迪杰斯特拉算法的很大的优点就是弗洛伊德算法可以在实现过后实现了图中每一个结点的对其余各个结点的最短路径,而后者只是针对一个目标结点计算出该结点到其他结点的最短路径。但是,由于这次只是做弗洛伊德算法的讲解,对于迪杰斯特拉算法便不予赘述。
如果要求任意两个结点之间的最短路径,我们可能已经有了简单的想法,在两个结点之间存在直接的路径时,比较这个路径和经过一次结点的中转,甚至二次、三次、以及多次的中转之后的权值之和比较,最短的权值之和就可以确定该路径为最短路径;若两者没有直接路径,则需考虑在经过结点的中转以后,无论经过了几次中转结点,只要选出权值最短的路径,该路径就是最短路径。
这便是最短路径的求取方法。
这个过程看似实现较难,但加上了一些辅助变量后,便可以顺利实现。
但是要加入哪些辅助变量呢?首先需要考虑到的是path数组存放计算出的最短路径的结果。行标表示起点结点,列标表示终点结点,则该位置存放的数据则是终点结点的前一个结点。
例如:从a到达c的最短路径为:a—》b—》c;若a的下标是1;c的下标是4;b的下标是2;则path[1][4]=2;若从c到e的最短路径是c—》d—》a—》e;c,d,a,e的下标分别为3,4,1,5;
则path[3][5]=1;在查找最短路径时的顺序则是path[3][5]=1,path[3][1]=4;path[3][4]=3;路径顺序就为:下标5《--1《--4《--3。
另外,需要加入辅助数组D,该数组用于记录两结点直接的最短路径长度如D[0][3]=3;表示从0到3的最短路径长度为3;
现在,让我们回到上述自己设想的问题,如何比较两个结点之间的路径长度和呢?
在弗洛伊德算法中,采用的方法是权值的更新,以下是弗洛伊德的具体算法,以便于讲解
下面开始对代码展开讲解:
个人认为,算法的实现可以分为两部分,其中每一部分可以用以上的两个for循环来划分,第一个for循环如下,进行的是权值和路径的初始化
D[i][j]=G.arc[i][j]; //将下标为i的结点与相关的结点权值初始化,如果有直接路径,则长度是已知权值,否则的初始化结果是无穷
if (D[i][j]<Maxint)
p[i][j]=i; //已初始化后,有直接路径的话终点结点的前一个结点便是起点结点
else
p[i][j]=0; //没有直接路径,进行特殊标记。
进行了初始化之后,数组D内的元素应该与图D的权值数组G.arc相等,并且数组path内的数据也根据图中结点间的关系获得了初值。
第二部分体现为针对两个目标结点加入图中的每一个结点作为中转结点进行路径长度的判断,根据判断结果更新两个数组内容,这个算法很容易使人联想到密码破解时使用的穷举法。
代码如下:
for (i=0;i<G.vexnum;i++)
for(j=0;j<G.vexnum;j++) //起始结点
for(k=0;k<G.vexnum;k++) //终点结点
if (D[j][k]>D[j][i]+D[i][k]) //j,k是需要更新的两个结点,现在欲在这两个结点中加入下标为i的结点,并判断加入新的结点以后的路径长度。
{
D[j][k]=D[j][i]+D[i][k];
p[j][k]=p[k][i]; //可以更新,则进行数据更新。
}
到此,循环结束,最短路径的查找也完成。
需要说明的是,每一次数据的更新,path数组都保存有前一个结点的信息,如图:
首先path[0][1]=0;D[0][1]=6;之后,有初始化后的结果:path[0][2]=0,D[0][2]=1,2和1之间没有路径故D[2][1]=Maxint,path[0][1],D[0][1]无需更新,之后,判断0和3,D[0][3]=Maxint是初始化的结果,更新加入2之后成了path[0][3]=2;D[0][3]=3;path[2][3]=0,D[2][3]=2,path[3][1],D[3][1]=1加入任意结点之后无需更新,故保持;再进行path[0][1],D[0][1]的判断时,尝试加入结点2,不成功,再尝试加入点3时,由于D[0][3]已经更新为3,所以满足更新条件,D[0][1]=4,path[0][1]=3完成了从0到1中间大于1个中转结点的实现。
总结在于,第二部分使用三个for语句的嵌套起到了重要的作用,数据的更新和信息的保存也需要准确理解。