探秘SPFA——强大的单源最短路径算法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探秘SPFA——强大的单源最短路径算法相关的知识,希望对你有一定的参考价值。
基于上次发blog,有位朋友让我多写些基本概念,就利用这次详解伟大的SPFA算法来谈。以下是百科上的算法简介,很清楚,看一遍再继续对理解程序很有帮助!(当然后面我也会解释)
SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,它还有一个重要的功能是判负环(在差分约束系统中会得以体现),在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。
求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm,是西南交通大学段凡丁于1994年发表的(中国人的算法就是牛)。从名字我们就可以看出,这种算法在效率上一定有过人之处。很多时候,给定的图存在负权边,这时类似Dijkstra算法等便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。简洁起见,我们约定加权有向图G不存在负权回路,即最短路径一定存在。如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
顺便解释一下“松弛”:松弛操作是指对于每个顶点v∈V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界,称为最短路径估计(shortest-path estimate)。 ————摘自《百度百科》
它的定义在上面第一句话解释的不能再简洁了,理解上述就好了。
至少我认为,SPFA算法是所有单源最短路算法中最实用的一种。(大佬们可以有其他想法,在此仅表示本人观点)
还是用一道求最短路径的模板题来解释:
题目描述
【样例2输入】 3 3 1 2 5 2 3 5 3 1 2 【样例2输出】 2
【样例3输入】 6 9 1 2 7 1 3 9 1 5 14 2 3 10 2 4 15 3 4 11 3 5 2 4 6 6 5 6 9 【样例3输出】 20
故事:如何所有点(包括终点)到出发点的距离最短(最近)。 1、给出一个图有N个点,和一些有向边(无向边也行,多建立一个反向边就是) 2、一开始出发点到出发点的距离为0,其它点到出发点的距离为无穷大。 3、核心思路:其他点都在迫切的想知道自己到出发点的距离,并且他们都想自己的好朋友能更近一点到出发点(更新自己的好朋友到出发点的距离) 4、核心思路:一个点什么时候能更新自己好朋友到出发点的距离呢?当自己到出发点的距离变得更短的时候。 5、核心思路:我们建一个队列q,让能更新别人的点站到q里面。然后让q中的点一个一个出来更新。 6、最后没人出来更新了,就结束,表示所有点到出发点的距离都是最短了。
我用的是模拟链表存图,你用其他的邻接矩阵也可以(要看点的规模,一般太大用邻接矩阵很划不来)
来吧,不废话,上代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 10005, M = 200005, oo = 0x3fffffff; //N表示最大点的个数 ,M表示最大边的个数 ,oo是无穷大
struct Edge{
int to,wei,next; //邻接链表的套路,to邻接顶点,wei表边的权重,next表链表指针
};
Edge edge[M]; //储存边的信息
int n,m,source,head[N],x,y,c,en(0),dist[N]; //dist【i】表示起点到 i的最短距离
bool inq[N];
queue<int> q;
void Addedge(int x,int y,int c) //存图 ,x到y有一条权重为c的边
{
edge[en].to=y;
edge[en].wei=c;
edge[en].next=head[x];
head[x]=en++;
}
void init()
{
cin>>n>>m;
memset(head,-1,sizeof(head)); //清零,作为每个点dfs的终止标志
fill(inq,inq+n+1,false); //一开始都不在队列中
while (m--)
{
cin>>x>>y>>c;
Addedge(x,y,c);
Addedge(y,x,c);
}
fill(dist,dist+n+1,oo); //先初始化为无穷大
source=1; //起点
}
void spfa() //这是套路
{
q.push(source);
dist[source]=0;
inq[source]=true;
while (!q.empty())
{
int u=q.front();
q.pop();
inq[u]=false;
for (int p=head[u];p!=-1;p=edge[p].next) //遍历整张图
{
int v=edge[p].to;
if (dist[v]>dist[u]+edge[p].wei) //如果到v的距离大于到u再加上u到v的距离,就更新
{
dist[v]=dist[u]+edge[p].wei;
if (inq[v]!=true)
{
q.push(v);
inq[v]=true; //指标记,防止重复进入,否则队列没有意义
}
}
}
}
}
void output()
{
cout<<dist[n]<<endl; //输出到终点的距离就行了,其实你想输出到哪点的最短距离都可以
}
int main()
{
init();//输入存图
spfa();//算法核心
output();//输出答案
return 0;
}
代码很简洁,希望大家理解!
了解SPFA是十分有用的,如果认为自己掌握的不错,就可以去做[NOIP提高组2009]最优贸易。提示:正反两遍SPFA。
题解附上,最好先不看:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const int N = 100005, M = 500005; struct Edge{ int to,next; }; Edge edge[4*M]; int n,m,en(0),price[N],head1[N],head2[N],buy[N],sell[N]; bool inq[N]; queue< int > q; void insert( int head[], int x, int y){ edge[en].to = y; edge[en].next = head[x]; head[x] = en++; } void init(){ //freopen("trade.in","r",stdin); scanf ( "%d%d" ,&n,&m); for ( int i=1;i<=n;i++) scanf ( "%d" ,&price[i]); memset (head1,-1, sizeof (head1)); memset (head2,-1, sizeof (head2)); for ( int i=0,x,y,z;i<m;i++){ scanf ( "%d%d%d" ,&x,&y,&z); insert(head1,x,y); insert(head2,y,x); if (z==2){ insert(head1,y,x); insert(head2,x,y); } } } void spfa1(){ int u,v; memset (buy,0x3f, sizeof (buy)); memset (inq,0, sizeof (inq)); buy[1] = price[1]; q.push(1) ; inq[1] = true ; while (!q.empty() ){ u = q.front() ; q.pop(); inq[u] = false ; for ( int p=head1[u]; p!=-1; p=edge[p].next ){ v = edge[p].to ; if (min(buy[u],price[v])<buy[v]){ buy[v] = min(buy[u],price[v]); if (!inq[v]){ q.push(v); inq[v] = true ; } } } } } void spfa2(){ int u,v; memset (sell,0, sizeof (sell)); memset (inq,0, sizeof (inq)); sell[n] = price[n]; q.push(n); inq[n] = true ; while (!q.empty() ){ u = q.front(); q.pop(); inq[u] = false ; for ( int p=head2[u]; p!=-1; p=edge[p].next ){ v = edge[p].to; if (max(sell[u],price[v])>sell[v]){ sell[v] = max(sell[u],price[v]); if (!inq[v]){ q.push(v) ; inq[v] = true ; } } } } } int main(){ init(); spfa1(); spfa2(); int ans=0; for ( int i=1;i<=n;i++) ans = max(ans,sell[i]-buy[i]); //freopen("trade.out","w",stdout); printf ( "%d\n" ,ans); return 0; } |
以上是关于探秘SPFA——强大的单源最短路径算法的主要内容,如果未能解决你的问题,请参考以下文章