最短路径

来源:互联网 发布:淘宝旺铺有必要买吗 编辑:程序博客网 时间:2024/06/03 21:10

一、最短路径的概念


       由图的概念可知,在一个图中,若从一顶点到另一顶点存在着一条路劲(这里只讨论无回路的简单路径),则称该路径长度为该路径上所经过的边的数目,它也等于该路径上的顶点数减1。由于从一顶点到另一顶点可能存在着多条路径,每条路径上所经过的边数可能不同,即路径长度不同,把路径长度最短(即经过的边数最少)的那条路径叫做最短路径,其路径长度叫做最短路径长度或最短距离。

       上面所述的图的最短路径问题只是对无权图而言的,若图是带权图,则把从一个顶点i到图中其余任一个顶点j的一条路径上所经过的边的权值之和定义为该路径的带权路径长度,从Vi到Vj可能不止一条路径,把带权路径长度最短(即其值最小)的那条路径也称做最短路径,其权值也称做最短路径长度或最短距离。

       例如,在图9-6 中,从V0到V4共有3条路径:{0,4},{0,1,2,4}和{0,1,3,4},其带权路径长度分别为30、38和23,可知最短路径为{0,1,3,4},最短距离为23.


  


       实际上,这两类最短路径问题可合并为一类,只要把无权图上的每条边标上数值为1的权就归属于有权图了,所以在以后的讨论中,若不特别指明,均认为是带权图的最短路径问题。

       求图的最短路径问题包括两个方面:一是求图中一顶点到其余个顶点的最短路径;二是求图中每对顶点之间的最短路径。下面分别进行讨论。


