今天突然发现spfa有点忘了,所以写了这篇博客,回顾回顾最短路算法,方便以后忘了可以复习复习QAQ
最短路三种算法
算法一:迪杰斯特拉算法
注意:注意:迪杰斯特拉要求图中不能有负权边
推荐博客:
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html
实现方法有两种,第一种就是数组标记,第二种是优先队列实现。推荐第二种,时间复杂度占优,而且可以剪枝。
第一种:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
int e[1000][1000],d[1000],vis[1000];
int n,m;
void dijiesitela(int s)
{
for(int i=0; i<1000; i++)
{
d[i]=inf;
vis[i]=0;
}
d[s]=0;
while(1)
{
int v=-1;
for(int i=1; i<=n; i++)
{
if(!vis[i]&&(v==-1||d[i]<d[v]))
{
v=i;
}
}
if(v==-1)
break;
vis[v]=1;
for(int i=1; i<=n; i++)
{
d[i]=min(d[i],d[v]+e[v][i]);
}
}
}
int main()
{
//int n,m;//顶点数,边数
while(scanf("%d%d",&n,&m)==2)
{
memset(e,inf,sizeof(e));
for(int i=1; i<=m; i++)
{
int u,v,cost;
scanf("%d%d%d",&u,&v,&cost);
if(e[u][v]>cost)
e[u][v]=cost;
//e[u][v]=e[v][u]=cost;//无向图
}
dijiesitela(1);
for(int i=1; i<=n; i++)
{
if(i==1)
printf("%d",d[i]);
else
printf(" %d",d[i]);
}
printf("\n");
}
return 0;
}
第二种:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
typedef pair<int,int>p; //first是最短距离,second是点编号
struct node
{
int to,cost;
} e;
vector<node>G[1000];
int d[1000];
void dijiesitela(int s)
{
priority_queue<p,vector<p>,greater<p> >que;
que.push(p(0,s));
fill(d,d+1000,inf);
d[s]=0;
while(!que.empty())
{
p x=que.top();
que.pop();
int u=x.second;
if(d[u]<x.first)
continue;
for(int i=0; i<G[u].size(); i++)
{
node y=G[u][i];
if(d[y.to]>d[u]+y.cost)
{
d[y.to]=d[u]+y.cost;
que.push(p(d[y.to],y.to));
}
}
}
}
int main()
{
int n,m;//顶点数,边数
while(scanf("%d%d",&n,&m)==2)
{
node now;
for(int i=1; i<=m; i++)
{
int u,v,cost;
scanf("%d%d%d",&u,&v,&cost);
now.to=v;
now.cost=cost;
G[u].push_back(now);
now.to=u;
//now.cost=cost;
//G[v].push_back(now);//无向图
}
dijiesitela(1);//求点1到所有点的最短距离
for(int i=1; i<=n; i++)
{
if(i==1)
printf("%d",d[i]);
else
printf(" %d",d[i]);
}
printf("\n");
}
return 0;
}
算法二:floyed,这里不多讲,floyed算是最短路里面很常用的,但是复杂度是O(n*n *n),
typedef struct
{
char vertex[VertexNum]; //顶点表
int edges[VertexNum][VertexNum]; //邻接矩阵,可看做边表
int n,e; //图中当前的顶点数和边数
}MGraph;
void Floyd(MGraph g)
{
int A[MAXV][MAXV];
int path[MAXV][MAXV];
int i,j,k,n=g.n;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
A[i][j]=g.edges[i][j];
path[i][j]=-1;
}
for(k=0;k<n;k++)
{
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if(A[i][j]>(A[i][k]+A[k][j]))
{
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
}
}
算法三:SPFA
注意:SPFA可以实现存在负权值的图的最短路求解,也可以判断图中是否存在负环
推荐博客:
(1)http://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html
(2)https://www.61mon.com/index.php/archives/196/
SPFA算法中需要用到的主要变量
int n; //表示n个点,从1到n标号
int s,t; //s为源点,t为终点
int d[N]; //d[i]表示源点s到点i的最短路
int p[N]; //记录路径(或者说记录前驱)
queue q; //一个队列,用STL实现,当然可有手打队列,无所谓
bool vis[N]; //vis[i]=1表示点i在队列中 vis[i]=0表示不在队列中
几乎所有的最短路算法其步骤都可以分为两步
1.初始化
2.松弛操作
初始化: d数组全部赋值为INF(无穷大);p数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱
然后d[s]=0; 表示源点不用求最短路径,或者说最短路就是0。将源点入队;
(另外记住在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记)
队列+松弛操作
读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队
以此循环,直到队空为止就完成了单源最短路的求解
SPFA可以处理负权边
定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。
证明:
每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)
期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
**判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)**
SPFA的两种写法,bfs和dfs,bfs判别负环不稳定,相当于限深度搜索,但是设置得好的话还是没问题的,dfs的话判断负环很快
一:bfs写法:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <stack>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
const int maxn=1000;
int n,m;//顶点数,边数
int e[maxn][maxn];
int vis[maxn],d[maxn],c[maxn],path[maxn];
bool SPFA(int s)
{
for(int i=0; i<maxn; i++)
path[i]=s;
queue<int>p;
p.push(s);
vis[s]=1;
c[s]++;
d[s]=0;
while(!p.empty())
{
int u=p.front();
p.pop();
vis[u]=0;
for(int v=1; v<=n; v++)
{
if(e[u][v]!=inf)//如果u点和i点直接相邻,就是u,i点之间有边
{
if(d[v]>d[u]+e[u][v])
{
d[v]=d[u]+e[u][v];
path[v]=u;
if(!vis[v])
{
p.push(v);
c[v]++;
if(c[v]>=n)
return false;
vis[v]=1;
}
}
}
}
}
return true;
}
void print(int s)
{
for(int i=1;i<=n;i++)
{
if(i!=s)
{
int u=i;
stack<int>ok;
cout<<s<<"点到"<<i<<"点的最短路="<<d[i]<<endl;
while(u!=s)
{
ok.push(u);
u=path[u];
}
cout<<s;
while(!ok.empty())
{
cout<<"--"<<ok.top();
ok.pop();
}
cout<<endl;
}
else
cout<<d[i]<<endl;
}
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
memset(e,inf,sizeof(e));
memset(vis,0,sizeof(vis));
memset(d,inf,sizeof(d));
memset(c,0,sizeof(c));
for(int i=1; i<=m; i++)
{
int u,v,cost;
scanf("%d%d%d",&u,&v,&cost);
if(e[u][v]>cost)
e[u][v]=cost;
//e[u][v]=e[v][u]=cost;//无向图
}
if(!SPFA(1))//求点1到所有点的最短路
printf("有负环");
else
print(1);
}
return 0;
}
二:dfs写法
int spfa_dfs(int u)
{
vis[u]=1;
for(int k=f[u]; k!=0; k=e[k].next)
{
int v=e[k].v,w=e[k].w;
if( d[u]+w < d[v] )
{
d[v]=d[u]+w;
if(!vis[v])
{
if(spfa_dfs(v))
return 1;
}
else
return 1;
}
}
vis[u]=0;
return 0;
}
最短路练习题目:https://vjudge.net/contest/66569#problem
题目对应代码:
方法一:优先队列(迪杰斯特拉)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
typedef pair<int,int>p; //first是最短距离,second是点编号
struct node
{
int to,cost;
} e;
vector<node>G[2000+10];
int d[2000+10];
void dijiesitela(int s)
{
priority_queue<p,vector<p>,greater<p> >que;
que.push(p(0,s));
fill(d,d+2010,inf);
d[s]=0;
while(!que.empty())
{
p x=que.top();
que.pop();
int u=x.second;
if(d[u]<x.first)
continue;
for(int i=0; i<G[u].size(); i++)
{
node y=G[u][i];
if(d[y.to]>d[u]+y.cost)
{
d[y.to]=d[u]+y.cost;
que.push(p(d[y.to],y.to));
}
}
}
}
int main()
{
int n,m;//顶点数,边数
while(scanf("%d%d",&m,&n)==2)
{
node now;
for(int i=1; i<=m; i++)
{
int u,v,cost;
scanf("%d%d%d",&u,&v,&cost);
now.to=v;
now.cost=cost;
G[u].push_back(now);
now.to=u;
now.cost=cost;
G[v].push_back(now);
}
dijiesitela(1);
// for(int i=1; i<=n; i++)
// {
// if(i==1)
// printf("%d",d[i]);
// else
// printf(" %d",d[i]);
// }
// printf("\n");
printf("%d\n",d[n]);
}
return 0;
}
方法二:数组(迪杰斯特拉)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
int e[2000+10][2000+10],d[2000+10],vis[2000+10];
int n,m;
void dijiesitela(int s)
{
for(int i=0; i<2000; i++)
{
d[i]=inf;
vis[i]=0;
}
d[s]=0;
while(1)
{
int v=-1;
for(int i=1; i<=n; i++)
{
if(!vis[i]&&(v==-1||d[i]<d[v]))
{
v=i;
}
}
if(v==-1)
break;
vis[v]=1;
for(int i=1;i<=n;i++)
{
d[i]=min(d[i],d[v]+e[v][i]);
}
}
}
int main()
{
//int n,m;//顶点数,边数
while(scanf("%d%d",&m,&n)==2)
{
memset(e,inf,sizeof(e));
// for(int i=1;i<=n;i++)
// for(int j=1;j<=n;j++)
// {
// if(i==j)
// e[i][j]=0;
// else
// e[i][j]=inf;
// }
for(int i=1; i<=m; i++)
{
int u,v,cost;
scanf("%d%d%d",&u,&v,&cost);
if(e[u][v]>cost)
e[u][v]=e[v][u]=cost;
}
dijiesitela(1);
// for(int i=1; i<=n; i++)
// {
// if(i==1)
// printf("%d",d[i]);
// else
// printf(" %d",d[i]);
// }
printf("%d\n",d[n]);
}
return 0;
}
方法三:SPFA
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <stack>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
const int maxn=2000+10;
int n,m;//顶点数,边数
int e[maxn][maxn];
int vis[maxn],d[maxn],c[maxn],path[maxn];
bool SPFA(int s)
{
for(int i=0; i<maxn; i++)
path[i]=s;
queue<int>p;
p.push(s);
vis[s]=1;
c[s]++;
d[s]=0;
while(!p.empty())
{
int u=p.front();
p.pop();
vis[u]=0;
for(int v=1; v<=n; v++)
{
if(e[u][v]!=inf)//如果u点和v点直接相邻,就是u,v点之间有边
{
if(d[v]>d[u]+e[u][v])
{
d[v]=d[u]+e[u][v];
path[v]=u;
if(!vis[v])
{
p.push(v);
c[v]++;
if(c[v]>=n)
return false;
vis[v]=1;
}
}
}
}
}
return true;
}
void print(int s)
{
for(int i=1;i<=n;i++)
{
if(i!=s)
{
int u=i;
stack<int>ok;
cout<<s<<"点到"<<i<<"点的最短路="<<d[i]<<endl;
while(u!=s)
{
ok.push(u);
u=path[u];
}
cout<<s;
while(!ok.empty())
{
cout<<"--"<<ok.top();
ok.pop();
}
cout<<endl;
}
else
cout<<d[i]<<endl;
}
}
int main()
{
while(scanf("%d%d",&m,&n)==2)
{
memset(e,inf,sizeof(e));
memset(vis,0,sizeof(vis));
memset(d,inf,sizeof(d));
memset(c,0,sizeof(c));
for(int i=1; i<=m; i++)
{
int u,v,cost;
scanf("%d%d%d",&u,&v,&cost);
if(e[u][v]>cost)
//e[u][v]=cost;
e[u][v]=e[v][u]=cost;//无向图
}
SPFA(1);
//if(!SPFA(1))//求点1到所有点的最短路
//printf("有负环");
//else
//print(1);
printf("%d\n",d[n]);
}
return 0;
}
推荐一个题目:https://vjudge.net/problem/HDU-6005
题解:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include<malloc.h>
#include <map>
#include <vector>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
int cnt,ans;
typedef pair<int,int>p;
map<p,int>m;
vector<p>G[10000+10];
int vis[10000+10],d[10000+10];
struct node
{
int u,v,cost;
}e[10000+10];
void init()
{
cnt=0;
ans=inf;
for(int i=0;i<10000+10;i++)
{
G[i].clear();
}
//memset(vis,0,sizeof(vis));
m.clear();
}
void dij(int s,int t,int cost)
{
priority_queue<p,vector<p>,greater<p> >q;
for(int i=0;i<10000+10;i++)
{
d[i]=inf;
vis[i]=0;
}
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty())
{
p x=q.top();
q.pop();
int u=x.second,dis=x.first;
if(dis+cost>=ans)
break;
if(vis[u])
continue;
vis[u]=1;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].first,id=G[u][i].second;
int val=e[id].cost;
if(d[v]>d[u]+val)
{
d[v]=d[u]+val;
q.push(make_pair(d[v],v));
}
}
}
ans=min(ans,d[t]+cost);
}
int main()
{
int t;
scanf("%d",&t);
int kases=0;
while(t--)
{
int n;
scanf("%d",&n);
init();
for(int i=1;i<=n;i++)
{
int x1,x2,y1,y2,val;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&val);
p p1=make_pair(x1,y1);
p p2=make_pair(x2,y2);
if(!m[p1]) m[p1]=++cnt;
if(!m[p2]) m[p2]=++cnt;
int u=m[p1],v=m[p2];
G[u].push_back(make_pair(v,i));
G[v].push_back(make_pair(u,i));
e[i].u=u;e[i].v=v;e[i].cost=val;
}
for(int i=1;i<=n;i++)
{
int u=e[i].u,v=e[i].v,cost=e[i].cost;
e[i].cost=inf;
dij(u,v,cost);
e[i].cost=cost;
}
if(ans==inf)
ans=0;
printf("Case #%d: %d\n",++kases,ans);
}
return 0;
}