关于最短路径问题(图论)

Posted konjac蒟蒻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于最短路径问题(图论)相关的知识,希望对你有一定的参考价值。

模版题为【hdu 2544】最短路。比较详细的解释请见:【转】彻底弄懂最短路径问题(图论)

前言:我先写一些总结性的话——
1.推荐使用优先队列优化后的Dijkstra算法,速度快又稳定,而SPFA算法虽快但不稳定;但也有特殊情况,譬如说:【uva 658】It\'s not a Bug, it\'s a Feature!(图论--Dijkstra或spfa算法+二进制表示+类“隐式图搜索”)这些类“隐式图搜索”的题。然而......似乎我们这边大家都喜欢用SPFA,因为最初接触的就是它而且它就是一个BFS,比较好打;
2.出现负边和判断负环都用Bellman-Ford算法(也就是SPFA算法);

3.Floyd算法本质是DP或贪心思想,枚举出了所有路径的情况,一些“合法性”“可到达性”的题目可以用它。

 

1.Dijkstra算法

概述:依次将当前没有标记过的,且与源点的距离最近的点标记 / 纳入联盟内,并拓展该点,更新与之相连边的所有另一点的离源点的距离。   P.S.之前我以为几乎就是MST中的Prim算法;这个原理我真心不怎么理解 _(눈_ 눈」∠)_
实现:邻接表+优先队列。
时间复杂度:O(n^2+m); O(n log n+m)。
应用:有向图和无向图,正权图上的单源最短路(Single-Source Shortest Paths, SSSP,即从单个源点出发到所有结点的最短路)。
注意——正权图上可能有零环和正环,但都不会影响最短路的计算;Dijkstra中优先队列的top()是汇点的值时就可以跳出,因为剩下的可以拓展的点都比它的值大,由于没有负权边,就不可能通过一些边又比它小了。

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<iostream>
 5 using namespace std;
 6 
 7 const int N=110,M=10010,D=1010,INF=(int)1e9;
 8 int n,m;
 9 int last[N],d[N],vis[N];
10 struct edge{int x,y,d,next;}a[2*M];
11 
12 int mmin(int x,int y) {return x<y?x:y;}
13 void ins(int len,int x,int y,int d)
14 {
15     a[len].x=x,a[len].y=y,a[len].d=d;
16     a[len].next=last[x],last[x]=len;
17 }
18 int Dijkstra()
19 {
20     memset(vis,0,sizeof(vis));
21     memset(d,63,sizeof(d));
22     d[1]=0;
23     for (int k=1;k<=n;k++)//加n个点进联盟
24     {
25       int p=0,mn=INF;
26       for (int i=1;i<=n;i++)
27         if (!vis[i] && d[i]<mn) p=i,mn=d[i];
28       vis[p]=1;
29       for (int i=last[p];i;i=a[i].next)
30       {
31         int y=a[i].y;
32         d[y]=mmin(d[y],d[p]+a[i].d);
33       }
34     }
35     return d[n];
36 }
37 int main()
38 {
39     while (1)
40     {
41       scanf("%d%d",&n,&m);
42       if (!n && !m) break;
43       memset(last,0,sizeof(last));
44       for (int i=1;i<=m;i++)
45       {
46         int x,y,d;
47         scanf("%d%d%d",&x,&y,&d);
48         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
49       }
50       printf("%d\\n",Dijkstra());
51     }
52     return 0;
53 }
54 
55 Dijkstra
Dijkstra
 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<queue>
 6 using namespace std;
 7 
 8 const int N=110,M=10010,D=1010,INF=(int)1e9;
 9 int n,m;
