介绍: 来自:https://www.cnblogs.com/Mr94Kevin/p/9513642.html
Bellman-Ford算法是一种单源最短路算法,允许图中有负边权。Bellman-Ford算法的效率相对较低,但是很容易编写,也很好理解。比较流行的SPFA算法其实就是他的队列优化。Bellman-Ford算法的流程大体是这样,先将源点的最短路设置为0,将其他结点的最短路设置为inf,然后进行n-1次迭代,每次检查每条边进行松弛操作,这样就得到了其他结点的最短路。而且可以检查是否存在负环,若n次迭代仍然可以进行松弛操作,则说明存在负环。不难看出时间复杂度是O(nm)的。怎么理解Bellman-Ford算法呢?不妨想想一棵最短路径树,根是源点,叶子结点是图中其他结点,叶子结点到根节点的路径就是其他结点的最短路。虽然这棵树一开始我们并不知道什么样子,但他是客观存在的。而Bellman-Ford算法就是逐层构造这棵最短路树,假设处理到了第i层,那么第i-1层已经处理完毕,那么扫描每一条边,看是否可以松弛,一定可以由第i-1层扩展到第i层。如果把源点看做第0层,那么这棵树最多有n-1层,因此迭代n-1次一定可以得到这棵最短路树(如果存在的话),那么如果第n次仍然可以进行松弛操作,说明不存在最短路(存在负环)。
Bellman_Ford判负权环模板
//Bellman_Ford标准版模板_SPFA(能判负圈)
//求的是从s点到其他点的单源最短路径,复杂度O(n*m)
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 1e9
const int maxn=500+8;
struct Edge
{
int from,to,dist;
Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
struct BellmanFord
{
int n,m; //点数和边数,编号都从0开始
vector<Edge> edges; //边列表
vector<int> G[maxn];//每个节点出发的边编号(从0开始编号)
bool inq[maxn]; //是否在队列中
int d[maxn]; //s到各个点的距离
int p[maxn]; //最短路中的上一条弧
int cnt[maxn]; //进队次数
void init(int n)
{
this->n=n;///判负环时需要增加一个超级零点,则点的数量在初始化时增加1
for(int i=0;i<n;i++) G[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int dist)
{
edges.push_back(Edge(from,to,dist));
m = edges.size();
G[from].push_back(m-1);///记录每个边的编号,从0开始
}
///计算以s为源点的最短路径,如果判断是否存在负环可省略传值第五行的s都改为0
//如果图中存在s能到达的负圈,那么返回true
bool negativeCycle(int s)
{
queue<int> Q;
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++) d[i]= i==s?0:INF;
Q.push(s);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
inq[u]=false;
for(int i=0;i<G[u].size();i++)
{
Edge &e=edges[G[u][i]];
if(d[e.to] > d[u]+e.dist)
{
d[e.to] = d[u]+e.dist;///更新到达新一个点的距离
p[e.to] = G[u][i];///将最短路上的边存储起来
if(!inq[e.to])///若此点不在队列则将其添加到队列里
{
Q.push(e.to);
inq[e.to]=true;
if(++cnt[e.to]>n) return true;
}
}
}
}
return false;
}
}BF;
判负权环例题
题目连接:http://poj.org/problem?id=3259
题意:
给定一个有向图(注意:给定的路径里存在双向的边,注意区分),判断是否存在负权环。
分析:
套用上面刘汝佳判负权环的模板,稍微改动一下,添加一个超级零点即可。
参考博客:https://blog.csdn.net/u013480600/article/details/37822627
代码:
//Bellman_Ford标准版模板_SPFA(能判负圈)
//求的是从s点到其他点的单源最短路径,复杂度O(n*m)
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 1e9
const int maxn=500+8;
struct Edge
{
int from,to,dist;
Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
struct BellmanFord
{
int n,m; //点数和边数,编号都从0开始
vector<Edge> edges; //边列表
vector<int> G[maxn];//每个节点出发的边编号(从0开始编号)
bool inq[maxn]; //是否在队列中
int d[maxn]; //s到各个点的距离
int p[maxn]; //最短路中的上一条弧
int cnt[maxn]; //进队次数
void init(int n)
{
this->n=n;///判负环时需要增加一个超级零点,则点的数量在初始化时增加1
for(int i=0;i<n;i++) G[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int dist)
{
edges.push_back(Edge(from,to,dist));
m = edges.size();
G[from].push_back(m-1);///记录每个边的编号,从0开始
}
///计算以s为源点的最短路径,如果判断是否存在负环可省略传值第五行的s都改为0
//如果图中存在s能到达的负圈,那么返回true
bool negativeCycle()
{
queue<int> Q;
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++) d[i]= i==0?0:INF;
Q.push(0);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
inq[u]=false;
for(int i=0;i<G[u].size();i++)
{
Edge &e=edges[G[u][i]];
if(d[e.to] > d[u]+e.dist)
{
d[e.to] = d[u]+e.dist;///更新到达新一个点的距离
p[e.to] = G[u][i];///将最短路上的边存储起来
if(!inq[e.to])///若此点不在队列则将其添加到队列里
{
Q.push(e.to);
inq[e.to]=true;
if(++cnt[e.to]>n) return true;
}
}
}
}
return false;
}
}BF;
int main()
{
int T; scanf("%d",&T);
while(T--)
{
int n,m,w;
scanf("%d%d%d",&n,&m,&w);
BF.init(n+1);
while(m--)
{
int u,v,d;
scanf("%d%d%d",&u,&v,&d);///非虫洞里的为单向边
BF.AddEdge(u,v,d);
BF.AddEdge(v,u,d);
}
while(w--)
{
int u,v,d;
scanf("%d%d%d",&u,&v,&d);
BF.AddEdge(u,v,-d);
}
for(int i=1;i<=n;i++)///添加一个超级零点
BF.AddEdge(0,i,0);
if(BF.negativeCycle()) printf("YES\n");
else printf("NO\n");
}
return 0;
}