Overview and graph theory SPFA
2019-12-28
Powered by Gauss
1. Graph Theory - Shortest
Graph theory is an integral part of the learning process Informatics. Application of graph theory is very broad, in real life, everyone everywhere can be met, such as electronic map, ticket inquiries.
Examination of the scope of the algorithm contest now is immense, but the main test sites is graph theory, DP, number theory, strings and so on. Graph theory is a big test sites, such as:
topic | source |
Optimal Trade | NOIP2009 improve the Group III title |
Information transfer | NOIP2015 improve the Group II title |
Transport plan | NOIP2015 improve the group sixth title |
Finding your way | NOIP2014 improve the group fifth title |
Of course, the algorithm on graph theory is much more than that, just to name a few famous above topics.
[History of graph theory]
Floyd | Dijsktra | Bellman-Ford | SPFA | |
Space complexity | O (N 2 ) | MAN) |
MAN) | MAN) |
time complexity | O(N3) | O((M+N)logN) | O(NM) | O(NM) |
适用情况 | 稠密图 | 稠密图 | 稀疏图 | 稀疏图 |
负权 | Y | N | Y | Y |
有负权边 | Y | N | Y | Y |
判断负权回路 | N | N | Y | Y |
名称 | 时间 |
Floyd-Warshall | 899MS |
Dijsktra | 256MS |
Bellman-ford | 159MS |
SPFA | 43MS |
void spfa(int s,int n) { int r=0,l=0; memset(dl,0,sizeof(dl)); memset(b,1,sizeof(b)); memset(dis,0x7f7f7f7f,sizeof(dis)); dis[s]=0; dl[r++]=s; while(l<r) { int x=dl[l]; b[x]=1; for(int i=1;i<=n;i++) { if(a[x][i]!=0x7f7f7f7f) { if(dis[i]>dis[x]+a[x][i]) { dis[i]=dis[x]+a[x][i]; if(b[i]) { dl[r++]=i; b[i]=0; } } } } l++; } }
void bellman() { int s=1; int d[NUM]; for(int i=1;i<=n;i++) d[i]=INF; d[s]=0; for(int k=1;k<=n;k++) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(d[j]>d[i]+graph[i][j]) d[j]=d[i]+graph[i][j]; } } } printf("%d",d[n]); }
由此可以看出,bellman和SPFA的代码最大的区别就是“松弛”方式。
松弛的步骤如下所示:
from | to | to | to | to |
1 | 2 | 3 | 4 | 5 |
INF | 5 | 2 | 5 | 40 |
∵ 2+3+1<40
∴
from | to | to | to | to |
1 | 2 | 3 | 4 | 5 |
INF | 5 | 2 | 5 | 6 |
仔细观察两张表格的区别,得出松弛操作的表达式:
if(d[j]>d[i]+graph[i][j]) d[j]=d[i]+graph[i][j];
其中d[ j ]表示从1号点到 j 号点距离。
【SPFA】的计算过程
SPFA的思想很像BFS:
使用队列来实现:
1.起点s入队,计算s所有邻居到s得距离。把s出队,状态有更新的邻居入队,没更新的出队;
2.现在的队头就是s的一个邻居p,弹出p,重复步骤1、2;
PS:这时某个点可能因为多次修改而对此入队,这时将这个点u入队就行了。
下面给出SPFA的HDU2544的完整代码:
#include<bits/stdc++.h> using namespace std; const int INF=1e6; const int NUM=105; struct edge { int from,to,w; edge(int a,int b,int c) { from=a; to=b; w=c; } }; vector<edge>e[NUM]; int n,m; int pre[NUM]; int spfa(int s) { int dis[NUM]; bool inq[NUM]; int Neg[NUM]; memset(Neg,0,sizeof(Neg)); Neg[s]=1; for(int i=1;i<=n;i++) { dis[i]=INF; inq[i]=false; } dis[s]=0; queue<int>Q; Q.push(s); inq[s]=true; while(!Q.empty()) { int u=Q.front(); Q.pop(); inq[u]=false; for(int i=0;i<e[u].size();i++) { int v=e[u][i].to,w=e[u][i].w; if(dis[u]+w<dis[v]) { dis[v]=dis[u]+w;//松弛 pre[v]=u;//记录路径 if(!inq[v])//更新队列 { inq[v]=true;//更新队列的元素 Q.push(v);//加入队列 Neg[v]++;//判断入队次数 if(Neg[v]>=n) return 1;//判断负环 } } } } printf("%d\n",dis[n]); return 0; } int main() { while(~scanf("%d%d",&n,&m)) { if(n==0 && m==0) return 0; for(int i=1;i<=n;i++) e[i].clear(); while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); e[a].push_back(edge(a,b,c)); e[b].push_back(edge(b,a,c)); } spfa(1); } return 0; }
我们来详细解读一下上面的代码;
首先,因为题目的要求是无向图,所以要双向存储:
e[a].push_back(edge(a,b,c));
e[b].push_back(edge(b,a,c));
然后,我们从一号点开始进行SPFA计算
spfa(1);
紧接着,
int dis[NUM]; bool inq[NUM]; int Neg[NUM]; memset(Neg,0,sizeof(Neg)); Neg[s]=1;
其中dis用来存储距离,inq用来存储这个点是否在队列里,Neg用来存储负环。
Neg[s]=1; for(int i=1;i<=n;i++) { dis[i]=INF; inq[i]=false; }
这一段代码是初始化,将两个数组进行初始化,非常重要。
dis[s]=0; queue<int>Q; Q.push(s); inq[s]=true;
这里就是SPFA的精华部分,我们使用队列存储。
while(!Q.empty())
只要队列不为空,就说明有点需要松弛
int u=Q.front(); Q.pop(); inq[u]=false; for(int i=0;i<e[u].size();i++) { int v=e[u][i].to,w=e[u][i].w; if(dis[u]+w<dis[v]) { dis[v]=dis[u]+w;//松弛 pre[v]=u;//记录路径 if(!inq[v])//更新队列 { inq[v]=true;//更新队列的元素 Q.push(v);//加入队列 Neg[v]++;//判断入队次数 if(Neg[v]>=n) return 1;//判断负环 } } }
这里就是图论算法求最短路的精华,松弛;
今天就讲到这里,再见!!!