下午做了一个算法的课程实验,求任意两个结点之间的最短路径问题,老师上课的时候只讲解了Dijkstra求单源节点的最短路径问题,这里我需要向广大的看客解释一下,所谓单源节点就是一个图当中求从指定结点出发到任意结点的最短路径问题,比如有如下的图:

最短路径问题_第1张图片

这是一张有向图(无向图也类似),单源节点就是求比如从1出发到2,3,4,5的最短路径问题,使用Dijkstra这种贪心的算法思想可以比较好的求出答案,Dijstra算法是运用贪心的策略,从源点开始,不断地通过相联通的点找出到其他点的最短距离。
基本思想是:
    设置一个顶点的集合s,并不断地扩充这个集合,一个顶点属于集合s当且仅当从源点到该点的路径已求出。开始时s中仅有源点,并且调整非s中点的最短路径长度,找当前最短路径点,将其加入到集合s,直到终点在s中。
基本步骤:
1、把所有结点分成两组:第一组:包括已经确定最短路径的结点;第二组:包括尚未确定最短路径的结点。
2、开始时,第一组只包含起点,第二组包含剩余的点;
3、用贪心的策略,按最短路径长度递增的顺序把第二组的结点加到第一组去,直到v0可达的所有结点都包含于第一组中。在这个过程中,不断更新最短路径,总保持从v0到第一组各结点的最短路径长度dist都不大于从v0到第二组任何结点的路径长度。
4、每个结点对应一个距离值,第一组结点对应的距离就是v0到此结点的最短路径长度,第二组结点对应的距离值就是v0由第一组结点到此结点的最短路径长度。
5、直到所有的顶点都扫描完毕(v0可达的所有结点都包含于第一组中),找到v0到其它各点的所有最短路径。

而下午做的实验是要求任意两个结点之间的最短路径,于是聪明的朋友马上就会联想到把Dijkstra算法循环n次就行了,不错,我第一次就是这么写的,下面贴出核心代码,需要下载完整代码的朋友在附件里下吧:

   
   
   
   
  1. …………  
  2.  
  3. void Dijkstra(int dist[][MAXSIZE], int path[][MAXSIZE], int n, int index)   
  4. {   
  5.     int s, min;   
  6.     int visited[MAXSIZE], dist_temp[MAXSIZE];  
  7.  
  8.     for (int i = 0; i < n; i++) {   
  9.         visited[i] = NONE_VISIT;   
  10.         dist_temp[i] = dist[index][i];   
  11.     }   
  12.     visited[index] = VISITED;   
  13.     dist_temp[index] = 0;   
  14.     for (int j = 0; j < n-1; j++) {   
  15.         min = INT_MAX + 1;   
  16.         //选取结点u使得dist_temp[u] = min{dist_temp[w]} w不在visited[]中   
  17.         for (int u = 0; u < n; u++)   
  18.             if ((VISITED != visited[u]) && (min > dist_temp[u])) {   
  19.                 min = dist_temp[u];   
  20.                 s = u;   
  21.             }   
  22.         visited[s] = VISITED;   
  23.         for (int k = 0; k < n; k++)   
  24.             if (VISITED != visited[k]) {   
  25.                 if (dist_temp[k] > (dist_temp[s] + dist[s][k])) {   
  26.                     dist_temp[k] = dist_temp[s] + dist[s][k];   
  27.                     path[index][k] = path[s][k];   
  28.                 }   
  29.                 else   
  30.                     dist_temp[k] = dist_temp[k];   
  31.             }   
  32.     }   
  33.     for (int m = 0; m < n; m++)   
  34.         dist[index][m] = dist_temp[m];   
  35. }  
  36.  
  37. ………… 

这个函数的作用就是求出从index出发到任意结点最短路径,然后在主函数中循环调用这个函数就能够求出任意两个结点间的最短路径。其中dist[][]为外部定义的n*n的矩阵,用于记录任意两个结点之间的距离,path[][]也为n*n的矩阵,用于记录任意结点最短路径的前驱结点,为了后面显示路径使用。

而后在网上搜了一下,发现求解任意两个结点之间的最短路径有很多方法,其中最著名的就是Floyd算法,Floyd算法采用动态规划的思想,在一个有向图中结点i到结点j的最短路径不仅可以由i直达j,而且可以由i经过其它的中间结点到达j。因此可以把i到j的路径经由某一个中间结点k分成两段,一段是i到k,另一段是k到j。如果这两段都是最短路径,根据最优性原理,i到j也是一条最短路径。因此这是一个递推的过程。分析的关键有:
1.把结点编号。如果k是i到j中的一个中间结点,并且在所有中间结点中k的编号最大。那么i到k和j到k的最短路径上不会有比编号k-1还大的结点。
2.用Ak(i, j)表示从i到j并且不经过比k还大的结点的最短路径长度。于是在i到j的最短路径上,如果这条路径经过结点k,那么Ak(i, j)= Ak-1(i, k)+ Ak-1(k, j);如果不通过结点k,那么i到j的最短路径上能够经过的节点的最大可能的编号是k-1,所以Ak(i, j)= Ak-1(i, j)。   
3.把上面所说的两种情况和起来,得到递推的公式: Ak(i,j)=min{ Ak-1(i,j),Ak-1(i,k)+Ak-1(k,j)},k≥1它表示从i到j并且不经过比k还大得结点的最短路径长度,需要同时考察经过结点k和不经过的两种情况。Ak-1(i, j)表示不经过结点k,Ak-1(i, k)+Ak-1(k, j)表示经过结点k,选取其中最小的。


综上,要求任意结点间的最短路径可以先建立一个初始的矩阵A,定它为A0,它等于有向图的成本矩阵COST。然后求经过最大编号为1的中间结点的任意结点间最短路径矩阵A1,再求经过最大编号为2的中间结点的矩阵A2,以此类推直到求到An。An就是最终的结果。同样实现了一把,效果不错,核心函数还是贴出来,源程序附在博文后:

   
   
   
   
  1. …………  
  2.  
  3. void Floyd(int dist[][MAXSIZE], int path[][MAXSIZE], int n)   
  4. {   
  5.     int  i, j, k;   
  6.     //初始化path,让起始点直接到达目的点   
  7.     for (i = 0; i < n; i++)    
  8.         for (j = 0; j < n; j++)   
  9.             path[i][j] = i;   
  10.     for (k = 0; k < n; k++)   
  11.         for (i = 0; i < n; i++)   
  12.             for (j = 0; j < n; j++)   
  13.                 //如果发现一条路径比dist[i][j]的距离短,那么就通过那条路径   
  14.                 if (dist[i][j] > MAXSUM(dist[i][k], dist[k][j])) {   
  15.                     //path记录i到j通过的顶点   
  16.                     path[i][j] = path[k][j];   
  17.                     dist[i][j] = MAXSUM(dist[i][k], dist[k][j]);   
  18.                 }   
  19. }  
  20.  
  21. ………… 

这个函数可以把任意两点之间的最短路径求出来,并记录到达结点的路径,看起来很漂亮,但背后却无法避免的消耗了大量的时间,一个3层循环,直接上升到O(n^3)的时间复杂度,对于复杂一点的图来说已经很难接受了,相信大家能够想到优化方法,我也想过,在这里就不说了,留给大家想象的空间。还是那句话,欢迎大家修改使用我的代码化为己用,分享快乐~~