10 int last[N],vis[N],d[N];//,path[N];
11 struct edge{int x,y,d,next;}a[2*M];
12 struct node
13 {
14     int x,d;
15     bool operator < (const node& now) const
16     { return d>now.d; }// > 使原来的优先队列排序反转,dmin first out
17     node() {}
18     node(int u,int v) {x=u;d=v;}
19 };
20 priority_queue<node> q;//默认从大到小排序
21 
22 int mmin(int x,int y) {return x<y?x:y;}
23 void ins(int len,int x,int y,int d)
24 {
25     a[len].x=x,a[len].y=y,a[len].d=d;
26     a[len].next=last[x],last[x]=len;
27 }
28 int Dijkstra()//!!!important!!
29 {
30     while (!q.empty()) q.pop();
31     memset(vis,0,sizeof(vis));
32     memset(d,63,sizeof(d));
33     d[1]=0;
34     q.push(node(1,0)); //这里无 vis[x]=1;
35     while (!q.empty())
36     {
37       node now=q.top(); q.pop();
38       int x=now.x,tmp=now.d;
39       if (x==n) return d[x];//见“注意”
40       if (vis[x]) continue;//或 if (tmp!=d[x]) continue; 表示标记过了,不再用它更新相关的点,以防止重复拓展
41       vis[x]=1;
42       for (int i=last[x];i;i=a[i].next)
43       {
44         int y=a[i].y;
45         if (d[x]+a[i].d<d[y])
46         {
47           d[y]=d[x]+a[i].d;
48           //path[y]=x;
49           q.push(node(y,d[y]));//no judge
50         }
51       }
52     }
53     return -1;
54 }
55 int main()
56 {
57     while (1)
58     {
59       scanf("%d%d",&n,&m);
60       if (!n && !m) break;
61       memset(last,0,sizeof(last));
62       for (int i=1;i<=m;i++)
63       {
64         int x,y,d;
65         scanf("%d%d%d",&x,&y,&d);
66         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
67       }
68       printf("%d\\n",Dijkstra());
69     }
70     return 0;
71 }
72 
73 Dijkstra+优先队列(important!!)
Dijkstra+优先队列(important!!)

附上一个小笑话 ヾ(o◕∀◕)ノヾ Dijkstra这么厉害,为什么没有把求多点之间的最短路的Floyd算法也给发明了。——原因是,它叫Dijkstra,不叫Dkijstra。··(≧∀≦)··

 

2.Bellman-ford算法

概述:由于最多只包含n-1个结点,便通过n-1轮,对m条边进行松弛,更新端点的值。
实现:队列。

时间复杂度:O(nm)。
应用:有向图和无向图,正权和负权图的最短路,判断负环;若要算出单源最短路,需要让源点和每个点之间加一条权值为0的边,这样每个点都可以被访问。

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<queue>
 6 using namespace std;
 7 
 8 const int N=110,M=10010,D=1010,INF=(int)1e9;
 9 int n,m;
10 int last[N],vis[N],d[N];//,path[N];
11 struct edge{int x,y,d,next;}a[2*M];
12 
13 int mmin(int x,int y) {return x<y?x:y;}
14 void ins(int len,int x,int y,int d)
15 {
16     a[len].x=x,a[len].y=y,a[len].d=d;
17     a[len].next=last[x],last[x]=len;
18 }
19 int Bellman_ford()//!!!important!!
20 {
21     memset(vis,0,sizeof(vis));
22     memset(d,63,sizeof(d));
23     d[1]=0;
24     for (int k=1;k<n;k++)
25       for (int i=1;i<=m;i++)
26       {
27         int x=a[i].x,y=a[i].y;
28         d[y]=mmin(d[y],d[x]+a[i].d);
29       }
30     return d[n];
31 }
32 int main()
33 {
34     while (1)
35     {
36       scanf("%d%d",&n,&m);
37       if (!n && !m) break;
38       memset(last,0,sizeof(last));
39       for (int i=1;i<=m;i++)
40       {
41         int x,y,d;
42         scanf("%d%d%d",&x,&y,&d);
43         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
44       }
45       m=2*m;
46       printf("%d\\n",Bellman_ford());
47     }
48     return 0;
49 }
Bellman-ford算法

 

3.SPFA算法(Shortest Path Faster Algorithm)

概述:如它的名字,它其实是在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作
实现:队列。
时间复杂度:O(km),k为每个节点进入队列的次数,且k一般<=2;最坏情况是O(nm),比如图为完全图时会有n-1条边的松弛路径,每个点会入队n-1次。{详见Dijkstra、Bellman_Ford、SPFA、Floyd算法复杂度比较。}
应用:有向图和无向图,正权和负权图上的单源最短路,判断负环。拓展:若要算出单源最短路,需要让源点和每个点之间加一条权值为0的边,这样每个点都可以被访问。

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<queue>
 6 using namespace std;
 7 
 8 const int N=110,M=10010,D=1010,INF=(int)1e9;
 9 int n,m;
