Bellman-Ford算法:
核心思路: 源点d值设为0,其他d值设为INF。执行N-1次操作(N为结点数),每次遍历所有边来进行松弛操作。N-1次操作结束后,再遍历一次所有边,若还能继续松弛,说明有源点可达的负环。
模板:
struct edge
{
int u;
int v;
int w;
} a[maxm];
int N,M; //N个结点,M条边
int d[maxn];
bool BF(int s)
{
fill(d,d+maxn,INF);
d[s]=0;
for(int i=1;i<=N-1;i++) //进行N-1次操作
{
for(int j=1;j<=M;j++) //每轮操作遍历所有边
{
int u=a[j].u,v=a[j].v,w=a[j].w;
if(d[u]+w<d[v]) //以u为中介点可以使d[v]更小
d[v]=d[u]+w; //松弛操作
}
}
for(int j=1;j<=M;j++) //再遍历一遍
{
int u=a[j].u,v=a[j].v,w=a[j].w;
if(d[u]+w<d[v]) //如果仍然可以被松弛
return false; //说明有源点可达的负环
}
return true;
}
PS. 这里可以进行一点优化,若在N-1轮操作内的某一轮没有进行松弛操作,那么说明已求得最短路,可以直接结束。
SPFA算法:
核心思路: 由于只有当某个结点u的d[u]值被改变时,从它出发的边的邻接点v的d[v]值才有可能被改变,那么就可以利用队列对BF算法进行优化,得到SPFA算法。另外当 某个结点的入队次数 > N-1 时,说明有源点可达的负环。
模板:
int G[maxn][maxn]
int N;
int d[maxn],cnt[maxn]; //cnt记录结点入队次数
bool inq[maxn]; //inq标记结点是否在队列中
bool SPFA(int s)
{
fill(d,d+maxn,INF);
memset(cnt,0,sizeof(cnt));
memset(inq,false,sizeof(inq));
queue<int> q;
d[s]=0;
q.push(s); //源点入队
inq[s]=true;
cnt[s]++; //源点入队次数+1
if(cnt[s]>N-1)
return true;
while(!q.empty())
{
int u=q.front();
q.pop(); //出队
inq[u]=false;
for(int v=1;v<=N;v++)
{
if(G[u][v]!=INF&&d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v]; //进行松弛操作
if(!inq[v]) //若v不在队列中,则入列
{ //注意该判断语句不能放到上一个if中!!!
q.push(v);
inq[v]=true;
cnt[v]++; //入队次数+1
if(cnt[v]>N-1) //若入队次数>N-1时,说明有源点可达的负环
return true;
}
}
}
}
return false;
}
最后还要注意一点,BF算法和SPFA算法只能判断是否存在源点可达的负环,对于源点不可达的(即和源点不属于同一连通块),无法判断负环存在与否。