SPFA(用邻接表实现)

一.Dijkstra、Bellman-Ford及Spfa算法思想对比

1.dijkstra算法本质上算是贪心的思想,每次在剩余节点中找到离起点最近的节点放到队列中,并用来更新剩下的节点的距离,再将它标记上表示已经找到到它的最短路径,以后不用更新它了。这样做的原因是到一个节点的最短路径必然会经过比它离起点更近的节点,而如果一个节点的当前距离值比任何剩余节点都小,那么当前的距离值一定是最小的。(剩余节点的距离值只能用当前剩余节点来更新,因为求出了最短路的节点之前已经更新过了)
dijkstra就是这样不断从剩余节点中拿出一个可以确定最短路径的节点最终求得从起点到每个节点的最短距离。

2.Bellman-Ford

bellman-ford算法进行n-1次更新(一次更新是指用所有节点进行一次松弛操作)来找到到所有节点的单源最短路。bellman-ford算法和dijkstra其实有点相似,该算法能够保证每更新一次都能确定一个节点的最短路,但与dijkstra不同的是,并不知道是那个节点的最短路被确定了,只是知道比上次多确定一个,这样进行n-1次更新后所有节点的最短路都确定了(源点的距离本来就是确定的)。

for(int k=1;k<=n;k++)
    for(int i=1;i<=m;i++)
        if(dis[v[i])>dis[u[i]]+w[i])
            dis[v[i])>dis[u[i]]+w[i];


bellman-ford的一个优势是可以用来判断是否存在负环,在不存在负环的情况下,进行了n-1次所有边的更新操作后每个节点的最短距离都确定了,再用所有边去更新一次不会改变结果。而如果存在负环,最后再更新一次会改变结果。原因是之前是假定了起点的最短距离是确定的并且是最短的,而又负环的情况下这个假设不再成立。

3.spfa

可以看成是bellman-ford的队列优化版本,正如在前面讲到的,bellman每一轮用所有边来进行松弛操作可以多确定一个点的最短路径,但是用每次都把所有边拿来松弛太浪费了,不难发现,只有那些已经确定了最短路径的点所连出去的边才是有效的,因为新确定的点一定要先通过已知(最短路径的)节点

所以我们只需要把已知节点连出去的边用来松弛就行了,但是问题是我们并不知道哪些点是已知节点,不过我们可以放宽一下条件,找哪些可能是已知节点的点,也就是之前松弛后更新的点,已知节点必然在这些点中。
所以spfa的做法就是把每次更新了的点放到队列中记录下来。

在实现上,bellman-ford可以用边集数组或链式前向星来实现。
而spfa则只能用链式前向星实现。

在效率上,当图很稠密的时候spfa就退化成和bellman -ford差不多了,因为对于入队的每个节点都要和很多节点去进行松弛操作。


二、给出spfa的代码

#include
#include
#include
#include
using namespace std;
const int maxn=1e5+5;
const int inf=99999999;
int dis[maxn],vis[maxn],n,m,i;
struct edge{
	int v,w;
	edge(){}
	edge(int vv,int ww){
	   v=vv;
	   w=ww;
	} 
};
vector >a(maxn);
queueq;
int main()
{
	int u,v,w;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		a[u].push_back(edge(v,w));
	}
	memset(vis,0,sizeof(vis));
	for(i=1;i<=n;i++)dis[i]=inf;
	dis[1]=0;
	vis[1]=1;
	while(!q.empty())q.pop();
	q.push(1);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(i=0;idis[u]+w)
			{
				dis[v]=dis[u]+w;	
			    if(!vis[v])
			    {
				  q.push(v);
				  vis[v]=1;
			   }
		   }
	    }
	}
	for(i=1;i<=n;i++)
	{
		printf("%d ",dis[i]);
	}
	return 0;
}

 

你可能感兴趣的:(算法)