10 int last[N],inq[N],cnt[N],d[N];//,path[N];
11 struct edge{int x,y,d,next;}a[2*M];
12 queue<int> q;
13 
14 int mmin(int x,int y) {return x<y?x:y;}
15 void ins(int len,int x,int y,int d)
16 {
17     a[len].x=x,a[len].y=y,a[len].d=d;
18     a[len].next=last[x],last[x]=len;
19 }
20 int spfa()
21 {
22     while (!q.empty()) q.pop();
23     memset(d,63,sizeof(d));
24     memset(inq,0,sizeof(inq));
25     memset(cnt,0,sizeof(cnt));
26     q.push(1);
27     inq[1]=cnt[1]=1,d[1]=0;
28     while (!q.empty())
29     {
30       int x=q.front();
31       q.pop(), inq[x]=0;
32       for (int i=last[x];i;i=a[i].next)
33       {
34         int y=a[i].y;
35         if (d[x]+a[i].d<d[y])
36         {
37           d[y]=d[x]+a[i].d;
38           //path[y]=x; 或 path[y]=i;
39           if (!inq[y])
40           {
41             inq[y]=1,cnt[y]++;
42             if (cnt[y]>n) return -1;//> 判断负圈,一个点被更新了超过n次
43             q.push(y);
44           }
45         }
46       }
47     }
48     return d[n];
49 }
50 int main()
51 {
52     while (1)
53     {
54       scanf("%d%d",&n,&m);
55       if (!n && !m) break;
56       memset(last,0,sizeof(last));
57       for (int i=1;i<=m;i++)
58       {
59         int x,y,d;
60         scanf("%d%d%d",&x,&y,&d);
61         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
62       }
63       printf("%d\\n",spfa());
64     }
65     return 0;
66 }
Spfa(important!!)

唉,其实用 bfs 的 spfa 判断负环很慢!应该用 dfs 的。具体解释和代码请见:【洛谷 P3385】模板-负环(图论--spfa)

 

4.Floyd-warshall算法

概述:枚举所有情况,限制每两点中间通过的结点范围而松弛n轮。
实现:DP思想,递推。
时间复杂度:O(n^3)。
应用:有向图和无向图,正权和负权图上的每两点之间的最短路问题 和 连通性结果为有向图的传递闭包(Transitive Closure. 数学上定义为:在集合X上的二元关系R的传递闭包是包含R的X上的最小传递关系)问题。

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<queue>
 6 using namespace std;
 7 
 8 const int N=110,M=10010,D=1010,INF=(int)1e9;
 9 int n,m;
10 int d[N][N];
11 
12 int mmin(int x,int y) {return x<y?x:y;}
13 int Floyd()
14 {
15     for (int k=1;k<=n;k++)
16      for (int i=1;i<=n;i++)
17       for (int j=1;j<=n;j++)//此时的d[i][j]表示i,j间只能经过1~k点时的最短路径
18         d[i][j]=mmin(d[i][j],d[i][k]+d[k][j]);//原3维方程:d[k][i][j]=mmin(d[k][i][j],d[k-1][i][k]+d[k-1][k][j]);
19     return d[1][n];
20 }
21 int main()
22 {
23     while (1)
24     {
25       scanf("%d%d",&n,&m);
26       if (!n && !m) break;
27       memset(d,63,sizeof(d));
28       for (int i=1;i<=m;i++)
29       {
30         int x,y,t;
31         scanf("%d%d%d",&x,&y,&t);
32         d[x][y]=d[y][x]=t;
33       }
34       printf("%d\\n",Floyd());
35     }
36     return 0;
37 }
Floyd算法

 

以上是关于关于最短路径问题(图论)的主要内容,如果未能解决你的问题,请参考以下文章

图论:图的四种最短路径算法

彻底弄懂最短路径问题(图论)

图论之最短路径floyd算法

图论算法之最短路径

算法总结图论-最短路径

图论_最短路径