【为什么要优化】
关于SPFA,他死了(懂的都懂)
进入正题。。。
一般来说,我们有三种优化方法。
SLF优化:
SLF优化,即 Small Label First 策略,使用 双端队列 进行优化。
一般可以优化15%~20%,在竞赛中比较常用。
设从 u 扩展出了 v ,队列中队首元素为 k ,若 dis[ v ] < dis[ k ] ,则将 v 插入队首,否则插入队尾。
注:队列为空时直接插入队尾。
1 deque<int> q;
2
3 inline void spfa(int x)
4 {
5 memset(d,0x3f,sizeof(d));
6 memset(v,0,sizeof(v));
7 d[x]=0;v[x]=1;
8 q.push_back(x);
9 while(q.size())
10 {
11 int index=q.front();q.pop_front();
12 v[index]=0;
13 for(int i=head[index];i;i=g[i].next){
14 int y=g[i].ver,z=g[i].edge;
15 if(d[y]>d[index]+z){
16 d[y]=d[index]+z;
17 if(!v[y]){
18 if(!q.empty()&&d[y]>=d[q.front()]) q.push_back(y);
19 else q.push_front(y);
20 v[y]=1;
21 }
22 }
23 }
24 }
25 }
LLL优化:
LLL优化,即 Large Label Last 策略,使用 双端队列 进行优化。
一般用SLF+LLL可以优化50%左右,但是在竞赛中并不常用LLL优化。(所以我就懒得写了,这是从这个大佬那里嫖来的)
设队首元素为 k ,每次松弛时进行判断,队列中所有 dis 值的平均值为 x 。
若 dist[ k ] > x ,则将 k 插入到队尾,查找下一元素,直到找到某一个 k 使得 dis[ k ] <= x ,则将 k 出队进行松弛操作。
1 void spfa(){ 2 int u,v,num=0; 3 long long x=0; 4 list<int> q; 5 for(int i=1;i<=n;i++){path[i]=MAX;vis[i]=false;} 6 path[s]=0; 7 vis[s]=true; 8 q.push_back(s); 9 num++; 10 while(!q.empty()){ 11 u=q.front(); 12 q.pop_front(); 13 num--;x-=path[u]; 14 while(num&&path[u]>x/num){ 15 q.push_back(u); 16 u=q.front(); 17 q.pop_front(); 18 } 19 vis[u]=false; 20 for(int i=head[u];i;i=a[i].next){ 21 v=a[i].to; 22 if(relax(u,v,a[i].w)&&!vis[v]){ 23 vis[v]=true; 24 if(!q.empty()&&path[v]<path[q.front()])q.push_front(v); 25 else q.push_back(v); 26 num++;x+=path[v]; 27 } 28 } 29 } 30 for(int i=1;i<=n;i++)printf("%d ",path[i]); 31 printf(" "); 32 }
DFS优化:
这种优化顾名思义,就是用dfs的思想代替bfs的思想来优化Bellman-Ford。
常常用于判断正/负环,时间复杂度可以达到O(m)(m是边)。思路是,我们每一次dfs的时候如果走回之前dfs过的点,那就是有环,除了这个dfs的标记,我们还可以打另一个vis数组记录更新过权值的节点,以后就不必重复更新,大大降低复杂度。
不过如果无环的话,那还是上面那两种优化稍微适用一点。代码比较短,但是不好扩展。
1 inline bool spfa(int x) 2 { 3 dfs[x]=1; 4 for(int i=head[x];i;i=g[i].next) 5 { 6 int y=g[i].ver,z=g[i].edge; 7 if(!v[y]||d[y]<d[x]+z){ 8 if(dfs[y]) return 0; 9 v[y]=1; 10 d[y]=d[x]+z; 11 if(!spfa(y)) return 0; 12 } 13 } 14 dfs[x]=0; 15 return 1; 16 }