<学习笔记> 最短路
floyd 算法
Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。(摘自baidu)
时间复杂度 : O (n^3);
空间复杂度:O(n^2) ;
优缺点分析
优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
缺点:时间复杂度比较高,不适合计算大量数据。(摘自 baidu)
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5
6 int N,M,S,T,a,b,c;
7 int de[510][510];
8
9 int main()
10 {
11 scanf("%d%d%d%d",&N,&M,&S,&T);
12 memset(de,63,sizeof(de));
13 for(int i=1;i<=M;++i)
14 {
15 scanf("%d%d%d",&a,&b,&c);
16 de[a][b]=c;
17 de[b][a]=c;
18 }
19 for(int k=1;k<=N;++k) // 枚举中间点必须放在第一重循环
20 for(int i=1;i<=N;++i)
21 for(int j=1;j<=N;++j)
22 if(i!=k&&j!=k&&i!=j)
23 de[i][j]=min(de[i][j],de[i][k]+de[k][j]);
24 printf("%d",de[S][T]);
25 return 0;
26 }
BFS
可求边权都为1的图的单源最短路径。
dijkstra 算法
迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
求单源最短路径的算法。
采用贪心的思想,每次都用当前de最小的点来更新其他所有点,并把他标记为used(其他点都无法再更新他),直到所有点都不能更新为止。
复杂度 O(n^2)
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5
6 int N,M,S,T,cnt,a,b,c;
7 int Rode[2510][2510];
8 int first[2510],next[13000],de[2510];
9 bool used[2500];
10
11 int main()
12 {
13 scanf("%d%d%d%d",&N,&M,&S,&T);
14 memset(de,63,sizeof(de));
15 memset(Rode,63,sizeof(Rode));
16 for(int i=1;i<=M;++i)
17 {
18 scanf("%d%d%d",&a,&b,&c);
19 Rode[a][b]=c;
20 Rode[b][a]=c;
21 }
22 de[S]=0;
23 while(true)
24 {
25 int maxn=1e9+7,pos=0;
26 for(int i=1;i<=N;++i)
27 if(!used[i]&&maxn>de[i])
28 maxn=de[i],pos=i;
29 if(!pos) break;
30 used[pos]=1;
31 for(int i=1;i<=N;++i)
32 if(de[i]>de[pos]+Rode[pos][i])
33 de[i]=de[pos]+Rode[pos][i];
34 }
35 printf("%d",de[T]);
36 return 0;
37 }
dijkstra 堆优化
每次找de最小的点时用堆。
时间复杂度 O((m+n)logn)
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<queue>
5 using namespace std;
6
7 int N,M,S,T,cnt,a,b,c;
8 int first[2510],next[13000],de[2510];
9 bool used[2500];
10
11 struct maple{
12 int f,t,d;
13 }Rode[13000];
14 struct leaf{
15 int f,d;
16 bool operator <(const leaf &a) const{
17 return a.d<d;
18 }
19 };
20
21 void Build(int f,int t,int d)
22 {
23 Rode[++cnt]=(maple){f,t,d};
24 next[cnt]=first[f];
25 first[f]=cnt;
26 }
27
28 priority_queue<leaf> q;
29
30 int main()
31 {
32 scanf("%d%d%d%d",&N,&M,&S,&T);
33 memset(de,63,sizeof(de));
34 for(int i=1;i<=M;++i)
35 {
36 scanf("%d%d%d",&a,&b,&c);
37 Build(a,b,c);
38 Build(b,a,c);
39 }
40 de[S]=0;
41 q.push((leaf){S,0});
42 while(!q.empty())
43 {
44 leaf A=q.top();
45 q.pop();
46 while(!q.empty()&&used[A.f]) A=q.top(),q.pop();
47 used[A.f]=1;
48 if(A.f==T)
49 {
50 printf("%d",A.d);
51 break;
52 }
53 for(int i=first[A.f];i;i=next[i])
54 if(de[Rode[i].t]>A.d+Rode[i].d)
55 {
56 de[Rode[i].t]=A.d+Rode[i].d;
57 q.push((leaf){Rode[i].t,de[Rode[i].t]});
58 }
59 }
60 return 0;
61 }
Bellman-Ford 算法
Bellman - ford算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
时间复杂度 O (nm)
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<queue>
5 using namespace std;
6
7 int N,M,S,T,cnt,a,b,c;
8 int first[2510],next[13000],de[2510];
9
10 struct maple{
11 int f,t,d;
12 }Rode[13000];
13
14 void Build(int f,int t,int d)
15 {
16 Rode[++cnt]=(maple){f,t,d};
17 next[cnt]=first[f];
18 first[f]=cnt;
19 }
20
21 bool Bellman_ford()
22 {
23 memset(de,63,sizeof(de));
24 de[S]=0;
25 for(int i=1;i<N;++i)
26 for(int j=1;j<=cnt;++j)
27 if(de[Rode[j].t]>de[Rode[j].f]+Rode[j].d)
28 de[Rode[j].t]=de[Rode[j].f]+Rode[j].d;
29 for(int i=1;i<=cnt;++i) // 判负环
30 if(de[Rode[i].t]>de[Rode[i].f]+Rode[i].d)
31 return false;
32 return true;
33 }
34 int main()
35 {
36 scanf("%d%d%d%d",&N,&M,&S,&T);
37 for(int i=1;i<=M;++i)
38 {
39 scanf("%d%d%d",&a,&b,&c);
40 Build(a,b,c);
41 Build(b,a,c);
42 }
43 if(!Bellman_ford()) printf("-1");
44 else printf("%d",de[T]);
45 return 0;
46 }
SPFA 算法 (Bellman-ford 算法的队列优化)
SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,它还有一个重要的功能是判负环(在差分约束系统中会得以体现),在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。
期望时间复杂度:O(me), 其中m为所有顶点进队的平均次数,可以证明m一般小于等于2n。
每次从队列里弹出一个点进行更新,同时把最短路径被更新的点压进队列里。
与bfs算法比较,复杂度相对稳定。但在稠密图中复杂度比迪杰斯特拉算法差。(摘自 baidu)
总复杂度(玄学)
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<queue>
5 using namespace std;
6
7 int N,M,S,T,cnt,a,b,c;
8 int first[2510],next[13000],de[2510],rd[2510];
9 bool used[2510];
10
11 struct maple{
12 int f,t,d;
13 }Rode[13000];
14
15 void Build(int f,int t,int d)
16 {
17 Rode[++cnt]=(maple){f,t,d};
18 next[cnt]=first[f];
19 first[f]=cnt;
20 }
21
22 queue<int> q;
23 bool SPFA()
24 {
25 memset(de,63,sizeof(de));
26 de[S]=0,rd[S]=1,used[S]=1;
27 q.push(S);
28 while(!q.empty())
29 {
30 int A=q.front();
31 q.pop();
32 used[A]=0;
33 for(int i=first[A];i;i=next[i])
34 if(de[Rode[i].t]>de[A]+Rode[i].d)
35 {
36 de[Rode[i].t]=de[A]+Rode[i].d;
37 if(!used[Rode[i].t])
38 {
39 used[Rode[i].t]=1;
40 ++rd[Rode[i].t];
41 q.push(Rode[i].t);
42 if(rd[Rode[i].t]>N) // 判负环
43 return false;
44 }
45 }
46 }
47 return true;
48 }
49 int main()
50 {
51 scanf("%d%d%d%d",&N,&M,&S,&T);
52 for(int i=1;i<=M;++i)
53 {
54 scanf("%d%d%d",&a,&b,&c);
55 Build(a,b,c);
56 Build(b,a,c);
57 }
58 if(!SPFA()) printf("-1");
59 else printf("%d",de[T]);
60 return 0;
61 }
SPFA 的双端队列优化
入队时比较当前点和对头的最短路径长度,如果小于对头,就从头压入,否则就从尾压入。
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<queue>
5 using namespace std;
6
7 int N,M,S,T,cnt,a,b,c;
8 int first[2510],next[13000],de[2510],rd[2510];
9 bool used[2510];
10
11 struct maple{
12 int f,t,d;
13 }Rode[13000];
14
15 void Build(int f,int t,int d)
16 {
17 Rode[++cnt]=(maple){f,t,d};
18 next[cnt]=first[f];
19 first[f]=cnt;
20 }
21
22 deque<int> q;
23
24 bool SPFA()
25 {
26 memset(de,63,sizeof(de));
27 de[S]=0,rd[S]=1,used[S]=1;
28 q.push_back(S);
29 while(!q.empty())
30 {
31 int A=q.front();
32 q.pop_front();
33 used[A]=0;
34 for(int i=first[A];i;i=next[i])
35 if(de[Rode[i].t]>de[A]+Rode[i].d)
36 {
37 de[Rode[i].t]=de[A]+Rode[i].d;
38 if(!used[Rode[i].t])
39 {
40 used[Rode[i].t]=1;
41 ++rd[Rode[i].t];
42 if(!q.empty()&&de[Rode[i].t]<de[q.front()]) q.push_front(Rode[i].t);
43 else q.push_back(Rode[i].t);
44 if(rd[Rode[i].t]>N) return false; // 判负环
45 }
46 }
47 }
48 return true;
49 }
50 int main()
51 {
52 scanf("%d%d%d%d",&N,&M,&S,&T);
53 for(int i=1;i<=M;++i)
54 {
55 scanf("%d%d%d",&a,&b,&c);
56 Build(a,b,c);
57 Build(b,a,c);
58 }
59 if(!SPFA()) printf("-1");
60 else printf("%d",de[T]);
61 return 0;
62 }
分层图最短路
可以把de数组加一维来存状态解决分层图的问题。具体看题目。
最短路的例题 : codevs 1557 热浪 codevs 2273 扬帆远洋大战牧师妹酱
分层图最短路例题 :bzoj 2763 飞行路线 codevs 1391 伊吹萃香
奇怪的最短路问题 :codevs 2218 补丁vs错误 codevs 1324 昂贵的聘礼 洛谷 1032 字串变换