单源最短路:SPFA学习笔记

单源最短路: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表示没有负环
}



点赞