单源最短路:SPFA学习笔记
SPFA 是 Shortest Path Faster Algorithm 的简称。一听到这个给力的名字就知道它不简单。SPFA出现直接就让Dijkstra算法靠边站,不光解决了负环还提高了效率,真是一举两得。
我学习SPFA是直接看baidu的百科(http://baike.baidu.com/view/682464.htm)里面讲的很系统,但是说实话不会有很多人去看那上面的代码,因为网页显示问题,根本没法看出层次关系,C的代码还好,但是pascal代码就惨不忍睹了。我这里就把我的代码贴出来(只有C):
C代码:
#include <stdio.h>
#define INF 1000000000//"无穷大" 十亿
int g[1000][1000];//图
int use[1000];//use[i]表示i是否在队列里
int q[1000];//队列
int d[1000];//距离
int n;
void SPFA(int s);
int main()
{
int i,j;
scanf("%d",&n);
//这里是直接读入一个邻接矩阵
for(i=1;i<=n;i++) {
for(j=1;j<=n;j++) {
scanf("%d",&g[i][j]);
if(g[i][j]==0 && i!=j) g[i][j] = INF;//如果邻接矩阵中有0并且不是对角线上就设置为无穷大
}
}
SPFA(1);//算出序号为1到其他结点的最短距离
for(i=1;i<=n;i++) {
printf("%d ",d[i]);
}
while(1);
return 0;
}
// 最朴素的版本 前提是没有负环的骚扰
void SPFA(int s)
{
//第一段是初始化内容
int i=0,k=0,f=1,r=0;//f = front 队头大哥 r = rear 队尾小弟
q[f] = s;//把出发点加入队
use[s] = 1;//表示出发点已经在队里了
for(i=1;i<=n;i++) d[i] = INF;//设置出发点到其他点的距离都为无穷
d[s] = 0;//出发点到它本身的距离为0
//----------------朴素的分割线--------------------------------------
//第二段是计算
while(f!=r) {//如果不是空队(这里用的是循环队列)
r=r%n+1;//使r自加(因为是循环队列所以要mod n 注意所有数组都是从1开始存的!)
use[r] = 0;//表示队尾元素不在队列里
k = q[r];//取出队尾元素
for(i=1;i<=n;i++) {//从1到n遍历
if(d[i]-d[k]>g[k][i]) {
//其实是 d[i] > g[k][i] + d[k] 为了防止溢出写成了减法
//松弛操作
d[i] = g[k][i] + d[k];
if(use[i]==0) {//如果i不在队里
f=f%n+1;//把i加入队
q[f]=i;
use[i]=1;//表示i在队里
}
}
}
}
}
如果要求多源最短路
那么算法就要这样更改:
先将d[n]数组改成d[n][n]的二维数组(n为数据的最大规模)
这样d[i][j]就表示从 i 顶点到 j 顶点的最短距离
// 多源最短路的版本 前提是没有负环的骚扰 更改的内容用"!"指出
void SPFA(int s)
{
int i=0,k=0,f=1,r=0;
q[f] = s;
use[s] = 1;
for(i=1;i<=n;i++) d[s][i] = INF; //!!!!!!!!!!!!!!!
d[s][s] = 0;//!!!!!!!!!!!!!!!
while(f!=r) {
r=r%n+1;
use[r] = 0;
k = q[r];
for(i=1;i<=n;i++) {
if(d[s][i]-d[s][k]>g[k][i]) {//!!!!!!!!!!!!!!!!!
d[s][i] = g[k][i] + d[s][k];//!!!!!!!!!!!!!!
if(use[i]==0) {
f=f%n+1;
q[f]=i;
use[i]=1;
}
}
}
}
}
//这样调用:
int main()
{
for(i=1;i<=n;i++) {
SPFA(i);
}
}
如果SPFA与负环邂逅,就要在后面加上一点判断
对于负环有这样的结论:在一个n顶点的图里,对于一个顶点最多最多做n-1次松弛,如果松弛次数达到了n次以上,那毫无疑问这是一个有负环的图
用数组time[n]来保存顶点松弛的次数
//负环版本 更改的部分用"!"指出
int SPFA(int s)
{
int i=0,k=0,f=1,r=0;
q[f] = s;
use[s] = 1;
for(i=1;i<=n;i++) {
d[s][i] = INF;
time[i] = 0;//!!!!!!!!!!!!
}
d[s][s] = 0;
time[s]++; //!!!!!!!!!!!!
while(f!=r) {
r=r%n+1;
use[r] = 0;
k = q[r];
for(i=1;i<=n;i++) {
if(d[s][i]-d[s][k]>g[k][i]) {
d[s][i] = g[k][i] + d[s][k];
if(use[i]==0) {
f=f%n+1;
q[f]=i;
time[i]++;//!!!!!!!!!!!!!!
if(time[i]>=n) return 1;//返回1表示有负环
use[i]=1;
}
}
}
}
return 0;//返回0表示没有负环
}