求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm。
SPFA算法是西南交通大学段凡丁于1994年发表的.
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。
我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。
我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。
这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。
期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
实现方法:建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。
重复执行直到队列为空
判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
模版:
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4
using
namespace std;
5 typedef
struct node
6 {
7
long adj,data;
/*
adj为邻接点,data为边的权值
*/
8
struct node *next;
9 }node;
10 node *g[
1000];
11
long dis[
1000],flag[
1000],q[
1000],n,e;
/*
dis记录最短路径值,flag做出入队标记,q为队列
*/
12
void spfa(
long st)
13 {
14
long i,k,front,rear;
15 node *p;
16
for(i=
0;i<n;i++)
17 dis[i]=
0x7fffffff;
/*
初始化路径为长整型最大数
*/
18 memset(flag,
0,
sizeof(flag));
/*
入队标记初始化为0
*/
19 front=
0;
20 rear=
1;
21
/*
将起始点入队
*/
22 dis[st]=
0;
/*
到自己距离为0
*/
23 q[rear]=st;
/*
入队
*/
24 flag[st]=
1;
/*
置入队标记
*/
25
while(front!=rear)
/*
当队列不为空时
*/
26 {
27 front=(front+
1)%
1000;
/*
出队,注意使用循环队列
*/
28 k=q[front];
/*
记下出队点号
*/
29 flag[k]=
0;
/*
置出队标记
*/
30 p=g[k];
/*
找到以出队点为头的邻接链表,考察由出队点中转至各邻接点路径是否更短
*/
31
while(p)
32 {
33
if(dis[k]+p->data<dis[p->adj])
34 {
35 dis[p->adj]=dis[k]+p->data;
/*
路径更短,更新路径值
*/
36
if(!flag[p->adj])
/*
若其邻接点未入队
*/
37 {
38 rear=(rear+
1)%
1000;
/*
入队
*/
39 q[rear]=p->adj;
40 flag[p->adj]=
1;
/*
置入队标记
*/
41 }
42 }
43 p=p->next;
/*
记得改变循环变量,考察下一个邻接点
*/
44 }
45 }
46 }
47
int main()
48 {
49
long i,x,y,z;
50 node *p;
51 scanf(
"
%ld%ld
",&n,&e);
/*
读入点数,边数
*/
52 memset(g,
0,
sizeof(g));
/*
初始化图
*/
53
for(i=
1;i<=e;i++)
/*
读入边的信息,左点、右点、权值
*/
54 {
55 scanf(
"
%ld%ld%ld
",&x,&y,&z);
56 p=(node*)malloc(
sizeof(node));
/*
创建链表
*/
57 p->data=z;
58 p->adj=y;
59 p->next=g[x];
/*
尾插法
*/
60 g[x]=p;
61 }
62 spfa(
0);
/*
计算由起始点到各点的最短路径
*/
63
for(i=
0;i<n;i++)
64 printf(
"
%ld
",dis[i]);
65 printf(
"
\n
");
66
return
0;
67 }
POJ 3259
View Code
1 #include <iostream>
2 #include <cstring>
3 #include <cstdlib>
4 #include <cstdio>
5 #include <queue>
6
using
namespace std;
7
8
const
int N=
501;
9
const
int NN=
100001;
10
const
int inf=
0x7fffffff;
11 queue<
int> qu;
12
int n,nu;
13
14 typedef
struct node
15 {
16
int adj,val;
17
struct node *next;
18 }node;
19 node node[NN],*p[N];
20
21
int SPFA()
22 {
23
int x,i,a,b;
24
int vis[N],dis[N],num[N];
25
struct node *head[N];
26
for(i=
1;i<=n;i++)
27 {
28 vis[i]=
0;
29 num[i]=
0;
30 dis[i]=inf;
31 head[i]=p[i];
32 }
33 dis[
1]=
0;
34 vis[
1]=
1;
35 num[
1]++;
36 qu.push(
1);
37
while(!qu.empty())
38 {
39 x=qu.front();
40 qu.pop();
41 vis[x]=
0;
42 head[x]=p[x];
43
while(head[x])
44 {
45 a=head[x]->adj;
46 b=head[x]->val;
47
if(dis[a]>dis[x]+b)
48 {
49 dis[a]=dis[x]+b;
50
if(!vis[a])
51 {
52 qu.push(a);
53 vis[a]=
1;
54 num[a]++;
55
if(num[a]>=n)
//
如果入队的次数超过总数,说明存在回路
56
return
1;
57 }
58 }
59 head[x]=head[x]->next;
60 }
61 }
62
return
0;
63 }
64
65
int main()
66 {
67
int t,i,m,w,a,b,c;
68 scanf(
"
%d
",&t);
69
while(t--)
70 {
71
while(!qu.empty())
72 qu.pop();
73 memset(node,
0,
sizeof(node));
74 memset(p,
0,
sizeof(p));
75 nu=
0;
76 scanf(
"
%d%d%d
",&n,&m,&w);
77
for(i=
0;i<m;i++)
78 {
79 scanf(
"
%d%d%d
",&a,&b,&c);
80 node[nu].adj=b;
81 node[nu].val=c;
82 node[nu].next=p[a];
83 p[a]=&node[nu];
84 nu++;
85 node[nu].adj=a;
86 node[nu].val=c;
87 node[nu].next=p[b];
88 p[b]=&node[nu];
89 nu++;
90 }
91
for(i=
0;i<w;i++)
92 {
93 scanf(
"
%d%d%d
",&a,&b,&c);
94 node[nu].adj=b;
95 node[nu].val=-c;
96 node[nu].next=p[a];
97 p[a]=&node[nu];
98 nu++;
99 }
100
if(SPFA())
101 puts(
"
YES
");
102
else
103 puts(
"
NO
");
104 }
105
return
0;
106 }