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\n", mp[1][n]);
123         //Dijkstra(1);
124         //if(BellManFord(1))
125         Spfa(1);
126         printf("%d\n", dis[n]);
127     }
128     return 0;
129 }

 

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

posted @ 2015-06-02 00:01  mypride  阅读(265)  评论(0编辑  收藏  举报