Floyd 算法

来源:互联网 发布:建一个淘宝白菜群佣金 编辑:程序博客网 时间:2024/05/01 23:42

 

 佛罗伊德算法在图论中求两个点之间的最短距离时很容易,最起码在编码量上很低,不过时间复杂度有O(n^3).

记不清从哪个大神那里看到了这个算法的解析,只是给了我很深的印象.下文也是那个大神的主要意思,照般照抄,我第一次接触Floyd算法到理解这个算法都全靠那个大神,感激中....

算法描述如下:

      如果有一个矩阵d,其中 d(i,j) > 0表示 i 城市到 j 城市的距离。若 i 与 j 之间无路可通,那么 d(i,j) 就是无穷大。又有 d(i,i) = 0。编写一个程序,通过这个距离矩阵d,把任意两个城市之间的最短与其行径的路径找出来。
      如何找出最短路径呢?我们知道对于任何一个城市而言,i 到 j 的最短距离可以分为两种情况讨论,

  1:i 直接到 j ;

  2:i 经过 k 到达 j ;

        所以可以令k=1,2,3,...,n ( n 是城市的数目),在检查 d(i,j) 与 d(i,k) + d(k,j) 的值;在此d(i,k) 与 d(k,j) 分别是目前为止所知道的 i 到 k 与 k  到 j 的最短距离,因此 d(i,k) + d(k,j) 就是 i 到 j 经过 k 的最短距离。所以,若有 d(i,j) > d(i,k) + d(k,j),就表示从 i 出发经过 k 再到 j 的距离要比原来从 i 到 j 距离短,自然把 i 到 j 的 d(i,j) 重写为 d(i,k) + d(k,j),每当一个 k 查完了,d(i,j) 就是目前的 i 到 j 的最短距离。重复这一过程.

  最后当查完所有的 k 时,d(i,j) 里面存放的就是 i 到 j 之间的最短距离了。所以我们就可以用三个for循环把问题搞定了,但是有一个问题需要注意,那就是for循环的嵌套的顺序:仔细考虑的话,会发现是有问题的。

                     for(int i=0; i<n; i++)
                           for(int j=0; j<n; j++)
                                for(int k=0; k<n; k++)   
    

     问题出在我们太早的把i-k-j的距离确定下来了,假设一旦找到了i-p-j最短的距离后,i到j就相当处理完了,以后不会在改变了,一旦以后有使i到j的更短的距离时也不能再去更新了,所以结果一定是不对的。所以应当象下面一样来写程序:

                    for(int k=0; k<n; k++)
                         for(int i=0; i<n; i++)
                              for(int j=0; j<n; j++)

    这样作的意义在于固定了k,把所有i到j而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的i到j都处理完后,才会移动到下一个k,这样就不会有问题了,看来多层循环的时候,我们一定要当心,否则很容易就弄错了。

代码如下:

void init (){for (int i=0;i<n-1;i++)for (int j=i+1;j<n;j++)dist[i][j] = dist[j][i] = INF;for (int i=0;i<n;i++)dist[i][i] = 0;}void Floyd (){for (int k=0;k<n;k++)for (int i=0;i<n;i++)for (int j=0;j<n;j++)if (dist[i][j] > dist[i][k]+dist[k][j])dist[i][j] = dist[i][k]+dist[k][j];}


  以上的代码就应该是佛罗伊德酸法的核心代码了,已经可以求出任意两点之间的距离的最小值,但是如果要求你打印出他们之间经过的所有中间点,那么就还的继续写下去..

  这里要用到另一个矩阵P,它的定义是这样的:p(i,j)的值如果为p,就表示i到j的最短行经为i->...->p->j,也就是说p(i,j)是i到j的最短行径中的j之前的最后一个城市。

  P矩阵的初值为 p(i,j) =  i。对于 i 到 j 而言找出 p(i,j),令为 p,就知道了路径 i->...->p->j;

        再去找 p(i,p),如果值为 q,i 到 p 的最短路径为 i->...->q->p;

        再去找 p(i,q),如果值为 r,i 到 q 的最短路径为 i->...->r->q;

        如此一再反复,到了某个  p(i,t) 的值为 i 时,就表示 i 到 t 的最短路径为 i-> t,就会的到答案:  i 到 j 的最短行径为 i->t->...->q->p->j。

       因为上述的算法是从终点到起点的顺序找出来的,所以输出的时候要把它倒过来。
       但是,如何动态的回填P矩阵的值呢? 上面提到,当 d(i,j) > d(i,k) + d(k,j) 时,就要让 i 到 j 的最短路径改为走 i->...->k->...->j 这一条路,但是 d(k,j) 的值是已知的,换句话说,就是 k->...->j 这条路是已知的,所以 k->...->j 这条路上j的上一个城市 (即p(k,j)) 也是已知的,当然,因为要改走 i->...->k->...->j 这一条路,j 的上一个城市正好是 p(k,j)。所以一旦发现 d(i,j)>d(i,k)+d(k,j),就把 p(k,j) 存入 p(i,j)。

具体代码如下:

void SWAP(a, b) { temp = a; a = b; b = temp; }   void reverse(int x[], int n)   {       int i, j, temp;       for (i = 0, j = n-1; i < j; i++, j--)            SWAP(x[i], x[j]);   }void display_path(int dist[][MAXSIZE], int path[][MAXSIZE], int n)   {       int *tmp;       int count;       printf("\n\nOrigin->Dest   Dist   Path");       printf( "\n-----------------------------\n");       tmp = (int *) malloc(sizeof(int)*n);       for (int i = 0; i < n; i++)            for (int j = 0; j < n; j++)           {               if (i != j)               {                     printf("\n%6d->%d    ", i+1, j+1);                    if (dist[i][j] == MAXSIZE)                          printf(" WA  ");                     else                    {                         printf("%4d    ", dist[i][j]);                         count = 0;                           int k = j;                         do                         {                             k = tmp[count++] = path[i][k];                         } while (i != k);                         reverse(tmp, count);                          printf("%d", tmp[0]+1);                          for (k = 1; k < count; k++)                              printf("->%d", tmp[k]+1);                         printf("->%d", j+1);                    }               }           }       free(tmp);   }



 

 

0 0