简单的说,spfa就是通过队列·来实现的求最短路径的算法;
首先,要用到一个邻接表来存储各顶点之间的关系(包括起点u,终点v,以及u,v之间的权值w)。然后就是构造spfa函数;
第一步:对dis[ ]( 用来存从源点到各顶点的最短距离 )初始化为 INF(一个很大的数例如0x3f3f3f3f), 对 vis[ ]数组初始化为零(表示未有点入队列);
构造一个队列 queue< int >Q;并将源点 s加入队列;dis[s] = 0( 表示源点本身到本身的距离为零);vis[s] =1(表示源点进入队列)
第二步:从源点开始找到其他各点的最短路径,找到与源点相连的点的边对其进行松弛操作*(即当< s,v> 的距离大于 + 的距离时,将d[v]更新为+的距离)
当顶点v未入队列就将其加入队尾并标记为1,然后仍是取队头元素,以其为新的源点继续进行松弛->更新的操作;直到队列为空;
(注意,每次取对头元素时要将对头元素取消标记即令vis[u] = 0,)
另外,spfa的另一个好处就是来求含有负权值图的最短路径问题(前提图中不存在负权回路,);
还有就是当1、2两点有重边时;用spfs不用判重;因为1->2和2->1属于不同的邻接表中;(顶点不一样)
当图中含有负权回路时,不存在最短路径(因为有负权回路,它将能无限最短);也就有无限次出对入队,判断它的方法就是看图中的点入队的次数如果大于图中含有的顶点总数N的话,说明图中存在负权回路;
SPFA模板:
以杭电2544《最短路》为例:
#include
#include
#include
#include
#include
#include
#define MAXN 100+10//点数
#define MAXM 20000+10//边数
#define INF 0x3f3f3f
using namespace std;
struct Edge
{
int from, to, val, next;
};
Edge edge[MAXM];
int head[MAXN], edgenum;
int N, M;
void addEdge(int u, int v, int w)
{
Edge E = {u, v, w, head[u]};
edge[edgenum] = E;
head[u] = edgenum++;
}
void init()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
int dist[MAXN]; //存储源点 到这个点的最短路
int vis[MAXN]; //表示这个点是否在队列里面
//int used[MAXN]; //记录一个点 入队多少次
void SPFA(int sx) //sx为源点
{
queue Q; //存储每次入队的点
memset(dist, INF, sizeof(dist));
memset(vis, 0, sizeof(vis));
//memset(used, 0, sizeof(used));
Q.push(sx);
dist[sx] = 0;
vis[sx] = 1;
//used[sx]++;
while(!Q.empty())
{
int u = Q.front();//N
Q.pop();
vis[u] = 0;
for(int i = head[u]; i != -1; i = edge[i].next)//遍历以u为起点的 所有边
{
int v = edge[i].to;
if(dist[v] > dist[u] + edge[i].val)//
{
dist[v] = dist[u] + edge[i].val;
if(!vis[v])
{
vis[v] = 1;
Q.push(v);
// used[v]++;
// if(used[v] > N)//图中有负环
// return
}
}
}
}
printf("%d\n", dist[N]);
}
void getMap()
{
int a, b, c;
while(M--)
{
scanf("%d%d%d", &a, &b, &c);
addEdge(a, b, c),
addEdge(b, a, c);
}
}
int main()
{
while(scanf("%d%d", &N, &M), N||M)
{
init();
getMap();
SPFA(1);
}
return 0;
}