经典算法
单源最短路:
1.Bellman_ford(可判负环,可有负边)
d[i]表示起点S到i的最短路,那么d[i]=min{d[j]+w[j][i]}且存在j->i的边代价为w[j][i]
经过证明如果不存在负圈最多通过V-1次松弛就可以完成复杂度O(V*E)(V为结点数,E为边数)
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <set>
5 #include <algorithm>
6 #include <map>
7 #include <queue>
8 #include<vector>
9 #define maxn 50010
10 #define maxm 100010
11 #define INF 0x3fffffff
12 using namespace std;
13 struct edge{
14 int from,to,w;
15 edge(){}
16 edge(int _u,int _v,int _w){
17 from=_u,to=_v,w=_w;
18 }
19 };
20 edge G[maxm];
21 int V,E;
22 void addedge(int u,int v,int w){
23 G[E++]=edge(u,v,w);
24 }
25 int d[maxn];
26 int n,m;
27 //d[i] = min{d[j]+w[j][i]} {j->i}
28 void bellman_ford(int s){
29 V=n;
30 for(int i=0;i<V;++i)d[i]=INF;
31 d[s]=0;
32 while(1){
33 bool flag =false;
34 for(int i=0;i<E;++i){
35 edge e = G[i];
36 if(d[e.from]!=INF&&d[e.to]>d[e.from]+e.w){
37 d[e.to] = d[e.from]+e.w;
38 flag = true;
39 }
40 }
41 if(!flag)break;
42 }
43 }
44 int main (){
45 while(scanf("%d%d",&n,&m)!=EOF){
46 if(n==0&&m==0)break;
47 int u,v,w;
48 E=0;
49 for(int i=0;i<m;++i){
50 scanf("%d%d%d",&u,&v,&w);
51 addedge(u-1,v-1,w);
52 addedge(v-1,u-1,w);
53 }
54 bellman_ford(0);
55 printf("%d\n",d[n-1]);
56 }
57 }
判负环:看一下是不是多于V-1次松弛,如果有则存在负环
1 bool HaveNagativeLoop(){
2 memset(d,0,sizeof(d));
3 for(int i=0;i<V;++i){
4 for(int j=0;j<E;++j){
5 edge e = G[j];
6 if(d[e.to]>d[e.from]+e.w){
7 d[e.to] = d[e.from]+e.w;
8 if(i==V-1)return true;
9 }
10 }
11 }
12 return false;
13 }
2.dijkstra(无负边)
对于上述的d[i]=min{d[j]+w[j][i]}如果d[j]当前值不是d[j]能达到的最小值那么显然在后面d[i]还将更新,如何避免这种情况
选择d[j]已经是最小,即S->j的最短距离不再更新,如何选取?每次选择距离最短且未被使用的结点,用这个结点对其他节点进行松弛复杂度O(V*V)
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <set>
5 #include <algorithm>
6 #include <map>
7 #include <queue>
8 #include<vector>
9 #define maxn 1010
10 #define maxm 100010
11 #define INF 0x3fffffff
12 using namespace std;
13 //d[i]=min{d[j]+w[j][i]} {d[j]已经不再更新}
14 //每次找最小d[j]然后松弛
15 int G[maxn][maxn];
16 int d[maxn];
17 bool used[maxn];
18 int V;
19 void dijksta(int s){
20 for(int i=0;i<V;++i){
21 used[i]=false;
22 d[i]=INF;
23 }
24 d[s]=0;
25 while(1){
26 int v =-1;
27 for(int i=0;i<V;++i){
28 if(!used[i]&&(v==-1||d[i]<d[v]))v=i;
29 }
30 if(v==-1)break;
31 used[v]=true;
32 for(int i=0;i<V;++i){
33 d[i]=min(d[i],d[v]+G[v][i]);
34 }
35 }
36 }
37 void init(int n){
38 V = n;
39 for(int i=0;i<V;++i){
40 for(int j=0;j<V;++j){
41 if(i==j)G[i][j]=0;
42 else G[i][j]=INF;
43 }
44 }
45 }
46 int main (){
47 int n,m;
48 while(scanf("%d%d",&n,&m)!=EOF){
49 if(n==0&&m==0)break;
50 int u,v,w;
51 init(n);
52 for(int i=0;i<m;++i){
53 scanf("%d%d%d",&u,&v,&w);
54 G[u-1][v-1]=w;
55 G[v-1][u-1]=w;
56 }
57 dijksta(0);
58 printf("%d\n",d[n-1]);
59 }
60 }
来优化一下上面的算法,每次需要选择最短的一个结点,这个可以使用一个优先队列来维护当前所有边里面权值最小的边,所以算法复杂度变为O(V*log(E))
使用邻接表存储图比较容易写
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <set>
5 #include <algorithm>
6 #include <map>
7 #include <queue>
8 #include<vector>
9 #define maxn 1010
10 #define maxm 100010
11 #define INF 0x3fffffff
12 using namespace std;
13 struct edge {
14 int to,w;
15 edge(){}
16 edge(int _to,int _w){
17 to=_to;w=_w;
18 }
19 };
20 typedef pair<int,int> P;
21 int V;
22 vector<edge> G[maxn];
23 int d[maxn];
24 void dijkstra(int s){
25 priority_queue<P,vector<P>,greater<P> >q;
26 for(int i=0;i<V;++i)d[i]=INF;
27 d[s]=0;
28 q.push(P(d[s],s));
29 while(!q.empty()){
30 P p = q.top();
31 q.pop();
32 int v = p.second;
33 if(d[v]<p.first)continue;
34 for(int i=0;i<G[v].size();++i){
35 edge e = G[v][i];
36 if(d[e.to]>d[v]+e.w){
37 d[e.to]=d[v]+e.w;
38 q.push(P(d[e.to],e.to));
39 }
40 }
41 }
42 }
43 int main (){
44 int n,m;
45 while(scanf("%d%d",&n,&m)!=EOF){
46 for(int i=0;i<n;++i)G[i].clear();
47 V=n;
48 if(n==0&&m==0)break;
49 int u,v,w;
50 for(int i=0;i<m;++i){
51 scanf("%d%d%d",&u,&v,&w);
52 G[u-1].push_back(edge(v-1,w));
53 G[v-1].push_back(edge(u-1,w));
54 }
55 dijkstra(0);
56 printf("%d\n",d[n-1]);
57 }
58 }
3.floyd(任意两点的最短路,可有负边)
dp的思想 d[k+1][i][j]表示从0~k 里面选择中间结点从i到j的最短距离
那么当k为-1时d[0][i][j]=w[i][j]
考虑如何转移 从0~k-1中间选到0~k中选新的方案有两种情况:
不经过第k个结点 那么 d[k+1][i][j]=d[k][i][j]
经过第k个结点 那么d[k+1][i][j] = d[k][i][k]+d[k][k][j]
则方程为d[k+1][i][j]=min{ d[k][i][j],d[k][i][k]+d[k][k][j]}
可以使用滚动数组来优化空间,由于d[k+1][][]只与d[k][][]有关,所以只要保证k是从小到大枚举的就可以节省以为空间,复杂度为O(V*V*V)
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <set>
5 #include <algorithm>
6 #include <map>
7 #include <queue>
8 #include<vector>
9 #define maxn 1010
10 #define maxm 100010
11 #define INF 0x3fffffff
12 using namespace std;
13 int d[maxn][maxn];
14 int V;
15 void init(int n){
16 V=n;
17 for(int i=0;i<V;++i){
18 for(int j=0;j<V;++j){
19 if(i==j)d[i][j]=0;
20 else d[i][j]=INF;
21 }
22 }
23 }
24 void floyd(){
25 for(int k=0;k<V;++k){
26 for(int i=0;i<V;++i){
27 for(int j=0;j<V;++j){
28 d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
29 }
30 }
31 }
32 }
33 int main (){
34 int n,m;
35 while(scanf("%d%d",&n,&m)!=EOF){
36 if(n==0&&m==0)break;
37 int u,v,w;
38 init(n);
39 for(int i=0;i<m;++i){
40 scanf("%d%d%d",&u,&v,&w);
41 d[u-1][v-1]=w;
42 d[v-1][u-1]=w;
43 }
44 floyd();
45 printf("%d\n",d[0][n-1]);
46 }
47 }