二、从一个顶点到其余各顶点的最短路径

      

       那么,如何求出从源点i到图找那个其余每一个顶点的最短路径呢?迪克斯特拉于1959年提出了解决此问题的特定算法,具体思路树:按照从源点到其余每一顶点的最短路径长度的升序依次求出从源点到各顶点的最短路径及长度,每次求出从源点i到一个终点m的最短路径及长度后,都要以该顶点的最短路径及长度,每次求出从源点i到一个终点m的最短路径及长度后,都要以该顶点m作为新考虑的中间点,用Vi到Vm的最短路径和最短路径长度对Vi到其他尚未求出最短路径的那些终点的当前最短路径及长度作必要的修改,使之成为当前新的最短路径和最短路径长度,当进行n-2次后,求出n-2个顶点的最短路径,除源点外,剩余的一个顶点的最短路径已经确定下来,无须再修改,所以算法结束。

       迪克拉斯算法需要设置一个集合,假定用set表示,其作用是保存已求得最短路径的终点序号,它的初值中只有一个元素,即源点i,以后每求出一个从源点i到终点m的最短路径,就将该顶点m并入Set集合中,顶点m将作为新考虑 的中间点;还需要设置一个具有权值类型的一维数组dist[n],该数组中的第j个元素dist[j]用来保存从源点i到终点MaxValue,以后每考虑一个新的中间点时,dist[j]的值可能变下;另外,再设置一个与dist数组对应的、类型为线性表List的一维数组path,该数组中的第j个线性表元素path[j]保存着从源点i到终点j的目前最短路径,即顶点序号列表,开始时,若Vi到Vj存在着一条边,则path[j]中保存着顶点序号i和j,否则path[j]为空表。

       为了简便起见,可采用一维数组s[n]来表示集合Set,具体做法是:若顶点j在集合Set中,则令数组元素s[j]的值为真,否则为假。这样,当判断一个顶点j是否在集合set以外时,只要判断对应的数组元素s[j]是否为假即可。

       例如,对于图9-6 来说,若求从源点V0到其余各顶点的最短路径,则开始时3个一维数组s,dist和path的值如表9-1所示。



       下面开始进行第1次运算,求出从源点V0到第1个终点的最短路径。首先从数组s的元素值为假的对应dist数组的所有元素中查找出值最小的元素,此时求出dist[1]的值最小,所以第1个终点为V1,最短距离为dist[1]=3,最短路径为path[1]=(0,1);接着把s[1]置为真(T),表示V1已加入set集合中;然后以V1为新考虑的中间点,对s数组中元素为假的每个顶点j(此时2<=j<=4)的目前最短路径长度dist[j]和目前最短路径path[j]进行必要的修改。因dist[1]+a[1][2]=3+25=28,小于dist[2]=∞,所以将28赋给dist[2],将path[1]的值赋给path[2],并添加上顶点序号2;同理,因dist[1]+a[1][3]=3+8=11,小于dist[3]=所以将11赋给dist[3],将path[1]的值赋给path[3],并添加上顶点序号3;最后再看从V0到V4以V1作为新考虑的中间点的情况,由于V1到V4没有出边,所以a[1][4]=,故dist[1]+a[1][4]不小于dist[4],因此dist[4]和path[4]无须修改,应维持原值。至此,第一次运算结束,3个一维数组的当前状态如表9-2所示。

  

       下面开始进行第2次运算,求出从源点V0到第2个终点的最短路径。首先从s数组中元素为假的对应dist数组的所有元素中查找出值最小的元素,此时求出dist[3]的值最小,所以第2个终点为V3,最短距离为dist[3]=11,最短路径为path[3]=(0,1,3);接着把s[3]置为真(T),表示V3已加入set集合中;然后以V3为新考虑的中间点,对s数组中元素为假的每个顶点j(此时j为2和4)的目前最短路径长度dist[j]和目前最短路径path[j]进行必要的修改。因dist[3]+a[3][2]=11+4=15,小于dist[2]=28,所以将15赋给dist[2],将path[3]的值赋给path[2],并添加上顶点序号2;同理,因dist[3]+a[3][4]=11+12=23,小于dist[4]=30所以将23赋给dist[4],将path[3]的值赋给path[4],并添加上顶点序号4;至此,第二次运算结束,3个一维数组的当前状态如表9-3所示。


        下面开始进行第3次运算,求出从源点V0到第3个终点的最短路径。首先从s数组中元素为假的对应dist数组的所有元素中查找出值最小的元素,此时求出dist[2]的值最小,所以第3个终点为V2,最短距离为dist[2]=15,最短路径为path[2]=(0,1,3,2);接着把s[2]置为真(T),表示V2已加入set集合中;然后以V2为新考虑的中间点,对s数组中元素为假的每个顶点j(此时j为4)的目前最短路径长度dist[j]和目前最短路径path[j]进行必要的修改。因dist[2]+a[2][4]=15+10=25,大于dist[4]=23,所以无须修改,原值不变。至此,第3次运算结束,3个一维数组的当前状态如表9-4所示。


        由于5个顶点,只需要3次,即n-2次,虽然此时还有一个顶点未加入set集合中,但它的最短路径及最短距离已经最后确定,无须修改,所以整个运算结束。最后在dist数组中得到从源点V0到每个顶点的最短路径长度,在path数组中得到相应的最短路径。

        如果用图形表示上述过程中每次运算的结果,则分别对应的图形如图9-7(a)~(e)所示,假定运算前的初始状态为第0次运算的结果。在每次运算的图形表示中,顶点0和实线有向边所指向的所有顶点为集合set中的顶点,虚线有向边指向的所有顶点和标记为最大值的所有顶点为集合set外的顶点;set集合中的顶点上标记的数组为从源点V0到该顶点的最短路径的长度,从源点V0到该顶点所经过的有向边为从V0到该顶点的最短距离;set集合外的顶点上所标记的数值为从源点V0到该顶点的目前最短路径长度,从V0到该顶点所经过的有向边为从V0到该顶点的目前最短路径。为了便于对照分析,把图9-6(a)重画于图9-7(a)中。


       假定一个带权图采用邻接矩阵表示,根据以上分析和举例,不难给出迪克斯特拉算法的具体描述。

