图论概述和SPFA
Posted warframe-gauss
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论概述和SPFA相关的知识,希望对你有一定的参考价值。
图论概述和SPFA
2019-12-28
Powered by Gauss
1.图论——最短路
图论是信息学学习过程中不可或缺的一个部分。图论的应用是非常广泛的,在现实生活中大家处处都能遇到,例如电子地图,机票查询等。
现在的算法竞赛考试的范围是无边无际,但主要的考点也就是图论,DP,数论,字符串等等。图论是一大考点,例如:
题目 | 来源 |
最优贸易 | NOIP2009提高组第三题 |
信息传递 | NOIP2015提高组第二题 |
运输计划 | NOIP2015提高组第六题 |
寻找道路 | NOIP2014提高组第五题 |
当然,关于图论的算法远远不止这些,上面只列举了一些著名的题目。
【图论的历史】
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 12 | 2 | 无法直接到达 | 无法直接到达 |
2 | 12 | 0 | 1 | 9 | 2 |
3 | 2 | 1 | 0 | 10 | 13 |
4 | 无法直接到达 | 9 | 10 | 0 | 5 |
5 | 无法直接到达 | 2 | 13 | 5 | 0 |
Floyd | Dijsktra | Bellman-Ford | SPFA | |
空间复杂度 | O(N2) |
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 ",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;//判断负环 } } }
这里就是图论算法求最短路的精华,松弛;
今天就讲到这里,再见!!!
以上是关于图论概述和SPFA的主要内容,如果未能解决你的问题,请参考以下文章