zoukankan      html  css  js  c++  java
  • hdu 2544最短路——最短路的初次总结 UESTC 6th Programming Contest Online

    这是一道标准的模板题,所以拿来作为这一段时间学习最短路的总结题目。

    题意很简单:

    有多组输入数据,每组的第一行为两个整数n, m。表示共有n个节点,m条边。

    接下来有m行,每行三个整数a, b, c。表示从a到b或从b到a长度为c。

    求从1到n的最短路。

    先说Floyd——

    这个算法看上去就是一个三重for循环,然后在循环里不断对选择的两个节点进行松弛(感觉松弛这两个字很高端有没有)。

    算法时间复杂度为O(n^3),n为节点数。所以一般可以用来处理规模1000以下的数据(即100数量级的,但是如果常数比较大的话也会超时,一般都是规模100的数据)。

    但是,这是我目前掌握的唯一一个可以直接计算多源最短路的算法(即算法执行之后,获得图中任意两点间的最短路)。

    代码如下:

    1 void Floyd()
    2 {
    3     for(int k = 1; k <= n; k++)             //选择的中间节点
    4         for(int i = 1; i <= n; i++)             //选择的源节点,即出发点
    5             for(int j = 1; j <= n; j++)             //选择的目标节点
    6                 if(mp[i][j] > mp[i][k] + mp[k][j]) 
    7                     mp[i][j] = mp[i][k]+mp[k][j];     //如果经过中间节点的路径(即i到k到j)小于直接从i到j,则进行更新操作(松弛)
    8 }

    这道题用floyd跑了41ms。

    接着是Dijkstra——

    Dijkstra的时间复杂度笼统的说是O(n^2), 精确一点是O(n^2+m), 如果源点可达,那么是O(n*lg n+m*lg n) => (m*lg n)。n是点数,m是边数。一般来说,规模10^4的数据是可以在1s内完成的。

    Dijkstra可以用来求单源最短路,也就是从一个点出发,到其他所有点的最短路。

    注意,当边存在负权的时候,Dijkstra就会求出错误答案了。

     1 void Dijkstra(int s)  //计算从s到其他所有点的最短路
     2 {
     3     for(int i = 1; i <= n; i++) dis[i] = mp[s][i];  //维护一个记录最短路的数组
     4     v[s] = 1;                                       //从s到s的最短路已获得,标记为1
     5     int minn, k;
     6     for(int i = 1; i <= n; i++)                     //进行n次,可能小于n提前结束(好像是n-1次?)
     7     {
     8         minn = M;
     9         for(int j = 1; j <= n; j++)                 //寻找当前已经确定的最短路
    10         {
    11             if(!v[j] && minn > dis[j])
    12             {
    13                 minn = dis[j];
    14                 k = j;
    15             }
    16         }
    17         if(minn == M) break;                        //提前结束(即到所有点的距离都以找到,v[]全部为1),则提前退出
    18         
    19         v[k] = 1;                                   //当前最短路已找到,标记为1
    20         for(int j = 1; j <= n; j++)                 //在当前最短路的基础上,寻找到其他未找到点的最短路
    21             if(!v[j] && dis[j] > dis[k]+mp[k][j]) dis[j] = dis[k]+mp[k][j];
    22     }
    23 }

    这道题用Dijkstra跑了15ms。

    接下来是BellManFord算法——

    这个算法就是进行连续松弛,时间复杂度为O(n*m)不能打折,所以效率比Dijkstra低。但是,不超过10^4的数据一般还是可以在1s内完成的

    这个算法的优点是可以计算存在负权的图的最短路,同时它也是求单源最短路的算法。它还可以判断图中是否存在负环(存在负环时是没有最短路的)。

     1 bool BellManFord(int s)
     2 {
     3     for(int i = 1; i <= n; i++) dis[i] = mp[s][i];
     4     for(int i = 1; i < n; i++)
     5     {
     6         for(int j = 1; j <= n; j++)
     7         {
     8             for(int k = 1; k <= n; k++)
     9             {
    10                 if(dis[j] > dis[k]+mp[j][k]) dis[j] = dis[k]+mp[j][k];
    11             }
    12         }
    13     }
    14     for(int j = 1; j <= n; j++)   //算法的精髓,在求出一般意义的最短路之后,在进行一次判断操作,以确定是否存在负环
    15     {
    16         for(int k = 1; k <= n; k++) if(dis[j] > dis[k]+mp[j][k]) return 0;  //存在负环,返回0
    17     }
    18     return 1; 
    19 }

    这个算法跑了31ms。

    最后是Spfa算法——

    这个算法是BellManFord算法的延伸,或者说优化。它的时间复杂度不稳定,不同的图可能算出来的不一样。我目前能写的是用队列的,类似于bfs,据说还有用dfs写的……

    总之我对这个算法也不是完全掌握,只是能用而已。

    如果Dijkstra也超时的话,可以用这个算法碰碰运气,说不定就过了。

    对了,这个算法由于是从BellManFord优化而来的,所以也可以判负环,不过我这个没有写。加一个判断节点入队次数的操作就行了,如果某节点入队大于n次,就是有负环。

    这个算法还是很好理解的。

     1 void Spfa(int s)
     2 {
     3     for(int i = 1; i <= n; i++) dis[i] = M;
     4     dis[s] = 0;
     5     queue<int>que;
     6     que.push(s);
     7     v[s] = 1;
     8 
     9     int p;
    10     while(!que.empty())
    11     {
    12         p = que.front();
    13         que.pop();
    14         v[p] = 0;
    15         for(int i = 1; i <= n; i++)
    16         {
    17             if(dis[i] > dis[p]+mp[p][i])
    18             {
    19                 dis[i] = dis[p]+mp[p][i];
    20                 if(v[i] == 0)
    21                 {
    22                     que.push(i);
    23                     v[i] = 1;
    24                 }
    25             }
    26         }
    27     }
    28 
    29 }

    同样跑了15ms。

    当然,以上的运行时间并不具有代表性,因为不同的图用不同的算法运行时间总存在差异,尤其是spfa,不过,某种程度上还是可以作为参考的。

    整体代码:

      1 #include <cstdio>
      2 #include <cmath>
      3 #include <cstring>
      4 #include <algorithm>
      5 #include <queue>
      6 using namespace std;
      7 
      8 const int M = 100000010;
      9 const int N = 110;
     10 
     11 int n, m;
     12 int a, b, c;
     13 int mp[N][N];
     14 bool v[N];
     15 int dis[N];
     16 
     17 void Init()
     18 {
     19     for(int i = 1; i <= n; i++)
     20     {
     21         for(int j =1; j < i; j++)
     22             mp[i][j] = mp[j][i] = M;
     23         mp[i][i] = 0;
     24         v[i] = 0;
     25     }
     26     for(int i = 0; i < m; i++)
     27     {
     28         scanf("%d%d%d", &a, &b, &c);
     29         if(mp[a][b] > c) mp[a][b] = mp[b][a] = c;
     30     }
     31 }
     32 
     33 void Floyd()
     34 {
     35     for(int k = 1; k <= n; k++)             //选择的中间节点
     36         for(int i = 1; i <= n; i++)             //选择的源节点,即出发点
     37             for(int j = 1; j <= n; j++)             //选择的目标节点
     38                 if(mp[i][j] > mp[i][k] + mp[k][j]) 
     39                     mp[i][j] = mp[i][k]+mp[k][j];     //如果经过中间节点的路径(即i到k到j)小于直接从i到j,则进行更新操作(松弛)
     40 }
     41 
     42 void Dijkstra(int s)  //计算从s到其他所有点的最短路
     43 {
     44     for(int i = 1; i <= n; i++) dis[i] = mp[s][i];  //维护一个记录最短路的数组
     45     v[s] = 1;                                       //从s到s的最短路已获得,标记为1
     46     int minn, k;
     47     for(int i = 1; i <= n; i++)                     //进行n次,可能小于n提前结束(好像是n-1次?)
     48     {
     49         minn = M;
     50         for(int j = 1; j <= n; j++)                 //寻找当前已经确定的最短路
     51         {
     52             if(!v[j] && minn > dis[j])
     53             {
     54                 minn = dis[j];
     55                 k = j;
     56             }
     57         }
     58         if(minn == M) break;                        //提前结束(即到所有点的距离都以找到,v[]全部为1),则提前退出
     59         
     60         v[k] = 1;                                   //当前最短路已找到,标记为1
     61         for(int j = 1; j <= n; j++)                 //在当前最短路的基础上,寻找到其他未找到点的最短路
     62             if(!v[j] && dis[j] > dis[k]+mp[k][j]) dis[j] = dis[k]+mp[k][j];
     63     }
     64 }
     65 
     66 bool BellManFord(int s)
     67 {
     68     for(int i = 1; i <= n; i++) dis[i] = mp[s][i];
     69     for(int i = 1; i < n; i++)
     70     {
     71         for(int j = 1; j <= n; j++)
     72         {
     73             for(int k = 1; k <= n; k++)
     74             {
     75                 if(dis[j] > dis[k]+mp[j][k]) dis[j] = dis[k]+mp[j][k];
     76             }
     77         }
     78     }
     79     for(int j = 1; j <= n; j++)   //算法的精髓,在求出一般意义的最短路之后,在进行一次判断操作,以确定是否存在负环
     80     {
     81         for(int k = 1; k <= n; k++) if(dis[j] > dis[k]+mp[j][k]) return 0;  //存在负环,返回0
     82     }
     83     return 1; 
     84 }
     85 
     86 void Spfa(int s)
     87 {
     88     for(int i = 1; i <= n; i++) dis[i] = M;
     89     dis[s] = 0;
     90     queue<int>que;
     91     que.push(s);
     92     v[s] = 1;
     93 
     94     int p;
     95     while(!que.empty())
     96     {
     97         p = que.front();
     98         que.pop();
     99         v[p] = 0;
    100         for(int i = 1; i <= n; i++)
    101         {
    102             if(dis[i] > dis[p]+mp[p][i])
    103             {
    104                 dis[i] = dis[p]+mp[p][i];
    105                 if(v[i] == 0)
    106                 {
    107                     que.push(i);
    108                     v[i] = 1;
    109                 }
    110             }
    111         }
    112     }
    113 
    114 }
    115 
    116 int main()
    117 {
    118     while(~scanf("%d%d", &n, &m) && (n+m))
    119     {
    120         Init();
    121         //Floyd();
    122         //printf("%d
    ", mp[1][n]);
    123         //Dijkstra(1);
    124         //if(BellManFord(1))
    125         Spfa(1);
    126         printf("%d
    ", dis[n]);
    127     }
    128     return 0;
    129 }

    总之这只是一个入门等级的最短路,接下来还有一大波优化等着我们呢,几乎每个最短路算法都有优化,用优先队列,用堆,用双向队列……哦,天哪!

  • 相关阅读:
    文学、哲学段子
    文学、哲学段子
    js技术要点---JS 获取网页源代码
    泛型类,泛型方法,泛型委托的定义方法
    数组元素的逆序数
    stm32 ARM中的RO、RW和ZI DATA
    poj 3040 Allowance 贪心
    schedule()函数的调用时机(周期性调度)
    以JTextPanel为例Swing的鼠标事件详解
    实习生面试总结
  • 原文地址:https://www.cnblogs.com/mypride/p/4545299.html
Copyright © 2011-2022 走看看