Bellman_ford算法解决带负权边的带权有向图单源最短路问题。当图中不存在带负权的环时,可以求出单源最短路;当图中存在负权环时,可以判断出来;
时间复杂度:
邻接矩阵存图时 V^3;
邻接表存图时 V*E
算法思想:(设源点为s)
构造一个最短路径长度数组序列dist(1)[u],dist(2)[u],dist(3)[u],,,,,,,,dist(n-1)[u];其中:dist(k)[u]表示从源点s只经过k条边(不构成负权回路)的最短路径长度,则有dist(1)[u] = 边(s,u)权值,最终结果为dist(n-1)[u];
求dist(k)[u]:
(1)、当k = 1 时,dist(1)[u] = 边(s,u)权值;
(2)、假设已经求出了dist(k-1)[u](u = 0,1,,,,n-1);则用每一个和u相连的节点v(对于每条边(v,u)),计算出min(dist(k-1)[v] + 边(v,u)的权值为w;则dist(k) = min(dist(k-1)[u],w);
负权回路判断:求出dist(n-1)[u](u = 0,1,2,,,,n-1之后),在对每条边(x,y)判断一下:dist[x] + 边(x,y)的值 <dist[y];如果等式成立则有负权回路(n各节点的图中,某条路径上有n条边,则一定有回路);
poj 3259 Bellman _ford 模板题(邻接表存图);
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
#include<string>
#include<stack>
#define ll long long
#define MAX 1000
#define INF 1000000000
#define eps 1e-8
using namespace std;
struct Edge{
int from, to, dist;
};
vector<int>G[MAX];
vector<Edge>edges;
int n;
void init(){
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});
int k = edges.size();
G[from].push_back(k-1);
}
bool Bellman_ford(int s){
int d[MAX];
for (int i=0; i<n; i++) d[i] = (i == s ? 0 : INF);
for (int i=1; i<n; i++){
for (int j = 0; j<edges.size(); j++){
Edge e = edges[j];
if (d[e.from] < INF){
d[e.to] = min(d[e.to] , d[e.from] + e.dist);
}
}
}
for (int i=0; i<edges.size(); i++){
Edge e = edges[i];
if (d[e.to] > d[e.from] + e.dist) return true;
}
return false;
}
int main(){
int T,m,w;
scanf("%d",&T);
while (T--){
init();
scanf("%d%d%d",&n,&m,&w);
int x,y,z;
for (int i=0; i<m; i++){
scanf("%d%d%d",&x,&y,&z);
addEdge(x-1,y-1,z);
addEdge(y-1,x-1,z);
}
for (int i=0; i<w; i++){
scanf("%d%d%d",&x,&y,&z);
addEdge(x-1,y-1,-z);
}
if (Bellman_ford(0)) printf("YES\n");
else printf("NO\n");
}
return 0;
}
SPFA:
用于快速求解单源最短路和判断图中是否有负权回路,是Bellma_ford的改进,采用了队列优化;期望的时间复杂度为O(E);
算法思想:(源点为s; d[i]记录节点i 到源点的当前最短距离);
(1)初始化d[s] = (s == 0 ? 0 : INF),维护一个队列,里面存放所有需要进行迭代的点,初始化只有一个点s;并用一个数组表示节点是否在队列中,一个数组记录节点的入队次数。
(2)每次迭代时取队首元素u;枚举所有与之相连的边v;(u->v)设其权值为w;判断d[u] + w < d[v];如果成立:更新d[v];判断v是否在队列中,若不在,则入队并判断节点v入队次数,如果入队次数大于等于n则存在负权回路;
poj3259 SPFA模板题目(判断有无负权回路);
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<climits>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<string>
#include<map>
#define ll long long
#define MAX 1000
#define INF 1000000000
#define eps 1e-8
using namespace std;
struct Edge{
int from,to,dist;
};
vector<int>G[MAX];
vector<Edge>edges;
int n;
void init(){
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});
int k = edges.size();
G[from].push_back(k-1);
}
bool Bellman_ford(int s){
bool inq[MAX]; //记录节点是否在队列中
int cnt[MAX]; //记录节点入队的次数
queue<int>q;
int d[MAX];
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
for (int i=0; i<n; i++) d[i] = (i == s ? 0 : INF);
for (int i=0; i<n; i++){
q.push(i); //首先让所有点都入队,防止图不连通。 求连通图的最短路时,只需先将源点入队就行
inq[s] = true;
//d[i] = 0; 只是判断有无负权回路可以直接让d[i] = 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;
if (!inq[e.to]){ //思考?什么时候才需要入队
q.push(e.to);
inq[e.to] = true;
if (++cnt[e.to] >= n) return true; //当某个节点入队超过n次说明图中存在负权回路
}
}
}
}
return false;
}
int main(){
int T,m,w;
scanf("%d",&T);
while (T--){
init();
scanf("%d%d%d",&n,&m,&w);
int x,y,z;
for (int i=0; i<m; i++){
scanf("%d%d%d",&x,&y,&z);
addEdge(x-1,y-1,z);
addEdge(y-1,x-1,z);
}
for (int i=0; i<w; i++){
scanf("%d%d%d",&x,&y,&z);
addEdge(x-1,y-1,-z);
}
if (Bellman_ford(0)) printf("YES\n");
else printf("NO\n");
}
return 0;
}