spfa的时间复杂度为O(me),其中e为路径数量,m为所有顶点进队的平均次数。有两种优化策略,分别是SLF和LLL。如果两者加起来,最大可以减少50%的时间。
它们的最终目的是一致的,都是想让最靠进答案的点先去更新它附近的点,可能更新过程中就把目前队列中的点给更新了。如果先让那些迟早要被更新的点先去更新其他点,所有这些点迟早会被再更新一遍。因此,m的值就会变大。想要控制spfa的时间复杂度,就要从m入手。
SLF:Small Label First,小权值点优先策略。它的目的是让目前权值较优的新插入队列的点,先去更新其它点。
具体操作是这样的:设要加入的节点是y,队首元素为front,若dist(y)<dist(front),则将y插入队首(使y优先操作),否则插入队尾。
LLL:Large Label Last, 大权值点延后策略。它想让目前权值比平均数要差的推迟跟新,因为这种更新很可能是无意义的。还不如让目前可能是最优解的一部分的点先去更新其它的点。
具体操作如下:设队首元素为front,每次弹出时进行判断,队列中所有dist值的平均值为a,若dist(front)>a则将front插入到队尾(使front延后操作)。然后再判断新的队列头 的元素。直到找到某一front使得dist(front)<=a,才让front去更新其它点。
(HINT:其实让x去更新其它点的操作叫做“松弛操作”)
代码:
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=110;
const int maxm=1010;
struct edge
{
int x,y,c,next;
}a[maxm*2];int len=0,last[maxn];
void ins(int x,int y,double c)
{
len++;
a[len].x=x;a[len].y=y;a[len].c=c;
a[len].next=last[x];last[x]=len;
}
int st,ed;
deque<int> q;bool v[maxn];
int d[maxn];
void spfa()
{
double sum=0;int cnt=1;//LLL操作
memset(v,false,sizeof(v));v[st]=true;
memset(d,64,sizeof(d));d[st]=0;
q.push_front(st);
while(q.empty()==false)
{
int x=q.front();q.pop_front();
if(d[x]*cnt>sum)//LLL操作
{
q.push_back(x);
continue;
}
v[x]=false;
sum-=d[x];cnt--;
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(d[y]>d[x]+a[k].c)
{
d[y]=d[x]+a[k].c;
if(v[y]==false)
{
v[y]=true;
sum+=d[y];cnt++;//LLL操作
if(q.empty()==false&&d[y]<d[q.front()]) q.push_front(y);//SLF操作
else q.push_back(y);
}
}
}
}
printf("%d\n",d[ed]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
ins(x,y,c);
ins(y,x,c);
}
scanf("%d%d",&st,&ed);
spfa();
return 0;
}