图论及其应用——图的最短路径问题

  基于前文对图和树的简单讨论,我们在这篇文章中将介绍有关图的最短路径的问题。
  最短路径的原始模型非常简单,给出一个图G(V、E),其中边元素e都带有权值,寻找vi和vj之间的一条路径,是的该路径上边的权值之和最小。
  基于这个最简单的模型,最短路径问题细分还会有好多种类,比如图G是否有向?权值是否有负值?比如是单源头最短路还是多源头?
  在这里,我们先讨论利用Dijkstra算法实现的单源无负权值无向图的最短路问题。
  考察无向图G<V,E>,点集的元素个数是n,我们利用二维数组map[i][j]记录无向图中边vivj的权值,如果vi与vj不连通,那么map[i][j]无限大。单源是s,并设置dis[j]记录单源s到j的最短路径(也就是最小的权值之和)。
  我们考察s点到vj的最短路径,假设是<s、v1、v2……vj-1、vj>是s到vj的最短路径,那么用反证法很容易证明,<s、v1、v2……vj-1>是s到vj-1的最短路径,那么对于s到vj的最短路径的求解,我们可以看成这样一个过程,在与vj直接相连的点vm、vn、vp中(这里假设与vj直接相连的只有这三个点),那么我们只需比较dis[m] + map[m][j] 、 dis[n] + map[n][j]、dis[p] + map[p][j]中的最小值,即是s到vj的最短路径。如果我们用动态规划的思想去解读这个过程,会得到这样一个状态转移方程:
  dis[j] = min(dis[m] + map[m][j] , dis[n] + map[n][j],dis[p] + map[p][j] , ……),其中vm、vn、vp……表示点集V中和vj直接相连的点。
  那么基于这种递推关系,我们就很容易考虑如何用程序化的算法来实现最小路径的求解了,可以看到,s到vj的最短路径是要基于较短的最短路径,因此我们需要从较短的最短路径开始构造。
  首先我们从与源头s直接相连的点集v1开始,找到一条边,并标记顶点v1下次不再访问,使得<s、v1>的权值最小,这便是s到v1所有边数为1的路径中的最短路径,那么基于这最短路径,便可以逐层的往上构造边数更长的最短路径。
  Dijkstra算法基于无负权的图,因此从源头s到与其直接相连的点的最短路径是不会存在中转点的。
  由于我们我每次从源头s开始构造最短路径,至少会完成一个点的最短路径的构造,那么对于含有n个元素的点集,显然我们需要构造n-1次,才可以确保找到源头s到G中每一个点的最短路径都记录在dis[]当中,那么如果想访问s到vj的最短路径的最小权值,只需输出dis[j]即可。
  我们通过一个题目来进一步体会一下Dijkstra算法的实现。(Problem source : hdu 2066)
  

Problem Description
虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子,^0^),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。
 
Input
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个; 接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b 之间可能有多条路) 接着的第T+1行有S个数,表示和草儿家相连的城市; 接着的第T+2行有D个数,表示草儿想去地方。
 
Output
输出草儿能去某个喜欢的城市的最短时间。


  数理分析:基于单源最短路径问题的模型,这里其实是给出了多个单源(和小草相连的城市,由于题目没给相关数据,显然这里认为小草的家到源头是不花时间的),也就是需要进行多次Dijkstra算法,然后维护一个最小值即可。
  基于对Dijkstra算法思想的理解,不难进行编程实现。
  参考代码如下。

 

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
#define MAX 0x3f3f3f3f

int road , link  ,want , total;
int Map[1010][1010] , linkarr[1010] , wantarr[1010] , dis[1010];
bool visit[1010];

void Dijkstra(int start)
{
     int temp , k;
     memset(visit , 0 , sizeof(visit));
     for(int i = 1;i <= total;++i)
          dis[i] = Map[start][i];
     dis[start] = 0;
     visit[start] = 1;
     for(int i = 1;i <= total;++i)
     {
         temp = MAX;
         for(int j = 1;j <= total;++j)
               if(!visit[j] && temp > dis[j])
                   temp = dis[k = j];
         visit[k] = 1;
         for(int j = 1;j <= total;++j)
               if(!visit[j] && dis[j] > dis[k] + Map[k][j])
                   dis[j] = dis[k] + Map[k][j];
     }
}

