Bellman-Ford算法

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;
}

  • 7
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值