Bellman-Ford算法是一种求单源最短路算法,时间复杂度:O(V * E)(V为图的节点数,E为图的边数),效率很低,但比起dijkstra算法,它可以处理负权边,而且能判断源点是否有负权环(floyd算法只能求最短路不能判断有无),即从源点经过一段路后回到原点有无负权和。
过程:
BELLMAN-FORD(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 for i ← 1 to |V[G]| - 1 3 do for each edge (u, v) ∈ E[G] 4 do RELAX(u, v, w) 5 for each edge (u, v) ∈ E[G] 6 do if d[v] > d[u] + w(u, v) 7 then return FALSE 8 return TRUE
for(节点数-1) // 跟 dijkstra一样
对每条边松弛
for(每条边)
if(可以松弛)
return 存在负边
return 无负边
常用一个数组dis[]表示源点到各点距离
结构体{
int u; // 起点
int v; // 终点
int w; // 边权
}edge[]表示边的信息
松弛部分很好理解,源点到某一边的终点距离 小于 源点到该边的起点距离 + 起点到终点距离 就更新 到终点距离
if( dis[edge[j].u] + edge[j].w < dis[edge[j].v] )
{
dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
}
下面看POJ 3259 例题
题意:有F个农场(测试数据组数),N个田(节点),M条双向路径(双/无向正权边),W个虫洞(单向负权边),问能否遇见原先的自己(负权环)
这题输入部分
输入F;
while(F--)
{
输入N,M,W;
1 -> M
加边入图(双向边就当成两条边处理)
1 -> W
负权边入图(注意负权)
进入算法
}
还可以用标记变量优化,见代码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define NMAX 2502
#define INF 0x7F7F7F7F
#define eps 10^(-6)
#define MEM(a) memset(a,0,sizeof(a));
#define MEM_MAX(a) memset(a,INF,sizeof(a));
#define FOR(i,n) for(int i=0;i<n;i++)
#define FIN freopen("in.txt","r",stdin);
#define FOUT freopen("out.txt","w",stdout);
struct node
{
int u,v,w; //边的信息
}edge[NMAX*2+201];
int dis[NMAX]; //源点到各点距离
int N,M,W; //N:节点数,M:正权双向边数,W:负权边数
int cnt; //总边数:不等于M+W,因为双向边要算两条边
int bellman_ford(int src)
{
for(int i=1;i<=N;i++)
dis[i] = INF;
dis[src] = 0;
for(int i=1;i<N;i++)
{
bool flag = false;
for(int j=1;j<=cnt-1;j++)
{
if( dis[edge[j].u] + edge[j].w < dis[edge[j].v] )
{
dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
flag = true;
}
}
if(!flag) //优化:如果没一条边更新,则最短路完成或有边不可达
break;
}
for(int i=1;i<=cnt-1;i++)
if(dis[edge[i].u] + edge[i].w < dis[edge[i].v])
return 0;
return 1;
}
int main()
{
int u,v,w,f,M,W;
scanf("%d",&f);
while(f--)
{
cnt = 1;
scanf("%d%d%d",&N,&M,&W);
for(int i=1;i<=M;i++)
{
scanf("%d%d%d",&u,&v,&w);
edge[cnt].u = u;
edge[cnt].v = v;
edge[cnt++].w = w;
edge[cnt].v = u;
edge[cnt].u = v;
edge[cnt++].w = w;
}
for(int i=1;i<=W;i++)
{
scanf("%d%d%d",&u,&v,&w);
edge[cnt].u = u;
edge[cnt].v = v;
edge[cnt++].w = -w; //负权
}
if(bellman_ford(1))
printf("NO\n");
else
printf("YES\n");
}
return 0;
}
此题的spfa解法
#include <cstdio>
#include <cstring>
#include <string>
#include <stack>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 505;
const int maxm = 2600;
const int inf = 0x3f3f3f3f;
int inq[maxn], head[maxn], dis[maxn]; //inq[u]==1:u在队列里
struct Edge{
int v, w, next;
} edge[maxm * 2];
int cnt;
void add_edge(int u, int v, int w){ //邻接表前插法
edge[cnt].v = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt++;
}
int times[maxn];
void init(int n){
cnt = 0;
memset(head, -1, sizeof(head));
memset(inq, 0, sizeof(inq));
memset(dis, inf, sizeof(dis));
memset(times, 0, sizeof(times));
}
int n;
bool spfa(int s, int t){
queue<int>q;
q.push(s);
dis[s] = 0;
inq[s] = 1;
times[s]++;
while (!q.empty()){
int u = q.front(); q.pop();
inq[u] = 0;
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
int w = edge[i].w;
if (dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if (!inq[v]){
inq[v] = 1;
q.push(v);
times[v]++;
if(times[v] > n){
return false;
}
}
}
}
}
return true;
}
int main() {
int m, a, b, c;
int te;
cin>>te;
int qq;
while(te--){
cin >> n >> m>>qq;
init(n);
for (int i = 0; i < m; ++i){
cin >> a >> b >> c;
add_edge(a, b, c);
add_edge(b, a, c);
}
for (int i = 0; i < qq; ++i){
cin >> a >> b >> c;
add_edge(a, b, -c);
}
if(spfa(1, n))
cout<<"NO"<<endl;
else
cout<<"YES"<<endl;
}
return 0;
}