Bellman-Ford算法大致可以分成三部分:
1.初始化所有d[s],源点d[s]=0,其他d[s]=INF
2.进行n-1次循环,在循环体中遍历所有的边,进行松弛计算(if(d[v]>d[u]+w[u][v]) d[v]=d[u]+w[u][v])
3.遍历图中所有的边,检验是否出现这种情况:d[v]>d[u]+w[u][v],若出现则返回false,没有最短路
Bellman_Ford(G, w, s)
d[s]←0 //初始化,s到s最短路权值为0,其他为正无穷
for each v∈V-{s}
d[v]←∞
parent[v]← NIL //为生成最短路径树起作用
//最多只需|V|-1次外层循环,|V|-1次结束后,若图G中无负权回路,那么s到其他所有顶点的最短路径求得
for i ← 1 to |V|-1
for each edge (u, v)∈E //算法核心,松弛每一条边,维持三角不等式成立
if d[v] > d[u] + w(u, v)
d[v] ← d[u]+w(u, v)
parent[v]←u //边(u,v)松弛成功,则u为v的前驱顶点,记录到parent[]
//进行完|V|-1次循环操作后,如果还能某条边还能进行松弛,说明到某个点的最短路径还未找到,那么必定是存在负权回路,返回FALSE
for each edge (u, v)∈E
if d[v] > d[u] + w(u, v)
return FALSE
return TRUE
//若进行上面的松弛之后没有返回,说明所有的d值都不会再改变了,那么最短路径权值完全找到,返回TRUE
因为经过数学证明,在没有负权回路存在的情况下,所有边最多进行n-1次松弛操作。如果进行n次迭代,则最短路要经过n条边,则必有两个点是重复的,则一定存在环路,并且是负环。
当负权存在时,连最短路都不一定存在了。如果最短路存在,一定存在一个不含环的最短路。因为在边权可正可负的图中,环有零环、正环和负环3种。如果包含零环或正环,去掉以后路径不会变长;如果包含负环,则意味着最短路不存在。
既然不含环,最短路最多只经过(起点不算)n-1个节点,可以通过n-1“轮”松弛操作得到,像这样(起点仍然是0):
for(int i=0;i<n;i++) d[i]=INF;
d[s]=0;
for(int k=0;k<n-1;k++){//迭代n-1次
for(int i=0;i<m;i++){//检查每条边
int x=u[i],y=v[i];
if(d[x]<INF) d[y]=min(d[y],d[x]+w[i]);//松弛
}
}
如果求s与t之间的最短路,此时图中虽有负权回路,但是1点与t点之间没有路,负环不在s点与t点的路径上,所以此时负权回路不影响求s与t之间的最短路。
模板中迭代的次数有其实际意义,假如当前迭代(需要使用备份数组实现)次数为k次,d[]数组的含义为源点s到每个点的最短距离经过不超过k条边
1、Bellman-Ford算法求解经过不超过k条边的最短路
给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible。
注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数n,m,k。
接下来m行,每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。
输出格式
输出一个整数,表示从1号点到n号点的最多经过k条边的最短距离。
如果不存在满足条件的路径,则输出“impossible”。
数据范围
1≤n,k≤500,1≤m≤10000,任意边长的绝对值不超过10000。
输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3
#include <iostream>
#include <cstring>
using namespace std;
const int N=510,M=1e4+10,INF=0x3f3f3f3f;
struct Edge{
int u,v,w;
}edges[M];
int d[N],backup[N];
int n,m,k;
int Bellman_ford(){
memset(d,0x3f,sizeof d);
d[1]=0;
for(int i=0;i<k;i++){
memcpy(backup,d,sizeof d);//备份数组
for(int j=0;j<m;j++){
int u=edges[j].u,v=edges[j].v,w=edges[j].w;
d[v]=min(d[v],backup[u]+w);
}
}
if(d[n]>INF/2) return -1;
/*
此处不能写d[n]==INF
*/
return d[n];
}
int main(){
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
edges[i]={u,v,w};
}
int t=Bellman_ford();
if(t==-1) puts("impossible");
else printf("%d",t);
return 0;
}
1号点不能到5号点,1号点也不能到n号点,但是5号点处的正无穷可以更新n号点的正无穷,但是d[n]不会减少太多
backup备份数组的妙用:使用备份数组可保证求最短距离经过的边不超过当前迭代次数。如果直接使用d[v]=min(d[v],d[u]+w)进行更新,有可能会发生串连现象。
当k==1时,只进行一次迭代,d[3]应该等于3,但是使用d[v]=min(d[v],d[u]+w)进行更新时,d[2]更新为1后,d[3]就会被更新为2,即在一次迭代中求出的最短距离经过了两条边,是不正确的。这种情况可以备份d[]数组,d[v]=min(d[v],backup[u]+w),即更新时只使用上次迭代的结果,此时不会发生串连,d[3]=3。
2、Bellman-Ford算法求解经过恰好k条边的最短路(可以重复经过)
给定一张由T条边构成的无向图,点的编号为1~1000之间的整数。
求从起点S到终点E恰好经过N条边(可以重复经过)的最短路。
注意: 数据保证一定有解。
输入格式
第1行:包含四个整数N,T,S,E。
第2..T+1行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。
输出格式
输出一个整数,表示最短路的长度。
数据范围
2≤T≤1002≤T≤100,
2≤N≤1062≤N≤106
输入样例:
2 6 6 4
11 4 6
4 4 8
8 4 9
6 6 8
2 6 9
3 8 9
输出样例:
10
#pragma GCC optimize(2)
#include <iostream>
#include <cstring>
using namespace std;
int hashTable[1010];
const int N = 210, M = 1000005;
int dist[N], back_up[N];
struct Edge{
int x, y, z;
}edge[M];
int n, k, m, S, E;
void inline read(int &x) {
x = 0;
char c = getchar();
while(!(c >= '0' && c <= '9')) c = getchar();
while(c >= '0' && c <= '9'){
x = x * 10 + c - '0';
c = getchar();
}
}
void bellman_ford(){
memset(dist, 0x3f, sizeof(dist));
dist[S] = 0;
for(int i = 0; i < k; i ++){
memcpy(back_up, dist, sizeof(dist));
memset(dist, 0x3f, sizeof(dist));
for(int j = 0; j < m; j ++){
int x = edge[j].x, y = edge[j].y, z = edge[j].z;
dist[y] = min(dist[y], back_up[x] + z);
dist[x] = min(dist[x], back_up[y] + z);
}
}
}
int main(){
scanf("%d%d%d%d", &k, &m, &S, &E);
if(!hashTable[S]) hashTable[S] = ++ n;
if(!hashTable[E]) hashTable[E] = ++ n;
S = hashTable[S], E = hashTable[E];
for(int i = 0; i < m; i ++){
int x, y, z;
read(z), read(x), read(y);
//scanf("%d%d%d", &z, &x, &y);
if(!hashTable[x]) hashTable[x] = ++ n;
if(!hashTable[y]) hashTable[y] = ++ n;
edge[i].x = hashTable[x], edge[i].y = hashTable[y], edge[i].z = z;
}
bellman_ford();
printf("%d", dist[E]);
return 0;
}