在家的学习效率实在太低,想想暑假都要过完了,什么东西都没学到。
如果你还没理解spfa 的过程,可以先看一下 最短路 - spfa - (一) 这篇文章的图解,接下来,我们就好好讲一讲spfa算法。
SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作是一种高效的最短路算法。相比于Dijkstra,SPFA可以计算带负环的回路。邻接表的复杂度为:O(kE)E为边数,k一般为2或3,k为所有顶点进队的平均次数。
过程:用数组dis记录更新后的状态,cnt记录更新的次数,队列q记录更新过的顶点,算法依次从q中取出待更新的顶点U,按照dis(k)[u]的递归式计算。在计算过程中,一旦发现顶点K有cnt[ k ] > n,说明有一个从顶点K出发的负权圈,此时没有最短路,应终止算法。否则,队列为空的时候,算法得到G的各顶点的最短路径长度。
关于松弛,个人感觉就是缩小路径上权值的上界。
负环的操作及判断,将在文末给出。
关于优化,将会在以后更新小讲一下。
下面来一个用邻接矩阵实现的模版吧,注意一下初始化和松弛操作
void SPFA(int s)
{
for(int i = 0; i < n; i ++)
dis[i] = INF;
vis[s] = true;
dis[s] = 0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int cur = q.front();
q.pop();
vis[cur] = false;
for(int i = 0; i < n; i ++)
{
if(dis[cur] + map[cur][i] < dis[i])
{
dis[i]=dis[cur] + map[cur][i];
if(!vis[i])
{
q.push(i);
vis[i] = true;
}
}
}
}
}
相信看完图解,可以很容易地理解。下面我们来看一些题目
第一题
POJ 2387 Til the Cows Come Home http://poj.org/problem?id=2387
本题就是很普通的最短路了,输入n, m,求1 到 n 的最短的距离,你可以用其它方法A掉,但是今天我们用一下spfa, 还有就是这几天看了好久的邻接表,所以这题就用邻接表来吧,过几天,会写一篇关于邻接表的。
#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 2005
#define INF 1 << 29
struct Edge
{
int to, next, dis;
}edge[2 * N];
int head[N];
int d[N];
bool vis[N];
int n, m;
int k;
void init()
{
memset(head, -1 , sizeof(head));
k = 1;
}
void addedge(int s, int t, int dist) // 用邻接表实现图的存储
{
edge[k].to = t;
edge[k].dis = dist;
edge[k].next = head[s];
head[s] = k ++;
}
int spfa(int s) // spfa 操作,如有问题,可以参看上一篇的spfa图解
{
queue<int> que;
for(int i = 1; i <= n; i ++)
d[i] = INF;
d[s] = 0;
memset(vis, 0, sizeof(vis));
que.push(s);
while(!que.empty())
{
int t = que.front();
que.pop();
vis[t] = false;
for(int i = head[t]; i != -1; i = edge[i].next)
{
int v = edge[i].to; // 起点
int w = edge[i].dis; // 距离
if(d[v] > d[t] + w)
{
d[v] = d[t] + w;
if(!vis[v]) // 若该点未在队列中,将改点拉入队列
{
que.push(v);
vis[v] = true;
}
}
}
}
return d[n]; // 返回1 到 n 的距离,即本题的答案
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
int a, b, c;
init();
for(int i = 1; i <= m; i ++)
{
scanf("%d%d%d",&a,&b,&c);
addedge(a, b, c); // 无向图,首尾都来一次
addedge(b, a, c);
}
printf("%d\n", spfa(1));
}
return 0;
}
关于负环的操作
下面给个 “所谓的模版” ,具体还是要看题意,也要注意各种初始化的问题
struct edge
{
int to, next, dis;
}e[MAX];
bool spfa()
{
for(int i = 0; i <= n; i ++)
dis[i] = INF;
queue<int> q;
dis[0] = 0;
vis[0] = true;
cnt[0] = 1;
q.push(0);
while(!q.empty())
{
int cur = q.front();
q.pop();
vis[cur] = false;
for(int i = head[cur]; i != -1; i = e[i].next)
{
int id = e[i].to;
if(dis[cur] + e[i].dis > dis[id])
{
dis[id] = dis[cur] + e[i].dis;
if(!vis[id])
{
cnt[id] ++;
if(cnt[cur] > n)
return false;
vis[id] = true;
q.push(id);
}
}
}
}
return true;
}
练习(你可以尝试各种最短路的算法,提高对算法的熟练度)
HDU 1874 畅通工程续 http://acm.hdu.edu.cn/showproblem.php?pid=1874
POJ 1201 Intervals http://poj.org/problem?id=1201