zoukankan      html  css  js  c++  java
  • 图论及其应用——图的最短路径问题

      基于前文对图和树的简单讨论,我们在这篇文章中将介绍有关图的最短路径的问题。
      最短路径的原始模型非常简单,给出一个图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
    ",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
    ",s[S][E]);
                    else
                         printf("What a pity!
    ");
                   }
            }
    }


     
       


     
     
     

  • 相关阅读:
    Learning KVM
    KVM HOST IN A FEW LINES OF CODE
    VM学习—实现自己的内核
    gvisor bluepillHandler + SwitchToUser
    GO语言调试利器dlv快速上手
    gvisor debug
    gvisor 系统 调用初始化
    Android开发 02
    Android开发 01
    加分项
  • 原文地址:https://www.cnblogs.com/rhythmic/p/5340403.html
Copyright © 2011-2022 走看看