//从一顶点到其余各顶点的最短路径public static void Dijkstra(AdjacencyGraph gr,int i,int [] dist,List [] path){//利用迪克斯特拉算法求图gr中从顶点i到其余各顶点的最短距离和最短路径//它们分别被保存在dist和path数组中int n=gr.vertices();              //取出图gr对象中的顶点个数并赋给nif(i<0||i>n-1){System.out.println("源点序号i的值不在有效范围内,退出运行!");System.exit(1);}int [][] a=gr.getArray();         //取出gr对象中邻接矩阵的引用boolean []s=new boolean[n];       //定义保存顶点集合的数组s//分别给s,dist和path数组赋初值for(int v=0;v<n;v++){if(v==i){s[v]=true;}else{s[v]=false;}}for(int v=0;v<n;v++){dist[v]=a[i][v];}for(int v=0;v<n;v++){path[v]=new SequenceList();if(a[i][v]!=gr.MaxValue && v!=i){path[v].add(i,1);path[v].add(v, 2);}}//共进行n-2次循环,每次求出从源点i到终点m的最短路径及长度for(int k=1;k<=n-2;k++){//求出第k次运算的终点mint w=gr.MaxValue;int m=i;for(int j=0;j<n;j++){if(s[j]==false && dist[j]<w){w=dist[j];m=j;}}//若条件成立,则把顶点m并入集合数组s中,否则退出循环,因为剩余顶点//其最短路径长度均为MaxValue,无须再计算下去if(m!=i){s[m]=true;}else{break;}//对s元素为false的对应dist和path中的元素作必要修改for(int j=0;j<n;j++){if(s[j]==false && dist[m]+a[m][j]<dist[j]){dist[j]=dist[m]+a[m][j];path[j].clear();for(int pos=1;pos<=path[m].size();pos++){path[j].add(path[m].value(pos),pos);}path[j].add(j,path[j].size()+1);}}}}

三、每对顶点之间的最短路径


        求图中每对顶点之间的最短路径是指把图中任意两个顶点Vi和Vj(i不等于j)之间的最短路径都计算出来。若图中有n个顶点,则共需要计算n(n-1)条最短路径。解决此问题有两种方法:一是分别以图中的每个顶点为源点共调用n次迪克斯特拉算法,因迪克斯特拉算法的时间复杂度为O(n^2),所以此方法的时间复杂度为O(n^3);二是采用下面介绍的弗洛伊德算法,此算法的时间复杂度仍为O(n^3),但比较简单。

       。。。。。。

      设一个带权图用邻接矩阵类的对象gr表示,保存该图中每对顶点之间最短距离的二维数组用a表示,a的初值等于gr对象中的邻接矩阵值。弗洛伊德算法需要在a上进行n次运算,n为图中的顶点数,每次以Vk(0<=0<=n-1)作为一个新考虑的中间点,求出每对顶点之间的当前最短距离(即路径长度),最后一次运算后,a中的每个元素a[i][j]就是图中从顶点Vi到顶点Vj的最短路径长度。利用Java语言编写出弗洛伊德算法如下,在该算法中没有记录每对顶点之间的最短路径(即所经过的顶点序列),只记录了每对顶点之间的最短距离。

      

//弗洛伊德算法public static void Floyed(AdjacencyGraph gr ,int [][] a){//利用弗洛伊德算法求出邻接矩阵类对象gr表示的图中每对顶点之间的最短距离//求对应数据保存于二维数组a中int i,j,k,n=gr.vertices();                //用n保存图中的顶点数//给二维数组a赋初值,它等于图的邻接矩阵类对象gr中邻接矩阵的值for(i=0;i<n;i++){for(j=0;j<n;j++){a[i][j]=(gr.getArray())[i][j];}}//依次以每个顶点作为中间点,逐步优化二维数组中的每个元素值a,可能逐渐变小for(k=0;k<n;k++){for(i=0;i<n;i++){for(j=0;j<n;j++){if(i==k || j==k || i==j){continue;}if(a[i][k]+a[k][j]<a[i][j]){a[i][j]=a[i][k]+a[k][j];}}}}}






原创粉丝点击