一、题目描述
SPFA算法
题目描述
有向图的单源点最短路径问题。源点编号为1,终点编号为n。
输入
第1行:2个空格分开的整数n(2<=n<=5000)和m(10<=m<=500000),分别表示图的顶点数和边数。
第2..m+1行:每行3个空格分开的整数i,j, w。i表示一条边的起点,j表示终点, w表示权值。
输出
第1行:1个整数,表示最小距离
样例输入
样例1:
4 7
1 2 68
1 3 19
1 4 66
2 3 23
3 4 65
3 2 57
4 1 68
样例2:
3 3
1 2 -7
2 3 4
3 1 2
样例输出
样例1:
66
样例2:
No Solution
二、算法分析
(在看算法分析时,请保证自己对题目已经十分熟悉)
(该算法比较复杂,需要读者具有一定的基础,了解队列的一些用法)
这种题目是典型的最短路径问题,可以用许多算法来解决,但是看了数据范围之后......只有SPFA能够解决。
知道了用SPFA算法, 那我们就来看看SPFA的思路
SPFA是Bellman-Ford的升级版,因为Bellman-Ford里有许多不必要的计算,所以SPFA就利用队列来进行了时间复杂度的优化。
SPFA算法全过程:
1、将起点加入队列,while循环开始。
2、从队列中取出一个元素,for循环开始。
3、用它的最短路径来更新与它相邻的点最短路径,如果有更新成功的,就将其入队
4、for循环结束
5、当队列为空时,while循环结束。
时间复杂度为O(kE),k为常数,大约为2,E为边数,一个接近完美的算法。
算法的总体结构有点像BFS(广搜),但是SPFA中不会判重,就是一个点有可能会多次入队。
看起来SPFA的思路很简单,但是实践起来就不同了。
三、题目分析
(在看题目分析时,请大家做好心理准备)
看完题目后,相信大家对程序的框架已经搭好了,大概是这样的:
初始化+SPFA+负权回路判断
但是仔细一看,这些都是难点。
1、初始化:
(1)、将dis数组清为最大值,dis[1]=0;
(2)、将输入的边进行排序(写cmp函数)
(3)、计算每一个点的出边数和对应的起始位置,为SPFA做准备
2、SPFA
(1)、取出一个点
(2)、判断是否有出边
(3)、进行连接点最短路径的更新
3、负权回路判断
这个比较简单,用一个pd数组,一个点入队一次就pd[该点编号]++,
如果超过了pd[该点编号]总点数,就输出“No Solution”。
AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
queue <int > q;
int dis[5005],out[5005][2],n,m,pd[5005];
struct node{
int cd,s,t;
}w[500005];
bool cmp(node a,node b){
if(a.s<b.s) return 1;
else if(a.s>b.s) return 0;
else if(a.t<b.t) return 1;
else return 0;
}
int main()
{
int i,u;
scanf("%d%d",&n,&m);
memset(dis,1,sizeof(dis));
for(i=1;i<=m;i++){
scanf("%d%d%d",&w[i].s,&w[i].t,&w[i].cd);
out[w[i].s][1]++;
}
sort(w+1,w+n+1,cmp);
q.push(1);
dis[1]=0;
out[1][0]=1;
for(i=2;i<=n;i++)
out[i][0]=out[i-1][0]+out[i-1][1];
while(!q.empty()){//SPFA
u=q.front();
q.pop();
if(out[u][1]!=0){
for(i=out[u][0];i<=out[u][0]+out[u][1]-1;i++){
if(dis[u]+w[i].cd<dis[w[i].t]){
dis[w[i].t]=dis[u]+w[i].cd;
q.push(w[i].t);
pd[w[i].t]++;//pd
if(pd[w[i].t]>n){
printf("No Solution");
return 0;
}
}
}
}
}
printf("%d",dis[n]);
}