int main()
{
    int x , y , cost , minn , answer;
    while(scanf("%d%d%d",&road,&link,&want) != EOF)
    {
         total = 0;
         memset(Map , MAX ,sizeof(Map));
           for(int i = 1;i <= road;++i)
           {
                scanf("%d%d%d",&x,&y,&cost);
                if(cost < Map[x][y])  //这里是个小坑,在记录图的信息的时候,会出现平行边,直接记录较小的边即可
                      Map[x][y] = Map[y][x] = cost;
                total = max(total , max(x , y));
           }

           for(int i = 1;i <= link;++i)
               scanf("%d",&linkarr[i]);
           for(int i = 1;i <= want;++i)
               scanf("%d",&wantarr[i]);

           answer = MAX;
           for(int i = 1;i <= link;++i) //遍历与小草家相连的城市并以其为起点进行Dijkstra算法
           {
               Dijkstra(linkarr[i]);
               minn = MAX;
               for(int j = 1;j <= want;++j)
                   if(dis[wantarr[j]] < minn)
                        minn = dis[wantarr[j]];
               if(answer > minn)
                  answer = minn;
           }
           printf("%d\n",answer);
    }
}

 

 

    我们曾提到,解决图中最短路径的几个经典算法Dijkstra、SPFA等算法,适用于解决单源最短路径的问题,那么对于一个图G,我们想知道任意两点vi到vj的最短路径,该如何求解呢?
  下面我们就来介绍求解每对顶点间的最短距离的Floyd算法。
  我们容易看到,对着求解任意两点之间的最短距离,我们肯定是要基于穷举算法来遍历到所有的情况。显然我们需要两层循环来遍历起始点i和终止点j,我们再设置一个中间点k,表示vi到vj的路径中会经过vk。
  我们反身来看待这个问题,需要得到所有情况,需要找到最优解,容易看到,这正是动态规划能够解决的问题,因此我们很自然的联想到利用动态规划的思想来解决这一模型。
  我们设置二维数组dp[i][j]来表示vi到vj的最短路径,我们容易得到如下的状态转移方程:
      dp[i][j] = min(dp[i][j],dp[i][k] + dp[k][j]),其中k要遍历vi、vj路径上的所有点。
  通过这样一个状态转移方程,我们就能够遍历出所有的情况并且找到每种情况的最优解了。
  我们根据一个具体的题目来编程实现一下Floyd算法。(Problem source : hdu 1596)
  

Problem Description
XX星球有很多城市,每个城市之间有一条或多条飞行通道,但是并不是所有的路都是很安全的,每一条路有一个安全系数s,s是在 0 和 1 间的实数(包括0,1),一条从u 到 v 的通道P 的安全度为Safe(P) = s(e1)*s(e2)…*s(ek) e1,e2,ek是P 上的边 ,现在8600 想出去旅游,面对这这么多的路,他想找一条最安全的路。但是8600 的数学不好,想请你帮忙 ^_^
 
Input
输入包括多个测试实例,每个实例包括: 第一行:n。n表示城市的个数n<=1000; 接着是一个n*n的矩阵表示两个城市之间的安全系数,(0可以理解为那两个城市之间没有直接的通道) 接着是Q个8600要旅游的路线,每行有两个数字,表示8600所在的城市和要去的城市
 
Output
如果86无法达到他的目的地,输出"What a pity!", 其他的输出这两个城市之间的最安全道路的安全系数,保留三位小数。


  数理分析:其实这个题目是在上文我们引入Floyd算法的模型的基础上稍作了改动,比如说在这个问题中并不是求解最短路径而是最长路径,而且这个这个“长”代表路径中边的权值的乘积,可以看到,思路是完全一样的,只不过是在状态转移方程上稍作修饰即可。
  即dp[i][j] = max(dp[i][j] , dp[i][k]*dp[k][j]).
  参考代码如下。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn = 1005;

int n;
double Map[maxn][maxn];
double s[maxn][maxn];

void F_W()
{

      for(int k = 1 ;k <= n;k++)
          for(int i = 1;i <= n;i++)
               for(int j = 1;j <= n ;j++)
                    s[i][j] = max(s[i][j] , s[i][k]*s[k][j]);
}

int main()
{
        while(scanf("%d",&n) != EOF)
        {
               int i , j;
               for(i = 1;i <= n;i++)
                   for(j = 1;j <= n;j++)
               {
                     scanf("%lf",&Map[i][j]);
                     s[i][j] = Map[i][j];
               }
               F_W();
               int num;
               scanf("%d",&num);
               int S , E;
               while(num--)
               {
                    scanf("%d%d",&S,&E);
                if(s[S][E] > 0)
                    printf("%.3lf\n",s[S][E]);
                else
                     printf("What a pity!\n");
               }
        }
}

 


 
   


 
 
 

posted on 2016-03-31 11:14  在苏州的城边  阅读(935)  评论(0编辑  收藏  举报

导航