最短路算法】
-
Dijkstra算法(o(nlogn))(权值非负)
#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=2e5+7; struct Edge { int to,next; ll len; }edge[maxn]; struct Node { int to; ll len; bool operator<(const Node& x)const{ return len>x.len; } Node(int x,ll y) { to=x; len=y; } }; ll dis[maxn]; int head[maxn]; int cnt; bool vis[maxn]; void add_edge(int u,int v,int w) { edge[++cnt].to=v; edge[cnt].len=w; edge[cnt].next=head[u]; head[u]=cnt; } int main() { int n,m,s; while(scanf("%d%d%d",&n,&m,&s)!=EOF) { int i,t,u,v; ll w; cnt=0; memset(head,-1,sizeof(head)); for(i=1;i<=m;i++) { scanf("%d%d%lld",&u,&v,&w); add_edge(u,v,w); } memset(vis,0,sizeof(vis)); priority_queue<Node>s; for(i=1;i<=n;i++) dis[i]=1e9+7; dis[1]=0; s.push(Node(1,0)); while(!s.empty()) { //cout<<"xxxxx"<<endl; Node o=s.top(); s.pop(); int u=o.to; if(vis[u]) continue; vis[u]=1; for(i=head[u];i!=-1;i=edge[i].next) { ll len=edge[i].len; int v=edge[i].to; //cout<<u<<' '<<v<<' '<<dis[u]<<' '<<dis[v]<<' '<<len<<endl; if(dis[v]>dis[u]+len) { dis[v]=dis[u]+len; s.push(Node(v,dis[v])); } } } for(i=1;i<n;i++) cout<<dis[i]<<' '; cout<<dis[n]<<endl; } }
-
bellman-ford算法(o(n*m))(权值可负)
#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=2010; const int Inf=1e9+7; struct Node { int to,len; Node(int a,int b){ to=a; len=b; } }; vector<Node>s[maxn]; int dis[maxn]; int cnt[maxn]; int n,m; int main() { int T; cin>>T; while(T--) { for(int i=1;i<=n;i++) s[i].clear(); cin>>n>>m; int i,t,u,v,w; memset(cnt,0,sizeof(cnt)); for(i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); if(w>=0) { s[u].push_back({v,w}); s[v].push_back({u,w}); } else s[u].push_back({v,w}); } for(i=1;i<=n;i++) dis[i]=Inf; queue<int>x; x.push(1); dis[1]=0; int z=0; while(!x.empty()) { int u=x.front(); x.pop(); if(cnt[u]>=n) { z++; break; } for(i=0;i<s[u].size();i++) { Node y=s[u][i]; int v=y.to,w=y.len; if(w+dis[u]<dis[v]) { dis[v]=w+dis[u]; cnt[v]=cnt[u]+1; x.push(v); } } } if(z>0) cout<<"YES"<<endl; else cout<<"NO"<<endl; } }
-
bellman算法(o(n*m))
//注意不能有负边
-
flod算法
for(k=1;k<=n;k++) for(i=1;i<=n;i++) for(t=1;t<=n;t++) if(len[i][t]>len[i][k]+len[k][t]) len[i][t]=len[i][k]+len[k][t];
-
johnson算法(多源最短路)
解决单源最短路径问题(Single Source Shortest Paths Problem)的算法包括:
- Dijkstra 单源最短路径算法:时间复杂度为 O(E + VlogV),要求权值非负;
- Bellman-Ford 单源最短路径算法:时间复杂度为 O(VE),适用于带负权值情况;
对于全源最短路径问题(All-Pairs Shortest Paths Problem),可以认为是单源最短路径问题的推广,即分别以每个顶点作为源顶点并求其至其它顶点的最短距离。例如,对每个顶点应用 Bellman-Ford 算法,则可得到所有顶点间的最短路径的运行时间为 O(V2E),由于图中顶点都是连通的,而边的数量可能会比顶点更多,这个时间没有比 Floyd-Warshall 全源最短路径算法 O(V3) 更优。那么,再试下对每个顶点应用 Dijkstra 算法,则可得到所有顶点间的最短路径的运行时间为 O(VE + V2logV),看起来优于 Floyd-Warshall 算法的 O(V3),所以看起来使用基于 Dijkstra 算法的改进方案好像更好,但问题是 Dijkstra 算法要求图中所有边的权值非负,不适合通用的情况。
在 1977 年,Donald B. Johnson 提出了对所有边的权值进行 “re-weight” 的算法,使得边的权值非负,进而可以使用 Dijkstra 算法进行最短路径的计算。
我们先自己思考下如何进行 “re-weight” 操作,比如,简单地对每条边的权值加上一个较大的正数,使其非负,是否可行?
1 1 1 s-----a-----b-----c \ / \ / \______/ 4
比如上面的图中,共 4 条边,权值分别为 1,1,1,4。当前 s --> c 的最短路径是 {s-a, a-b, b-c} 即 1+1+1=3。而如果将所有边的权值加 1,则最短路径就会变成 {s-c} 的 5,因为 2+2+2=6,实际上导致了最短路径的变化,显然是错误的。
那么,Johnson 算法是如何对边的权值进行 “re-weight” 的呢?以下面的图 G 为例,有 4 个顶点和 5 条边。
首先,新增一个源顶点 4,并使其与所有顶点连通,新边赋权值为 0,如下图所示。
使用 Bellman-Ford 算法 计算新的顶点到所有其它顶点的最短路径,则从 4 至 {0, 1, 2, 3} 的最短路径分别是 {0, -5, -1, 0}。即有 h[] = {0, -5, -1, 0}。当得到这个 h[] 信息后,将新增的顶点 4 移除,然后使用如下公式对所有边的权值进行 “re-weight”:
w(u, v) = w(u, v) + (h[u] - h[v]).
则可得到下图中的结果:
此时,所有边的权值已经被 “re-weight” 为非负。此时,就可以利用 Dijkstra 算法对每个顶点分别进行最短路径的计算了。
Johnson 算法描述如下:
- 给定图 G = (V, E),增加一个新的顶点 s,使 s 指向图 G 中的所有顶点都建立连接,设新的图为 G’;
- 对图 G’ 中顶点 s 使用 Bellman-Ford 算法计算单源最短路径,得到结果 h[] = {h[0], h[1], … h[V-1]};
- 对原图 G 中的所有边进行 “re-weight”,即对于每个边 (u, v),其新的权值为 w(u, v) + (h[u] - h[v]);
- 移除新增的顶点 s,对每个顶点运行 Dijkstra 算法求得最短路径;
… h[V-1]};
3. 对原图 G 中的所有边进行 “re-weight”,即对于每个边 (u, v),其新的权值为 w(u, v) + (h[u] - h[v]);
4. 移除新增的顶点 s,对每个顶点运行 Dijkstra 算法求得最短路径;