概要とグラフ理論SPFA
2019年12月28日
ガウスによって供給
1.グラフ理論 - 最短
グラフ理論は、学習プロセス情報の不可欠な部分です。グラフ理論の適用は、実際の生活の中で、どこでも、誰もがこのような電子地図、チケットのお問い合わせなど、満たすことができる、非常に広いです。
アルゴリズムコンテストの適用範囲の検討は今広大ですが、メインのテストサイトは、グラフ理論、DP、数論、文字列などです。グラフ理論は、以下のような大規模テストサイト、次のとおりです。
タイトル | ソース |
最適なトレード | グループIIIのタイトルを改善NOIP2009 |
情報伝達 | グループIIのタイトルを改善NOIP2015 |
交通計画 | グループ第タイトルを改善NOIP2015 |
あなたの方法を見つけます | グループ第五のタイトルを改善NOIP2014 |
もちろん、グラフ理論上のアルゴリズムは、わずか数有名上記のトピックに名前を付けるために、はるかにそれよりもです。
[グラフ理論の歴史]
フロイド | Dijsktra | ベルマン・フォード | SPFA | |
宇宙複雑 | O(N 2) | O(M) |
O(M) | O(M) |
時間複雑 | 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;//判断负环 } } }
这里就是图论算法求最短路的精华,松弛;
今天就讲到这里,再见!!!