Bellman-Ford是一种简单的求单源最短路的算法,虽然复杂度较高,但适用范围广,因此不失为一种实用的算法 最重要的是好写好理解
主要思想是每次枚举每一条边,更新终点的最短路
那么枚举多少次才全部更新完呢
答案是n-1
次
简单证明:
从源点到图上任意一点的最短路径一定不经过环,所以最短路径最多包含n-1
条边
枚举n-1
次所有边相当于,枚举构成从源点到一个点最短路径的边的所有可能
(因为所有点的最短路径都可以这么来求,我们顺便把其他点的都求了)
360百科证明:
首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|v|-1条边。
其次,从源点s可达的所有顶点如果 存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按每个点实际的最短路径[虽然我们还不知道,但它一定存在]的层次,逐层生成这棵最短路径树的过程。
注意,每一次遍历,都可以从前一次遍历的基础上,找到此次遍历的部分点的单源最短路径。如:这是第i次遍历,那么,通过数学归纳法,若前面单源最短路径层次为1~(i-1)的点全部已经得到,而单源最短路径层次为i的点,必定可由单源最短路径层次为i-1的点集得到,从而在下一次遍历中充当前一次的点集,如此往复迭代,[v]-1次后,若无负权回路,则我们已经达到了所需的目的–得到每个点的单源最短路径。[注意:这棵树的每一次更新,可以将其中的某一个子树接到另一个点下]
说Bellman-Ford适用范围广,是因为有负权边可以用,有负权环也可以用
循环次数是固定的,不用担心会爆
Bellman-Ford的一个应用就是求负环
负环定义:一个边权之和为负的环 我也不知道对不对
我们跑完最短路之后,再枚举每一条边,如果还有点可以更新,说明出现了负环
(想象一下跑在一个负环上,越跑距离反而越小,跑到机子瘫了也跑不出最小值)
例题
洛谷 P3385 【模板】负环
题目描述
暴力枚举/SPFA/Bellman-ford/奇怪的贪心/超神搜索
寻找一个从顶点1所能到达的负环,负环定义为:一个边权之和为负的环。
输入输出格式
输入格式:
第一行一个正整数T表示数据组数,对于每组数据:
第一行两个正整数N M,表示图有N个顶点,M条边
接下来M行,每行三个整数a b w,表示a->b有一条权值为w的边(若w<0则为单向,否则双向)
输出格式:
共T行。对于每组数据,存在负环则输出一行"YE5"(不含引号),否则输出一行"N0"(不含引号)。
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define N 2005
#define M 6005//m最好开两倍
#define INF 10005
using namespace std;
int t,n,m;
int u,v,w;
int cnt;
int dis[N],vis[N],head[N];
struct Edge
{
int u,v,w,next;
}e[M];
inline int read()
{
char c;
int x=0,f=1;
do{
c=getchar();
if(c=='-')
f=-1;
}while(c<'0' || c>'9');
do{
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}while(c>='0' && c<='9');
return f*x;
}
inline void addedge(int u,int v,int w)
{
cnt++;
e[cnt].u=u;
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
inline void bfs1()//记下所有1能到的点
{
queue<int> q;
q.push(1);
vis[1]=1;
while(!q.empty())
{
int x=head[q.front()];
q.pop();
while(x>0)
{
int y=e[x].v;
if(!vis[y])
{
vis[y]=1;
q.push(y);
}
x=e[x].next;
}
}
}
inline bool Bellman_ford()
{
for(int i=1;i<=n;++i)
{
head[i]=-1;
dis[i]=INF;
}
dis[1]=0;
for(int i=1;i<n;++i)
{
for(int j=1;j<=cnt;++j)
{
if(dis[e[j].v]>dis[e[j].u]+e[j].w)
dis[e[j].v]=dis[e[j].u]+e[j].w;
}
}
for(int j=1;j<=cnt;++j)
{
if(dis[e[j].v]>dis[e[j].u]+e[j].w && vis[e[j].u])
//存在负环且1能到负环上的点
return true;
}
return false;
}
int main(){
t=read();
while(t--)
{
cnt=0;
memset(vis,0,sizeof(vis));
n=read(),m=read();
while(m--)
{
u=read(),v=read(),w=read();
addedge(u,v,w);
if(w>=0)//w=0也要加两次
addedge(v,u,w);
}
bfs1();
if(Bellman_ford())
printf("YE5\n");
else
printf("N0\n");
}
return 0;
}