「学习笔记」SPFA 算法的优化
Posted 朝气蓬勃 后生可畏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「学习笔记」SPFA 算法的优化相关的知识,希望对你有一定的参考价值。
与其说是 SPFA 算法的优化,倒不如说是 Bellman-Ford 算法的优化。
栈优化
将原本的 bfs 改为 dfs,在寻找负环时可能有着更高效的效率,但是最坏复杂度为指数级别。
void dfs_spfa(int u)
if (fg) return;
vis[u] = true;
for(pil it : son[u])
int v = it.first;
ll w = it.second;
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
if (vis[v] == true) //如果这个点被访问过,就说明这是负环
fg = true;//打标记
return;
else dfs_spfa(v);
vis[u] = false;
SLF 优化
将 queue
换成 deque
,判断与队首元素的 dis
的大小,小的就放队首,大的就放队尾。
void spfa(int s)
for(int i = 1; i <= n; ++ i)
dis[i] = inf;
dis[s] = 0;
q.push_back(s);
f[s] = 1;
while (!q.empty())
int u = q.front();
q.pop_front();
f[u] = 0;
for (pii it : son[u])
int v = it.first;
int w = it.second;
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
if (! f[v])
if (! q.empty() && dis[v] < dis[q.front()])
q.push_front(v);
else q.push_back(v);
f[v] = 1;
D´Esopo-Pape 优化
将 queue
换成 deque
,判断一个点是否入过队列,没入过就放到队尾,如果就放到队首。
void spfa(int s)
for(int i = 1; i <= n; ++ i)
dis[i] = inf;
dis[s] = 0;
q.push_back(s);
f[s] = 1;
vis[s] = 1; // 是否入过队
while (!q.empty())
int u = q.front();
q.pop_front();
f[u] = 0;
for (pii it : son[u])
int v = it.first;
int w = it.second;
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
if (! f[v])
if (vis[v])
q.push_front(v);
else
q.push_back(v);
vis[v] = 1;
f[v] = 1;
LLL 优化
将 queue
换成 deque
,每次将入队结点距离和队内距离平均值比较,如果更大则插入至队尾,否则插入队首。
void spfa()
ll sum = 0;
for (int i = 1; i <= n; ++ i)
dis[i] = inf;
dis[s] = 0;
q.push_back(s);
g[s] = 1;
sum += dis[s];
while (!q.empty())
int u = q.front();
q.pop_front();
vis[u] = false;
sum -= dis[s];
for (pli it : son[u])
if (dis[it.second] > dis[u] + it.first)
dis[it.second] = dis[u] + it.first;
if (! vis[it.second])
if (q.empty() || dis[it.second] > sum / ((int)q.size()))
q.push_back(it.second);
else
q.push_front(it.second);
g[it.second] = 1;
vis[it.second] = true;
SLF 带容错优化
将 queue
换成 deque
,判断与队首元素的 dis
的大小,设定一个值 \\(W\\),如果比队首元素大超过 \\(W\\) 则放队尾。
\\(W\\) 一般设为所有边权的和的开方,即 \\(\\sqrtsum\\)。
mcfx 优化
在第 \\(\\left[L, R\\right]\\) 次访问一个结点时,将其放入队首,否则放入队尾。通常取 \\(L = 2, R = \\sqrt\\left|V\\right|\\)。
SLF + swap 优化
每当队列改变时,如果队首距离大于队尾,则交换首尾。
算法描述》关于SPFA和Dijkstra算法的两三事
本来我是想把这两个算法分开写描述的,但是SPFA其实就是Dijkstra的稀疏图优化,所以其实代码差不多,所以就放在一起写了。
因为SPFA是Dijkstra的优化,所以我想来讲讲Dijkstra。
什么是Dijkstra
Dijkstra是一种求单源最短路的基础算法,时间复杂度在不加堆优化的情况下是o(n^2)的,加了堆优化就能简化到o(nlogn),而且算法稳定性很强(从这点上来说比SPFA好多了,具体怎么好下面再讲),基础思路如下:
首先,把所有点到源的距离设为最大,然后把源加入队列,接着每次对队列首做这种操作:用邻接表找到从这个点能链到的所有所有点,接着要按大小顺序找当前距离和当前队首最近的点(这个按大小顺序就是可以用堆优化的地方),做松弛操作,松弛成功,将这个点入队,否则按大小顺序找下一条边,然后重复做松弛操作,直到当前点所有能链到的边松弛过,弹出当前点,重复以上操作,直到队列里没有其他元素。结束程序,输出单源到某点距离。
加了堆优化之后,有如此6的复杂度的Dijkstra,一般来说就是稳定单源最短路的最佳程序,但是在全图有负环的时候,Dijkstra算法就显得很无力了,在这时,我们就要用SPFA这种神奇的乱搞算法了。
什么是SPFA
SPFA算法全称是Shortest Path Faster Algorithm算法,人家都叫“Faster”了,那肯定有过人之处,基础思路如下:
首先,还是把所有点到源的距离设为最大,把源加入队列,接着每次对队首做这种操作:用邻接表找到从这个点能链到的所有所有点,然后对每条边做松弛操作,只要松弛成功,我们就判断当前松弛成功的点是否在队列,如果不在,就入队,否则就只是做松弛,然后弹出当前点,对下一点再次做以上操作,直到队列为空。
当然,和Dijkstra相比,SPFA看起来相当的乱搞,但是,对于绝大多数(稀疏)图来说,加了SLF和LLL两种优化的SPFA肯定比Dijkstra快得多(SLF和LLL就是两种决策优化,前者对于最值优化,后者是均值优化)。
顺带附上一道codevs1021的SPFA题目与代码
麦克找了个新女朋友,玛丽卡对他非常恼火并伺机报复。
因为她和他们不住在同一个城市,因此她开始准备她的长途旅行。
在这个国家中每两个城市之间最多只有一条路相通,并且我们知道从一个城市到另一个城市路上所需花费的时间。
麦克在车中无意中听到有一条路正在维修,并且那儿正堵车,但没听清楚到底是哪一条路。无论哪一条路正在维修,从玛丽卡所在的城市都能到达麦克所在的城市。
玛丽卡将只从不堵车的路上通过,并且她将按最短路线行车。麦克希望知道在最糟糕的情况下玛丽卡到达他所在的城市需要多长时间,这样他就能保证他的女朋友离开该城市足够远。
编写程序,帮助麦克找出玛丽卡按最短路线通过不堵车道路到达他所在城市所需的最长时间(用分钟表示)。
第一行有两个用空格隔开的数N和M,分别表示城市的数量以及城市间道路的数量。1≤N≤1000,1≤M≤N*(N-1)/2。城市用数字1至N标识,麦克在城市1中,玛丽卡在城市N中。
接下来的M行中每行包含三个用空格隔开的数A,B和V。其中1≤A,B≤N,1≤V≤1000。这些数字表示在A和城市B中间有一条双行道,并且在V分钟内是就能通过。
输出文件的第一行中写出用分钟表示的最长时间,在这段时间中,无论哪条路在堵车,玛丽卡应该能够到达麦克处,如果少于这个时间的话,则必定存在一条路,该条路一旦堵车,玛丽卡就不能够赶到麦克处。
5 7
1 2 8
1 4 10
2 3 9
2 4 10
2 5 1
3 4 7
3 5 10
27
题解如下
这道题关键在于最优解可能会无法连通,所以我们求出最优解之后,要枚举最优解中哪条路会断,取其中最坏情况(题目所需),得答案就好,因为算次优解一定不能使数据再包括最优解的某条边,不然就和最优解一样了,所以以上方法是正确的。
贴出代码
1 #include<stdio.h> 2 struct shit{ 3 int aim; 4 int get; 5 int next; 6 int lon; 7 }e[3010000]; 8 int max(int x,int y) 9 { 10 return x>y?x:y; 11 } 12 int pre[3010000]; 13 int d[1010000],a,b,n,m,num,star,ass,quq[60100000],point,head[1010000],ans; 14 bool f[10101000]; 15 void fuck(int x,int y,int s)//建边 16 { 17 e[++point].aim=x; 18 e[point].lon=s; 19 e[point].get=y; 20 e[point].next=head[y]; 21 head[y]=point; 22 } 23 int main() 24 { 25 scanf("%d%d",&n,&m); 26 for(int i=1;i<=m;i++) 27 { 28 scanf("%d%d%d",&a,&b,&num); 29 fuck(a,b,num); 30 fuck(b,a,num); 31 } 32 quq[++star]=1; 33 ass=1; 34 f[1]=true; 35 for(int i=2;i<=n;i++) 36 d[i]=214748364; 37 while(star<=ass) 38 { 39 for(int k=head[quq[star]];k!=0;k=e[k].next) 40 if(d[quq[star]]+e[k].lon<d[e[k].aim]) 41 { 42 d[e[k].aim]=d[quq[star]]+e[k].lon; 43 pre[e[k].aim]=k;//第一次的SPFA要记录路径(点的位置) 44 if(!f[e[k].aim]) 45 { 46 quq[++ass]=e[k].aim; 47 f[e[k].aim]=true; 48 } 49 } 50 f[quq[star++]]=false; 51 } 52 ans=max(d[n],ans); 53 int q=n; 54 while(q!=0) 55 { 56 a=e[pre[q]].lon; 57 e[pre[q]].lon=214748364;//直接设置当前边数值极大(即设置为不连通) 58 if(pre[q]%2==1)//根据读入边的方法来双向删边 59 e[pre[q]+1].lon=214748364; 60 else 61 e[pre[q]-1].lon=214748364; 62 star=1; 63 for(int i=1;i<=ass;i++) 64 quq[i]=0; 65 quq[star]=1; 66 ass=1; 67 f[1]=true; 68 for(int i=2;i<=n;i++) 69 { 70 d[i]=214748364; 71 f[i]=false; 72 } 73 while(star<=ass) 74 { 75 for(int k=head[quq[star]];k!=0;k=e[k].next) 76 if(d[quq[star]]+e[k].lon<d[e[k].aim]) 77 { 78 d[e[k].aim]=d[quq[star]]+e[k].lon; 79 if(!f[e[k].aim]) 80 { 81 quq[++ass]=e[k].aim; 82 f[e[k].aim]=true; 83 } 84 } 85 f[quq[star++]]=false; 86 } 87 if(d[n]!=214748364)ans=max(d[n],ans);//如果数值就为我们设置的最大,即切去当前边的图无法连通 88 e[pre[q]].lon=a; 89 if(pre[q]%2==1)//复原切去的边 90 e[pre[q]+1].lon=a; 91 else 92 e[pre[q]-1].lon=a; 93 q=e[pre[q]].get; 94 } 95 printf("%d",ans); 96 return 0; 97 }
PS:没有加LLL和SLF的主要原因是我还不会打→_→
以上是关于「学习笔记」SPFA 算法的优化的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 2595: [Wc2008]游览计划 [DP 状压 斯坦纳树 spfa]学习笔记