在带权图 G = ( V , E ) G = (V, E) G=(V,E) 中,每条边都有一个权值 w i w_i wi ,即边的长度。路径的长度为路径上所有边权之和。
最短路问题是指:求图中某点到另一点的最短路径的长度。 例如下图:
从点 1 1 1到点 4 4 4的最短路为 5 5 5,路径是 1 → 2 → 3 → 4 1\rightarrow 2\rightarrow 3\rightarrow 4 1→2→3→4。 特殊地,对于无权图或者带权图每条边权值相同的图,最短路可以通过BFS得到。
但是对于一般的带权图,就不能通过BFS得到最短路了,因为会出现两条边加起来小于一 边的情况,比如上图中的 1 → 2 → 3 1\rightarrow 2\rightarrow 3 1→2→3长度小于 1 → 3 1\rightarrow 3 1→3,这样如果BFS先访问到3,把最短路记录为 1 → 3 1\rightarrow 3 1→3的长度5,后续也不再更改,那就不对了。
接下来我们\解决最短路问题的相关算法,通过接下来学到的算法,我们就可以解决带权图最短路问题了。
接下来介绍一种算法Floyd算法可以解决最短路问题,所谓多源则是它可以求出以每个点为起点到其它每个点的最短路。
不过其实有一种特殊情况是求不出最短路的,就是有负环的情况,此时可以不断在这个负环上转圈,所以负环上的点之间最短路为负无穷,这种情况下一般也只是判断出负环,不做其它处理,Floyd 算法无法判断这样的情况,所以就在没有负环的情况下使用,负环的判断在之后的算法中会给出方法。
Floyd算法是-种利用动态规划的思想、计算给定的带权图中任意两个顶点之间最短路径的算法。无权图可以直接把边权看
作1。
我们用 d p [ k ] [ i ] [ j dp[k][i][j dp[k][i][j]示 i i i到 j j j能经过1 ~ k的点的最短路。那么实际上 d p [ 0 ] [ i ] [ j ] dp[0][i][j] dp[0][i][j]就是原图,如果 i , j i,j i,j之间存在边,那么
i , j i,j i,j之间不经过任何点的最短路就是边长,则 i , j i, j i,j之间的最短路为无穷大。
那么对于 i , j i,j i,j之间经过1 ~ k的最短路 d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j]可以通过经过1 ~ k - 1的最短路转移过来。
●如果不经过第k个点,那么就是 d p [ l k − 1 ] [ i ] [ j ] dp[lk - 1][i][j] dp[lk−1][i][j]。
●如果经过第k个点,那么就是 d p [ k − 1 ] [ i ] [ k ] + d p [ k − 1 ] [ k ] [ j ] dp[k - 1][i][k] + dp[k - 1][k][j] dp[k−1][i][k]+dp[k−1][k][j]。
所以就有转移
d p [ k ] [ i ] [ j ] = m i n ( d p [ k − 1 ] [ i ] [ j ] , d p [ k − 1 ] [ i ] [ k ] + d p [ k − 1 ] [ k ] [ j ] ) \displaystyle dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]) dp[k][i][j]=min(dp[k−1][i][j],dp[k−1][i][k]+dp[k−1][k][j])
我们仔细分析, d p [ k ] dp[k] dp[k] 只能由 d p [ k − 1 ] dp[k - 1] dp[k−1]转移过来。并且 d p [ k − 1 ] [ i ] [ k ] = d p l [ k ] [ i ] [ k ] dp[k - 1][i][k] = dpl[k][i][k] dp[k−1][i][k]=dpl[k][i][k],因为 i i i到 k k k的最短路中间肯定不会
经过 k k k。同理, d p [ k − 1 ] [ k ] [ j ] = d p [ k ] [ k ] [ i ] dp[k - 1][k][j] = dp[k][k][i] dp[k−1][k][j]=dp[k][k][i]。
那么转移实际上变成了
d p [ k ] [ i ] [ j ] = m i n ( d p [ k − 1 ] [ i ] [ j ] , d p [ k ] [ i ] [ k ] + d p [ k ] [ k ] [ j ] ) dp[k][i][j]=min(dp[k−1][i][j],dp[k][i][k]+dp[k][k][j]) dp[k][i][j]=min(dp[k−1][i][j],dp[k][i][k]+dp[k][k][j])
这时候,我们尝试把 k k k这一维去掉,就用 d p [ i ] [ j ] dp[i][j] dp[i][j] 来表示 i , j i, j i,j之间的最短路,那么转移变成了
∀ 1 ≤ k ≤ n ∀1 \leq k \leq n ∀1≤k≤n d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k ] [ j ] ) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
我们写出最终的 Floyd 的形式,这也是常用的写法,优化了一维的空间。并且写法更加简单。如果理解了动态规划的思想,你就一定明白了为什么枚举的中间点 k k k一定要写在最外面。没有理解这一点,很容易把 3 3 3个循环的顺序弄错了。
#include
#include
#include
#include
using namespace std;
const int MAXN=505;
int floyd[MAXN][MAXN];
int s,t;
int main(){
memset(floyd,0x3f,sizeof(floyd));
int n,m;
scanf("%d",&n);
for(int i=1;i<=n;i++){
floyd[i][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&floyd[i][j]);
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(floyd[i][j]>floyd[i][k]+floyd[k][j]){
floyd[i][j]=floyd[i][k]+floyd[k][j];
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d ",floyd[i][j]);
}
printf("\n");
}
return 0;
}
这里在使用的时候因为邻接矩阵存的是距离,所以需要把不连通的部分赋值为一个很大的整数,避免其对答案产生影响,这样也可以根据这个无穷大判断两点间是否可达。
如果需要记录路径,可以记录一下每两个点之间最后是被哪个点更新的,然后一条路就可以被拆成两半不断递归找到路径了。
#include
#include
#include
using namespace std;
const int MAXN=205;
int G[MAXN][MAXN];
int dis[MAXN];
bool vis[MAXN];
int pre[MAXN];
void print(int x){
if(pre[x]==0){
printf("%d",x);
return;
}
print(pre[x]);
printf(" %d",x);
}
int main(){
int n,m,s,t;
scanf("%d %d",&n,&m);
int a,b;
memset(G,0x3f,sizeof(G));
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=m;i++){
scanf("%d %d",&a,&b);
G[a][b]=1;
}
scanf("%d %d",&s,&t);
dis[s]=0;
for(int i=1;i<=n;i++){
int minn=0x3f3f3f3f;
int k=0;
for(int j=1;j<=n;j++){
if(vis[j]==0 && dis[j]<minn){
minn=dis[j];
k=j;
}
}
if(k==0) continue;
vis[k]=1;
for(int j=1;j<=n;j++){
if(dis[j]>dis[k]+G[k][j]){
dis[j]=dis[k]+G[k][j];
pre[j]=k;
}
}
}
if(s==t){
printf("0\n%d",s);
return 0;
}
if(dis[t]>m){
printf("no solution");
return 0;
}
printf("%d\n",dis[t]);
print(t);
return 0;
}
解决单源最短路径问题常用 Dijkstra 算法,用于计算一个顶点到其他所有顶点的最短路径。Dijkstra 算法的主要特点是以起点为中心,逐层向外扩展(这一点类似于 bfs,但是不同的是,bfs 每次扩展一个层,但是 Dijkstra 每次只会扩展一个点),每次都会取一个最近点继续扩展,直到取完所有点为止。
注意:Dijkstra 算法要求图中不能出现负权边。
我们定义带权图 G G G 所有顶点的集合为 V V V,接着我们再定义已确定从源点出发的最短路径的顶点集合为 U U U,初始集合 U U U 为空,记从源点 s s s 出发到每个顶点 v v v 的距离为 d v d_v dv ,初始 d s = 0 d_s=0 ds=0接着执行以下操作:
1.从 V − U V-U V−U中找出一个距离源点最近的顶点 v v v,将 v v v加入集合 U U U。
2.并用 d v d_v dv 和顶点 v v v连出的边来更新和 v v v相邻的、不在集合 U U U中的顶点的 d d d,这一步称为松弛操作。
3.重复步骤 1 和 2,直到 V = U V=U V=U 或找不出一个从 s s s出发有路径到达的顶点,算法结束。
如果最后 V ≠ U V \neq U V=U,说明有顶点无法从源点到达;否则每个 d i d_i di 表示从 s s s出发到顶点 i i i的最短距离。
Dijkstra 算法的时间复杂度为 O ( V 2 ) \mathcal {O}(V^2) O(V2),其中 V V V表示顶点的数量。
初始每个顶点的 d d d设置为无穷大 inf \inf inf,源点 M M M的 d M d_M dM设置为 0 0 0。当前 U = ∅ U=\emptyset U=∅, V − U V-U V−U中 d d d最小的顶点是 M M M。从顶点 M M M 出发,更新相邻点的 d d d。
更新完毕,此时 U = { M } U=\{M\} U={M}, V − U V-U V−U中 d d d 最小的顶点是 W W W。从 W W W 出发,更新相邻点的 d d d。
更新完毕,此时 U = { M , W } U=\{M,W\} U={M,W}, V − U V-U V−U 中 d d d 最小的顶点是 E E E。从 E E E出发,更新相邻顶点的 d d d。
更新完毕,此时 U = { M , W , E } U=\{M, W, E\} U={M,W,E}, V − U V-U V−U 中 d d d 最小的顶点是 X X X。从 X X X出发,更新相邻顶点的 d d d。
更新完毕,此时 U = { M , W , E , X } U=\{M,W,E,X\} U={M,W,E,X}, V − U V-U V−U 中 d d d 最小的顶点是 D D D。从 D D D 出发,没有其他不在集合 U U U 中的顶点。
此时 U = V U=V U=V,算法结束,单源最短路计算完毕。
#include
#include
#include
using namespace std;
const int MAXN=2505;
int G[MAXN][MAXN];
int dis[MAXN];
bool vis[MAXN];
int main(){
int n,m,s,t;
scanf("%d %d %d %d",&n,&m,&s,&t);
int a,b,c;
memset(G,0x3f,sizeof(G));
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=m;i++){
scanf("%d %d %d",&a,&b,&c);
G[a][b]=G[b][a]=c;
}
vis[s]=0;
dis[s]=0;
for(int i=1;i<=n;i++){
int minn=0x3f3f3f3f;
int k=0;
for(int j=1;j<=n;j++){
if(vis[j]==0 && dis[j]<minn){
minn=dis[j];
k=j;
}
}
if(!k) continue;
vis[k]=1;
for(int j=1;j<=n;j++){
if(dis[j]>dis[k]+G[k][j]){
dis[j]=dis[k]+G[k][j];
}
}
}
printf("%d",dis[t]);
return 0;
}
时间复杂度 O ( V 2 ) \mathcal {O}(V^2) O(V2),考虑优化
首先是松弛与 k k k点相邻的点操作:
for(int j=1;j<=n;j++){
if(dis[j]>dis[k]+G[k][j]){
dis[j]=dis[k]+G[k][j];
}
}
很容易想到用邻接表进行优化,只枚举相邻的即可
还可以更快?
我们把眼光转向寻找距离原点最近的点的那一波操作,找最值,想到什么?没错,优先队列(堆)
最终优化:
#include
#include
#include
#include
using namespace std;
const int MAXN = 2505;
bool vis[MAXN];
int n, m, dis[MAXN];
struct edge{
int v,w;
edge(){};
edge(int V, int W) {
v=V;
w=W;
}
};
struct node{
int u,dis_;
node(){}
node(int U,int D) {
u=U;
dis_=D;
}
bool operator<(const node a)const{return dis_>a.dis_;}
};
priority_queue<node> q;
vector<edge> cost[MAXN];
void add(int su,int sv,int scost) {
cost[su].push_back(edge(sv,scost));
cost[sv].push_back(edge(su,scost));
}
void Dijkstra(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push(node(s,0));
while(!q.empty()) {
int t=q.top().u;
q.pop();
if(vis[t]){
continue;
}
vis[t]=true;
for(int i=0;i<cost[t].size();i++){
int v=cost[t][i].v;
if(dis[v]>dis[t]+cost[t][i].w){
dis[v]=dis[t]+cost[t][i].w;
q.push(node(v,dis[v]));
}
}
}
}
int main() {
int n,m;
int s,t;
scanf("%d %d %d %d", &n, &m,&s,&t);
for(int i=1;i<=m;i++) {
int t1,t2,t3;
scanf ("%d %d %d",&t1,&t2,&t3);
add(t1,t2,t3);
}
Dijkstra(s);
printf("%d",dis[t]);
return 0;
}
时间复杂度 O ( M l o g M ) , M \mathcal {O}(MlogM),M O(MlogM),M为边数
曾今被我念成了 B e l l m a n − F l o y d Bellman-Floyd Bellman−Floyd [手动狗头]
Bellman-Ford算法:对每条边执行更新,迭代N-1次。
具体操作是对图进行最多n-1次松弛操作,每次操作对所有的边进行松弛,为什么是n-1次操作呢?这是因为我们输入的边不一定是按源点由近至远,万一是由远至近最坏情况就得n-1次,我们可以以一个单链 A → B → C → D A\rightarrow B\rightarrow C\rightarrow D A→B→C→D来举例
初始化为全部距离原点为INF
假如现在输入边是 C → D , 3 C \rightarrow D,3 C→D,3
然鹅没有任何一条边得到松弛
又输入 B → C , 2 B \rightarrow C,2 B→C,2
然鹅依然没有任何一条边得到松弛
现在输入 A → B , 1 A \rightarrow B,1 A→B,1
哎,好像可以了,只需要从A到B一路松弛下去,就OK了
好消息:Bellman-Ford可以应用于负权图
初始化
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 、 3 、 4 、 5 、 6 ] = ∞ Dis[2、3、4、5、6] = ∞ Dis[2、3、4、5、6]=∞
对第1条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 、 4 、 5 、 6 ] = ∞ Dis[3、4、5、6] = ∞ Dis[3、4、5、6]=∞
对第2条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 、 5 、 6 ] = ∞ Dis[4、5、6] = ∞ Dis[4、5、6]=∞
对第3条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 、 5 、 6 ] = ∞ Dis[4、5、6] = ∞ Dis[4、5、6]=∞
对第4条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 5 Dis[4] = 5 Dis[4]=5
D i s [ 5 、 6 ] = ∞ Dis[5、6] = ∞ Dis[5、6]=∞
对第5条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 5 Dis[4] = 5 Dis[4]=5
D i s [ 5 ] = 10 Dis[5] = 10 Dis[5]=10
D i s [ 6 ] = ∞ Dis[6] = ∞ Dis[6]=∞
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 5 Dis[4] = 5 Dis[4]=5
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = ∞ Dis[6] = ∞ Dis[6]=∞
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 5 Dis[4] = 5 Dis[4]=5
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = 11 Dis[6] = 11 Dis[6]=11
对第8条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 5 Dis[4] = 5 Dis[4]=5
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = 11 Dis[6] = 11 Dis[6]=11
到此,这个图的单源最短路已全部求出
现在让我们看看负权图吧:
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 、 3 、 4 、 5 、 6 ] = ∞ Dis[2、3、4、5、6] = ∞ Dis[2、3、4、5、6]=∞
对第1条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 、 4 、 5 、 6 ] = ∞ Dis[3、4、5、6] = ∞ Dis[3、4、5、6]=∞
对第2条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = 1 Dis[2] = 1 Dis[2]=1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 、 5 、 6 ] = ∞ Dis[4、5、6] = ∞ Dis[4、5、6]=∞
对第3条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 5 Dis[2] = -5 Dis[2]=−5
D i s [ 3 ] = − 2 Dis[3] = -2 Dis[3]=−2
D i s [ 4 、 5 、 6 ] = ∞ Dis[4、5、6] = ∞ Dis[4、5、6]=∞
唉?dis[2]貌似有什么不对,-5怎么走出来的呀?不对,-5好像还真的走出来, 1 → 2 → 3 → 2 1\rightarrow2\rightarrow3\rightarrow2 1→2→3→2就行了,发现了什么, 2 → 3 2\rightarrow3 2→3是一个“负环”,其实,在无向图中,凡是有负权边就相当于有负环了,因为可以在这条边上左右横跳,就可以让路程无限短。怎么办呢?那就不管它了呗,先换成有向图走完再说
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 1 Dis[2] = -1 Dis[2]=−1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 、 5 、 6 ] = ∞ Dis[4、5、6] = ∞ Dis[4、5、6]=∞
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 1 Dis[2] = -1 Dis[2]=−1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 3 Dis[4] = 3 Dis[4]=3
D i s [ 5 、 6 ] = ∞ Dis[5、6] = ∞ Dis[5、6]=∞
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 1 Dis[2] = -1 Dis[2]=−1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 3 Dis[4] = 3 Dis[4]=3
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = ∞ Dis[6] = ∞ Dis[6]=∞
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 1 Dis[2] = -1 Dis[2]=−1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 3 Dis[4] = 3 Dis[4]=3
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = ∞ Dis[6] = ∞ Dis[6]=∞
对第7条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 1 Dis[2] = -1 Dis[2]=−1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 3 Dis[4] = 3 Dis[4]=3
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = 10 Dis[6] = 10 Dis[6]=10
对第8条边双向进行松弛之后
D i s [ 1 ] = 0 Dis[1] = 0 Dis[1]=0
D i s [ 2 ] = − 1 Dis[2] = -1 Dis[2]=−1
D i s [ 3 ] = 2 Dis[3] = 2 Dis[3]=2
D i s [ 4 ] = 3 Dis[4] = 3 Dis[4]=3
D i s [ 5 ] = 8 Dis[5] = 8 Dis[5]=8
D i s [ 6 ] = 10 Dis[6] = 10 Dis[6]=10
OK,求完了,等等,刚才不是还留下了一个负环的问题吗?
我们想想负环是不是可以无限松弛,那么我们就可以在算法跑完后再跑一遍,看看有没有可以继续松弛的,如果有,那么这个图一定有负环
附赠路径输出
#include
#include
#include
using namespace std;
const int MAXN=505;
struct edge{
int u,v;
int val;
}G[MAXN];
int n,m;
int dis[MAXN];
int pre[MAXN];
int w[MAXN];
int s,t;
void print(int x){
if(pre[x]==0){
printf("%d",x);
return;
}
print(pre[x]);
printf(" %d",x);
}
bool BellmanFord(){
dis[s]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(dis[G[j].v]>dis[G[j].u]+G[j].val){
dis[G[j].v]=dis[G[j].u]+G[j].val;
pre[G[j].v]=G[j].u;
}
else if(dis[G[j].v]==dis[G[j].u]+G[j].val){
dis[G[j].v]=dis[G[j].u]+G[j].val;
pre[G[j].v]=min(G[j].u,pre[G[j].v]);
}
}
}
bool flag=true;
for(int i=1;i<=m;i++){//判负环
if(dis[G[i].v]>dis[G[i].u]+G[i].val){
return 0;
}
}
return flag;
}
int main(){
memset(dis,0x3f,sizeof(dis));
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d %d",&G[i].u,&G[i].v,&G[i].val);
pre[G[i].v]=G[i].u;
}
scanf("%d %d",&s,&t);
if(!BellmanFord()){
printf("No Solution");
}
else{
printf("%d\n",dis[t]);
print(t);
}
return 0;
}
它死了
NOI2018 Day 1,T1 出题人卡了 SPFA 并在讲课时说其死了。
在Bellmanford算法中,有许多松弛是无效的。这给了我们很大的改进的空间。SPFA算法正是对Bellmanford算法的改进。它是由西南交通大学段丁凡1994提出的。它采用了队列和松弛技术。先将源点加入队列。然后从队列中取出一个点(此时该点为源点),对该点的邻接点进行松弛,如果该邻接点松弛成功且不在队列中,则把该点加入队列。如此循环往复,直到队列为空,则求出了最短路径。
判断有无负环:如果某个点进入队列的次数超过N次则存在负环 ( 存在负环则无最短路径,如果有负环则会无限松弛,而一个带n个点的图至多松弛n-1次)
在 SPFA 算法中,使用 d i d_i di表示从源点到顶点 i i i的最短路,额外用一个队列来保存即将进行拓展的顶点列表,并用 i n _ q u e u e i in\_queue_i in_queuei 来标识顶点 i i i是不是在队列中。
重复步骤 2 2 2直到队列为空
最终 d d d 数组就是从源点出发到每个顶点的最短路距离。如果一个顶点从没有入队,则说明没有从源点到该顶点的路径。
如果进队次数超过 n n n次,那么说明图中存在负环。
在一定程度上,也可以认为 SPFA 是由 BFS 的思想转化而来。从不含边权或者说边权为 1 1 1个单位长度的图上的 BFS,推广到带权图上,就得到了 SPFA。只是 BFS 能保证第一次访问就一定是最短路,而 SPFA 在每次更新了最短路以后又重新入队从而去更新后续结点的最短路。比如下图, 2 2 2搜到 3 3 3的时候会再次更新 3 3 3最短路。
#include
#include
#include
#include
#include
using namespace std;
const int MAXN=1000005;
struct node{
int v;
int w;
node(){}
node(int V,int W){
v=V,w=W;
}
};
int n,m,s,t;
vector<node> G[MAXN];
queue<int> que;
bool vis[MAXN];
int dis[MAXN];
void add(int u,int v,int w){
G[u].push_back(node(v,w));
}
void spfa(){
dis[s]=0;
vis[s]=1;
que.push(s);
while(!que.empty()){
int u=que.front();
que.pop();
vis[u]=0;
for(int i=0;i<G[u].size();i++){
int v=G[u][i].v,w=G[u][i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
que.push(v);
vis[v]=1;
}
}
}
}
}
int main(){
scanf("%d %d",&n,&m);
int a,b,c;
s=1,t=n;
for(int i=1;i<=m;i++){
scanf("%d %d %d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=n;i++){
dis[i]=0x3f3f3f3f;
}
spfa();
printf("%d",dis[t]);
return 0;
}